Merge "Revert "Revert "This CL introduces new member variable on android.location.Location (see api/current.txt)."""
diff --git a/Android.bp b/Android.bp
index c97ee3d..9077344 100644
--- a/Android.bp
+++ b/Android.bp
@@ -352,12 +352,8 @@
"core/java/android/service/chooser/IChooserTargetResult.aidl",
"core/java/android/service/resolver/IResolverRankerService.aidl",
"core/java/android/service/resolver/IResolverRankerResult.aidl",
- "core/java/android/service/textclassifier/IConversationActionsCallback.aidl",
- "core/java/android/service/textclassifier/ITextClassificationCallback.aidl",
+ "core/java/android/service/textclassifier/ITextClassifierCallback.aidl",
"core/java/android/service/textclassifier/ITextClassifierService.aidl",
- "core/java/android/service/textclassifier/ITextLanguageCallback.aidl",
- "core/java/android/service/textclassifier/ITextLinksCallback.aidl",
- "core/java/android/service/textclassifier/ITextSelectionCallback.aidl",
"core/java/android/service/attention/IAttentionService.aidl",
"core/java/android/service/attention/IAttentionCallback.aidl",
"core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl",
diff --git a/api/current.txt b/api/current.txt
index 276871a..e16004c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4447,7 +4447,7 @@
public final class AutomaticZenRule implements android.os.Parcelable {
ctor @Deprecated public AutomaticZenRule(String, android.content.ComponentName, android.net.Uri, int, boolean);
- ctor public AutomaticZenRule(String, android.content.ComponentName, android.content.ComponentName, android.net.Uri, android.service.notification.ZenPolicy, int, boolean);
+ ctor public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
ctor public AutomaticZenRule(android.os.Parcel);
method public int describeContents();
method public android.net.Uri getConditionId();
@@ -10638,9 +10638,9 @@
}
public final class LocusId implements android.os.Parcelable {
- ctor public LocusId(@NonNull android.net.Uri);
+ ctor public LocusId(@NonNull String);
method public int describeContents();
- method @NonNull public android.net.Uri getUri();
+ method @NonNull public String getId();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.LocusId> CREATOR;
}
@@ -14113,14 +14113,14 @@
public class HardwareRenderer {
ctor public HardwareRenderer();
method public void clearContent();
- method public android.graphics.HardwareRenderer.FrameRenderRequest createRenderRequest();
+ method @NonNull public android.graphics.HardwareRenderer.FrameRenderRequest createRenderRequest();
method public void destroy();
method public boolean isOpaque();
method public void notifyFramePending();
method public void setContentRoot(@Nullable android.graphics.RenderNode);
method public void setLightSourceAlpha(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
method public void setLightSourceGeometry(float, float, float, float);
- method public void setName(String);
+ method public void setName(@NonNull String);
method public void setOpaque(boolean);
method public void setStopped(boolean);
method public void setSurface(@Nullable android.view.Surface);
@@ -14132,9 +14132,9 @@
}
public final class HardwareRenderer.FrameRenderRequest {
- method public android.graphics.HardwareRenderer.FrameRenderRequest setFrameCommitCallback(@NonNull java.util.concurrent.Executor, @NonNull Runnable);
- method public android.graphics.HardwareRenderer.FrameRenderRequest setVsyncTime(long);
- method public android.graphics.HardwareRenderer.FrameRenderRequest setWaitForPresent(boolean);
+ method @NonNull public android.graphics.HardwareRenderer.FrameRenderRequest setFrameCommitCallback(@NonNull java.util.concurrent.Executor, @NonNull Runnable);
+ method @NonNull public android.graphics.HardwareRenderer.FrameRenderRequest setVsyncTime(long);
+ method @NonNull public android.graphics.HardwareRenderer.FrameRenderRequest setWaitForPresent(boolean);
method public int syncAndDraw();
}
@@ -14156,6 +14156,7 @@
method @Nullable public android.graphics.ImageDecoder.OnPartialImageListener getOnPartialImageListener();
method @Nullable public android.graphics.PostProcessor getPostProcessor();
method public boolean isDecodeAsAlphaMaskEnabled();
+ method public static boolean isMimeTypeSupported(@NonNull String);
method public boolean isMutableRequired();
method public boolean isUnpremultipliedRequired();
method public void setAllocator(int);
@@ -14952,8 +14953,8 @@
public final class RenderNode {
ctor public RenderNode(@Nullable String);
- method public android.graphics.RecordingCanvas beginRecording(int, int);
- method public android.graphics.RecordingCanvas beginRecording();
+ method @NonNull public android.graphics.RecordingCanvas beginRecording(int, int);
+ method @NonNull public android.graphics.RecordingCanvas beginRecording();
method public long computeApproximateMemoryUsage();
method public void discardDisplayList();
method public void endRecording();
@@ -15006,7 +15007,7 @@
method public boolean setPivotX(float);
method public boolean setPivotY(float);
method public boolean setPosition(int, int, int, int);
- method public boolean setPosition(android.graphics.Rect);
+ method public boolean setPosition(@NonNull android.graphics.Rect);
method public boolean setProjectBackwards(boolean);
method public boolean setProjectionReceiver(boolean);
method public boolean setRotationX(float);
@@ -15445,38 +15446,38 @@
method public float getGradientCenterY();
method public float getGradientRadius();
method public int getGradientType();
- method public int getInnerRadius();
+ method @Px public int getInnerRadius();
method public float getInnerRadiusRatio();
method public int getOpacity();
method public android.graphics.drawable.GradientDrawable.Orientation getOrientation();
method public int getShape();
- method public int getThickness();
+ method @Px public int getThickness();
method public float getThicknessRatio();
method public boolean getUseLevel();
method public void setAlpha(int);
method public void setColor(@ColorInt int);
method public void setColor(@Nullable android.content.res.ColorStateList);
method public void setColorFilter(@Nullable android.graphics.ColorFilter);
- method public void setColors(@ColorInt int[]);
- method public void setColors(@ColorInt int[], @Nullable float[]);
+ method public void setColors(@Nullable @ColorInt int[]);
+ method public void setColors(@Nullable @ColorInt int[], @Nullable float[]);
method public void setCornerRadii(@Nullable float[]);
method public void setCornerRadius(float);
method public void setDither(boolean);
method public void setGradientCenter(float, float);
method public void setGradientRadius(float);
method public void setGradientType(int);
- method public void setInnerRadius(int);
- method public void setInnerRadiusRatio(float);
+ method public void setInnerRadius(@Px int);
+ method public void setInnerRadiusRatio(@FloatRange(from=0.0f, fromInclusive=false) float);
method public void setOrientation(android.graphics.drawable.GradientDrawable.Orientation);
- method public void setPadding(int, int, int, int);
+ method public void setPadding(@Px int, @Px int, @Px int, @Px int);
method public void setShape(int);
method public void setSize(int, int);
method public void setStroke(int, @ColorInt int);
method public void setStroke(int, android.content.res.ColorStateList);
method public void setStroke(int, @ColorInt int, float, float);
method public void setStroke(int, android.content.res.ColorStateList, float, float);
- method public void setThickness(int);
- method public void setThicknessRatio(float);
+ method public void setThickness(@Px int);
+ method public void setThicknessRatio(@FloatRange(from=0.0f, fromInclusive=false) float);
method public void setUseLevel(boolean);
field public static final int LINE = 2; // 0x2
field public static final int LINEAR_GRADIENT = 0; // 0x0
@@ -15689,10 +15690,10 @@
public class StateListDrawable extends android.graphics.drawable.DrawableContainer {
ctor public StateListDrawable();
method public void addState(int[], android.graphics.drawable.Drawable);
- method public int findStateDrawableIndex(int[]);
+ method public int findStateDrawableIndex(@NonNull int[]);
method public int getStateCount();
- method public android.graphics.drawable.Drawable getStateDrawable(int);
- method public int[] getStateSet(int);
+ method @Nullable public android.graphics.drawable.Drawable getStateDrawable(int);
+ method @NonNull public int[] getStateSet(int);
}
public class TransitionDrawable extends android.graphics.drawable.LayerDrawable implements android.graphics.drawable.Drawable.Callback {
@@ -23055,7 +23056,7 @@
ctor public AudioAttributes.Builder();
ctor public AudioAttributes.Builder(android.media.AudioAttributes);
method public android.media.AudioAttributes build();
- method public android.media.AudioAttributes.Builder setAllowCapture(boolean);
+ method @NonNull public android.media.AudioAttributes.Builder setAllowCapture(boolean);
method public android.media.AudioAttributes.Builder setContentType(int);
method public android.media.AudioAttributes.Builder setFlags(int);
method public android.media.AudioAttributes.Builder setLegacyStreamType(int);
@@ -23394,11 +23395,11 @@
public static final class AudioPlaybackCaptureConfiguration.Builder {
ctor public AudioPlaybackCaptureConfiguration.Builder(@NonNull android.media.projection.MediaProjection);
- method public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUid(int);
- method public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUsage(@NonNull android.media.AudioAttributes);
- method public android.media.AudioPlaybackCaptureConfiguration build();
- method public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUid(int);
- method public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUsage(@NonNull android.media.AudioAttributes);
+ method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUid(int);
+ method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUsage(@NonNull android.media.AudioAttributes);
+ method @NonNull public android.media.AudioPlaybackCaptureConfiguration build();
+ method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUid(int);
+ method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUsage(@NonNull android.media.AudioAttributes);
}
public final class AudioPlaybackConfiguration implements android.os.Parcelable {
@@ -23499,7 +23500,7 @@
ctor public AudioRecord.Builder();
method public android.media.AudioRecord build() throws java.lang.UnsupportedOperationException;
method public android.media.AudioRecord.Builder setAudioFormat(@NonNull android.media.AudioFormat) throws java.lang.IllegalArgumentException;
- method public android.media.AudioRecord.Builder setAudioPlaybackCaptureConfig(@NonNull android.media.AudioPlaybackCaptureConfiguration);
+ method @NonNull public android.media.AudioRecord.Builder setAudioPlaybackCaptureConfig(@NonNull android.media.AudioPlaybackCaptureConfiguration);
method public android.media.AudioRecord.Builder setAudioSource(int) throws java.lang.IllegalArgumentException;
method public android.media.AudioRecord.Builder setBufferSizeInBytes(int) throws java.lang.IllegalArgumentException;
}
@@ -25139,7 +25140,7 @@
field public static final long POSITION_UNKNOWN = 576460752303423487L; // 0x7ffffffffffffffL
}
- public static class MediaItem2.Builder {
+ public static final class MediaItem2.Builder {
ctor public MediaItem2.Builder();
method @NonNull public android.media.MediaItem2 build();
method @NonNull public android.media.MediaItem2.Builder setEndPosition(long);
@@ -38419,7 +38420,8 @@
method @Nullable public static android.net.Uri getDocumentUri(@NonNull android.content.Context, @NonNull android.net.Uri);
method public static android.net.Uri getMediaScannerUri();
method @Nullable public static android.net.Uri getMediaUri(@NonNull android.content.Context, @NonNull android.net.Uri);
- method public static String getVersion(android.content.Context);
+ method @NonNull public static String getVersion(@NonNull android.content.Context);
+ method @NonNull public static String getVersion(@NonNull android.content.Context, @NonNull String);
method @NonNull public static String getVolumeName(@NonNull android.net.Uri);
method @NonNull public static android.net.Uri setIncludePending(@NonNull android.net.Uri);
method @NonNull public static android.net.Uri setRequireOriginal(@NonNull android.net.Uri);
@@ -41870,26 +41872,26 @@
public static class ZenPolicy.Builder {
ctor public ZenPolicy.Builder();
- method public android.service.notification.ZenPolicy.Builder allowAlarms(boolean);
- method public android.service.notification.ZenPolicy.Builder allowAllSounds();
- method public android.service.notification.ZenPolicy.Builder allowCalls(int);
- method public android.service.notification.ZenPolicy.Builder allowEvents(boolean);
- method public android.service.notification.ZenPolicy.Builder allowMedia(boolean);
- method public android.service.notification.ZenPolicy.Builder allowMessages(int);
- method public android.service.notification.ZenPolicy.Builder allowReminders(boolean);
- method public android.service.notification.ZenPolicy.Builder allowRepeatCallers(boolean);
- method public android.service.notification.ZenPolicy.Builder allowSystem(boolean);
- method public android.service.notification.ZenPolicy build();
- method public android.service.notification.ZenPolicy.Builder disallowAllSounds();
- method public android.service.notification.ZenPolicy.Builder hideAllVisualEffects();
- method public android.service.notification.ZenPolicy.Builder showAllVisualEffects();
- method public android.service.notification.ZenPolicy.Builder showBadges(boolean);
- method public android.service.notification.ZenPolicy.Builder showFullScreenIntent(boolean);
- method public android.service.notification.ZenPolicy.Builder showInAmbientDisplay(boolean);
- method public android.service.notification.ZenPolicy.Builder showInNotificationList(boolean);
- method public android.service.notification.ZenPolicy.Builder showLights(boolean);
- method public android.service.notification.ZenPolicy.Builder showPeeking(boolean);
- method public android.service.notification.ZenPolicy.Builder showStatusBarIcons(boolean);
+ method @NonNull public android.service.notification.ZenPolicy.Builder allowAlarms(boolean);
+ method @NonNull public android.service.notification.ZenPolicy.Builder allowAllSounds();
+ method @NonNull public android.service.notification.ZenPolicy.Builder allowCalls(int);
+ method @NonNull public android.service.notification.ZenPolicy.Builder allowEvents(boolean);
+ method @NonNull public android.service.notification.ZenPolicy.Builder allowMedia(boolean);
+ method @NonNull public android.service.notification.ZenPolicy.Builder allowMessages(int);
+ method @NonNull public android.service.notification.ZenPolicy.Builder allowReminders(boolean);
+ method @NonNull public android.service.notification.ZenPolicy.Builder allowRepeatCallers(boolean);
+ method @NonNull public android.service.notification.ZenPolicy.Builder allowSystem(boolean);
+ method @NonNull public android.service.notification.ZenPolicy build();
+ method @NonNull public android.service.notification.ZenPolicy.Builder disallowAllSounds();
+ method @NonNull public android.service.notification.ZenPolicy.Builder hideAllVisualEffects();
+ method @NonNull public android.service.notification.ZenPolicy.Builder showAllVisualEffects();
+ method @NonNull public android.service.notification.ZenPolicy.Builder showBadges(boolean);
+ method @NonNull public android.service.notification.ZenPolicy.Builder showFullScreenIntent(boolean);
+ method @NonNull public android.service.notification.ZenPolicy.Builder showInAmbientDisplay(boolean);
+ method @NonNull public android.service.notification.ZenPolicy.Builder showInNotificationList(boolean);
+ method @NonNull public android.service.notification.ZenPolicy.Builder showLights(boolean);
+ method @NonNull public android.service.notification.ZenPolicy.Builder showPeeking(boolean);
+ method @NonNull public android.service.notification.ZenPolicy.Builder showStatusBarIcons(boolean);
}
}
@@ -45145,8 +45147,8 @@
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getImei();
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getImei(int);
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}) public String getLine1Number();
- method public String getManufacturerCode();
- method public String getManufacturerCode(int);
+ method @Nullable public String getManufacturerCode();
+ method @Nullable public String getManufacturerCode(int);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getMeid();
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getMeid(int);
method public String getMmsUAProfUrl();
@@ -45173,8 +45175,8 @@
method public int getSimState();
method public int getSimState(int);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getSubscriberId();
- method public String getTypeAllocationCode();
- method public String getTypeAllocationCode(int);
+ method @Nullable public String getTypeAllocationCode();
+ method @Nullable public String getTypeAllocationCode(int);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @NonNull public java.util.List<android.telephony.UiccCardInfo> getUiccCardsInfo();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVisualVoicemailPackageName();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailAlphaTag();
@@ -47113,7 +47115,7 @@
public static class LineHeightSpan.Standard implements android.text.style.LineHeightSpan android.text.ParcelableSpan {
ctor public LineHeightSpan.Standard(@Px @IntRange(from=1) int);
- ctor public LineHeightSpan.Standard(android.os.Parcel);
+ ctor public LineHeightSpan.Standard(@NonNull android.os.Parcel);
method public void chooseHeight(@NonNull CharSequence, int, int, int, int, @NonNull android.graphics.Paint.FontMetricsInt);
method public int describeContents();
method @Px public int getHeight();
@@ -51973,6 +51975,7 @@
method public int getStableInsetRight();
method public int getStableInsetTop();
method @NonNull public android.graphics.Insets getStableInsets();
+ method @NonNull public android.graphics.Insets getSystemGestureInsets();
method public int getSystemWindowInsetBottom();
method public int getSystemWindowInsetLeft();
method public int getSystemWindowInsetRight();
@@ -51994,6 +51997,7 @@
method @NonNull public android.view.WindowInsets build();
method @NonNull public android.view.WindowInsets.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
method @NonNull public android.view.WindowInsets.Builder setStableInsets(@NonNull android.graphics.Insets);
+ method @NonNull public android.view.WindowInsets.Builder setSystemGestureInsets(@NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setSystemWindowInsets(@NonNull android.graphics.Insets);
}
@@ -53037,7 +53041,7 @@
public final class ContentCaptureContext implements android.os.Parcelable {
method public int describeContents();
- method @NonNull public static android.view.contentcapture.ContentCaptureContext forLocusId(@NonNull android.net.Uri);
+ method @NonNull public static android.view.contentcapture.ContentCaptureContext forLocusId(@NonNull String);
method @Nullable public android.os.Bundle getExtras();
method @NonNull public android.content.LocusId getLocusId();
method public void writeToParcel(android.os.Parcel, int);
@@ -53085,18 +53089,19 @@
method public boolean isForEverything();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.contentcapture.UserDataRemovalRequest> CREATOR;
+ field public static final int FLAG_IS_PREFIX = 1; // 0x1
}
public static final class UserDataRemovalRequest.Builder {
ctor public UserDataRemovalRequest.Builder();
- method @NonNull public android.view.contentcapture.UserDataRemovalRequest.Builder addLocusId(@NonNull android.content.LocusId, boolean);
+ method @NonNull public android.view.contentcapture.UserDataRemovalRequest.Builder addLocusId(@NonNull android.content.LocusId, int);
method @NonNull public android.view.contentcapture.UserDataRemovalRequest build();
method @NonNull public android.view.contentcapture.UserDataRemovalRequest.Builder forEverything();
}
public final class UserDataRemovalRequest.LocusIdRequest {
+ method @NonNull public int getFlags();
method @NonNull public android.content.LocusId getLocusId();
- method @NonNull public boolean isRecursive();
}
}
@@ -56786,7 +56791,7 @@
method public boolean isFillViewport();
method public boolean isSmoothScrollingEnabled();
method public boolean pageScroll(int);
- method public void scrollToDescendant(android.view.View);
+ method public void scrollToDescendant(@NonNull android.view.View);
method public void setBottomEdgeEffectColor(@ColorInt int);
method public void setEdgeEffectColor(@ColorInt int);
method public void setFillViewport(boolean);
@@ -57332,7 +57337,7 @@
method @NonNull public android.view.textclassifier.TextClassifier getTextClassifier();
method public final android.content.res.ColorStateList getTextColors();
method @Nullable public android.graphics.drawable.Drawable getTextCursorDrawable();
- method public android.text.TextDirectionHeuristic getTextDirectionHeuristic();
+ method @NonNull public android.text.TextDirectionHeuristic getTextDirectionHeuristic();
method @NonNull public java.util.Locale getTextLocale();
method @NonNull @Size(min=1) public android.os.LocaleList getTextLocales();
method @NonNull public android.text.PrecomputedText.Params getTextMetricsParams();
diff --git a/api/system-current.txt b/api/system-current.txt
index d86ad4a..7518bb7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -25,6 +25,7 @@
field public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE";
field @Deprecated public static final String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE";
field public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE";
+ field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE";
field public static final String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
field public static final String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE";
field public static final String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE";
@@ -163,6 +164,7 @@
field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
field public static final String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS";
field public static final String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT";
+ field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES";
field public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS";
field public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
field public static final String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY";
@@ -1413,7 +1415,7 @@
field public static final String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED";
field public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
field public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
- field public static final String ACTION_REVIEW_ACCESSIBILITY_SERVICES = "android.intent.action.REVIEW_ACCESSIBILITY_SERVICES";
+ field @RequiresPermission(android.Manifest.permission.REVIEW_ACCESSIBILITY_SERVICES) public static final String ACTION_REVIEW_ACCESSIBILITY_SERVICES = "android.intent.action.REVIEW_ACCESSIBILITY_SERVICES";
field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_APP_PERMISSION_USAGE = "android.intent.action.REVIEW_APP_PERMISSION_USAGE";
field public static final String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS";
field public static final String ACTION_REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE";
@@ -6763,15 +6765,15 @@
method public static android.view.textclassifier.TextClassifier getDefaultTextClassifierImplementation(@NonNull android.content.Context);
method @Deprecated public final android.view.textclassifier.TextClassifier getLocalTextClassifier();
method @Nullable public final android.os.IBinder onBind(android.content.Intent);
- method public abstract void onClassifyText(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextClassification.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextClassification>);
- method public void onCreateTextClassificationSession(@NonNull android.view.textclassifier.TextClassificationContext, @NonNull android.view.textclassifier.TextClassificationSessionId);
- method public void onDestroyTextClassificationSession(@NonNull android.view.textclassifier.TextClassificationSessionId);
- method public void onDetectLanguage(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextLanguage.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLanguage>);
- method public abstract void onGenerateLinks(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextLinks.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLinks>);
- method @Deprecated public void onSelectionEvent(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.SelectionEvent);
- method public void onSuggestConversationActions(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.ConversationActions.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.ConversationActions>);
- method public abstract void onSuggestSelection(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextSelection.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextSelection>);
- method public void onTextClassifierEvent(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextClassifierEvent);
+ method @MainThread public abstract void onClassifyText(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextClassification.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextClassification>);
+ method @MainThread public void onCreateTextClassificationSession(@NonNull android.view.textclassifier.TextClassificationContext, @NonNull android.view.textclassifier.TextClassificationSessionId);
+ method @MainThread public void onDestroyTextClassificationSession(@NonNull android.view.textclassifier.TextClassificationSessionId);
+ method @MainThread public void onDetectLanguage(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextLanguage.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLanguage>);
+ method @MainThread public abstract void onGenerateLinks(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextLinks.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLinks>);
+ method @Deprecated @MainThread public void onSelectionEvent(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.SelectionEvent);
+ method @MainThread public void onSuggestConversationActions(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.ConversationActions.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.ConversationActions>);
+ method @MainThread public abstract void onSuggestSelection(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextSelection.Request, @NonNull android.os.CancellationSignal, @NonNull android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextSelection>);
+ method @MainThread public void onTextClassifierEvent(@Nullable android.view.textclassifier.TextClassificationSessionId, @NonNull android.view.textclassifier.TextClassifierEvent);
field public static final String SERVICE_INTERFACE = "android.service.textclassifier.TextClassifierService";
}
@@ -7951,7 +7953,7 @@
method @Deprecated public boolean getDataEnabled(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.util.Pair<java.lang.Integer,java.lang.Integer>> getLogicalToPhysicalSlotMapping();
method public static long getMaxNumberVerificationTimeoutMillis();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getPreferredNetworkTypeBitmap();
diff --git a/api/test-current.txt b/api/test-current.txt
index 684524e..c070171 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -523,6 +523,7 @@
method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void removeRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.role.RoleManagerCallback);
method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean removeRoleHolderFromController(@NonNull String, @NonNull String);
method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public void setRoleNamesFromController(@NonNull java.util.List<java.lang.String>);
+ field public static final int MANAGE_HOLDERS_FLAG_DONT_KILL_APP = 1; // 0x1
}
public interface RoleManagerCallback {
diff --git a/cmds/incident_helper/src/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp
index 012310c..77a56e5 100644
--- a/cmds/incident_helper/src/ih_util.cpp
+++ b/cmds/incident_helper/src/ih_util.cpp
@@ -142,7 +142,7 @@
}
if (lineSize - lastIndex > 0) {
int beginning = lastIndex;
- if (record.size() == indices.size()) {
+ if (record.size() == indices.size() && !record.empty()) {
// We've already encountered all of the columns...put whatever is
// left in the last column.
record.pop_back();
diff --git a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
index 21ced9c..5d525e6 100644
--- a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
+++ b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
@@ -65,8 +65,9 @@
if (line.empty()) continue;
nline++;
-
- if (stripPrefix(&line, "Tasks:")) {
+ // The format changes from time to time in toybox/toys/posix/ps.c
+ // With -H, it prints Threads instead of Tasks (FLAG(H)?"Thread":"Task")
+ if (stripPrefix(&line, "Threads:")) {
writeSuffixLine(&proto, CpuInfoProto::TASK_STATS, line, COMMA_DELIMITER,
CpuInfoProto::TaskStats::_FIELD_COUNT,
CpuInfoProto::TaskStats::_FIELD_NAMES,
diff --git a/cmds/incident_helper/testdata/cpuinfo.txt b/cmds/incident_helper/testdata/cpuinfo.txt
index ec4a839..aa3afc3 100644
--- a/cmds/incident_helper/testdata/cpuinfo.txt
+++ b/cmds/incident_helper/testdata/cpuinfo.txt
@@ -1,8 +1,8 @@
-Tasks: 2038 total, 1 running,2033 sleeping, 0 stopped, 0 zombie
+Threads: 2038 total, 1 running,2033 sleeping, 0 stopped, 0 zombie
-Mem: 3842668k total, 3761936k used, 80732k free, 220188k buffers
+ Mem: 3842668k total, 3761936k used, 80732k free, 220188k buffers
-Swap: 524284k total, 25892k used, 498392k free, 1316952k cached
+ Swap: 524284k total, 25892k used, 498392k free, 1316952k cached
400%cpu 17%user 0%nice 43%sys 338%idle 0%iow 0%irq 1%sirq 0%host
@@ -12,4 +12,4 @@
29438 29438 rootabcdefghij 20 0 57.9 R 14M 3.8M top test top
916 916 system 18 -2 1.4 S 4.6G 404M fg system_server system_server
28 28 root -2 0 1.4 S 0 0 bg rcuc/3 [rcuc/3]
- 27 27 root RT 0 1.4 S 0 0 ta migration/3 [migration/3]
\ No newline at end of file
+ 27 27 root RT 0 1.4 S 0 0 ta migration/3 [migration/3]
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index da72002..7298da6 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -219,6 +219,7 @@
"tests/anomaly/AnomalyTracker_test.cpp",
"tests/ConfigManager_test.cpp",
"tests/external/puller_util_test.cpp",
+ "tests/external/IncidentReportArgs_test.cpp",
"tests/external/StatsPuller_test.cpp",
"tests/indexed_priority_queue_test.cpp",
"tests/LogEntryMatcher_test.cpp",
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index a983b27..5968fa8 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -1568,7 +1568,7 @@
ROLLBACK_INITIATE = 1;
ROLLBACK_SUCCESS = 2;
ROLLBACK_FAILURE = 3;
- ROLLBACK_ROOT_TRIGGERED = 4;
+ ROLLBACK_BOOT_TRIGGERED = 4;
}
optional RollbackType rollback_type = 1;
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 5c6d548..0e91f52 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -324,6 +324,12 @@
EXPLICIT = 1;
}
optional Destination dest = 2;
+
+ // Package name of the incident report receiver.
+ optional string receiver_pkg = 3;
+
+ // Class name of the incident report receiver.
+ optional string receiver_cls = 4;
}
message PerfettoDetails {
diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.cpp b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
index 0ed2d75..7c2d242 100644
--- a/cmds/statsd/src/subscriber/IncidentdReporter.cpp
+++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
@@ -162,6 +162,10 @@
}
incidentReport.setDest(dest);
+ incidentReport.setReceiverPkg(config.receiver_pkg());
+
+ incidentReport.setReceiverCls(config.receiver_cls());
+
sp<IIncidentManager> service = interface_cast<IIncidentManager>(
defaultServiceManager()->getService(android::String16("incident")));
if (service == nullptr) {
diff --git a/cmds/statsd/tests/external/IncidentReportArgs_test.cpp b/cmds/statsd/tests/external/IncidentReportArgs_test.cpp
new file mode 100644
index 0000000..c170b12
--- /dev/null
+++ b/cmds/statsd/tests/external/IncidentReportArgs_test.cpp
@@ -0,0 +1,72 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <android/os/IncidentReportArgs.h>
+
+#include <gtest/gtest.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(IncidentReportArgsTest, testSerialization) {
+ IncidentReportArgs args;
+ args.setAll(0);
+ args.addSection(1000);
+ args.addSection(1001);
+
+ vector<uint8_t> header1;
+ header1.push_back(0x1);
+ header1.push_back(0x2);
+ vector<uint8_t> header2;
+ header1.push_back(0x22);
+ header1.push_back(0x33);
+
+ args.addHeader(header1);
+ args.addHeader(header2);
+
+ args.setDest(1);
+
+ args.setReceiverPkg("com.android.os");
+ args.setReceiverCls("com.android.os.Receiver");
+
+ Parcel out;
+ status_t err = args.writeToParcel(&out);
+ EXPECT_EQ(NO_ERROR, err);
+
+ out.setDataPosition(0);
+
+ IncidentReportArgs args2;
+ err = args2.readFromParcel(&out);
+ EXPECT_EQ(NO_ERROR, err);
+
+ EXPECT_EQ(0, args2.all());
+ set<int> sections;
+ sections.insert(1000);
+ sections.insert(1001);
+ EXPECT_EQ(sections, args2.sections());
+ EXPECT_EQ(1, args2.dest());
+
+ EXPECT_EQ(String16("com.android.os"), args2.receiverPkg());
+ EXPECT_EQ(String16("com.android.os.Receiver"), args2.receiverCls());
+
+ vector<vector<uint8_t>> headers;
+ headers.push_back(header1);
+ headers.push_back(header2);
+ EXPECT_EQ(headers, args2.headers());
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index 46a956c..9b0e657 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -2935,7 +2935,6 @@
Lcom/android/internal/telephony/ServiceStateTracker;->isInvalidOperatorNumeric(Ljava/lang/String;)Z
Lcom/android/internal/telephony/ServiceStateTracker;->log(Ljava/lang/String;)V
Lcom/android/internal/telephony/ServiceStateTracker;->loge(Ljava/lang/String;)V
-Lcom/android/internal/telephony/ServiceStateTracker;->mAttachedRegistrants:Landroid/os/RegistrantList;
Lcom/android/internal/telephony/ServiceStateTracker;->mCi:Lcom/android/internal/telephony/CommandsInterface;
Lcom/android/internal/telephony/ServiceStateTracker;->mCr:Landroid/content/ContentResolver;
Lcom/android/internal/telephony/ServiceStateTracker;->mCurDataSpn:Ljava/lang/String;
@@ -2947,7 +2946,6 @@
Lcom/android/internal/telephony/ServiceStateTracker;->mDataRoamingOnRegistrants:Landroid/os/RegistrantList;
Lcom/android/internal/telephony/ServiceStateTracker;->mDefaultRoamingIndicator:I
Lcom/android/internal/telephony/ServiceStateTracker;->mDesiredPowerState:Z
-Lcom/android/internal/telephony/ServiceStateTracker;->mDetachedRegistrants:Landroid/os/RegistrantList;
Lcom/android/internal/telephony/ServiceStateTracker;->mDeviceShuttingDown:Z
Lcom/android/internal/telephony/ServiceStateTracker;->mEmergencyOnly:Z
Lcom/android/internal/telephony/ServiceStateTracker;->mIccRecords:Lcom/android/internal/telephony/uicc/IccRecords;
@@ -2975,7 +2973,6 @@
Lcom/android/internal/telephony/ServiceStateTracker;->mUiccController:Lcom/android/internal/telephony/uicc/UiccController;
Lcom/android/internal/telephony/ServiceStateTracker;->mVoiceRoamingOffRegistrants:Landroid/os/RegistrantList;
Lcom/android/internal/telephony/ServiceStateTracker;->mVoiceRoamingOnRegistrants:Landroid/os/RegistrantList;
-Lcom/android/internal/telephony/ServiceStateTracker;->notifyDataRegStateRilRadioTechnologyChanged()V
Lcom/android/internal/telephony/ServiceStateTracker;->notifySignalStrength()Z
Lcom/android/internal/telephony/ServiceStateTracker;->pollState()V
Lcom/android/internal/telephony/ServiceStateTracker;->reRegisterNetwork(Landroid/os/Message;)V
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 7d828d8..92db23b 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -276,6 +276,7 @@
public abstract boolean isActivityStartsLoggingEnabled();
/** Returns true if the background activity starts is enabled. */
public abstract boolean isBackgroundActivityStartsEnabled();
+ public abstract boolean isPackageNameWhitelistedForBgActivityStarts(String packageName);
public abstract void reportCurKeyguardUsageEvent(boolean keyguardShowing);
/** Input dispatch timeout to a window, start the ANR process. */
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b654258..08239a1 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -332,7 +332,6 @@
String[] mInstrumentedSplitAppDirs = null;
String mInstrumentedLibDir = null;
boolean mSystemThread = false;
- boolean mJitEnabled = false;
boolean mSomeActivitiesChanged = false;
boolean mUpdatingSystemConfig = false;
/* package */ boolean mHiddenApiWarningShown = false;
@@ -1696,7 +1695,6 @@
public static final int SUICIDE = 130;
@UnsupportedAppUsage
public static final int REMOVE_PROVIDER = 131;
- public static final int ENABLE_JIT = 132;
public static final int DISPATCH_PACKAGE_BROADCAST = 133;
@UnsupportedAppUsage
public static final int SCHEDULE_CRASH = 134;
@@ -1746,7 +1744,6 @@
case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT";
case SUICIDE: return "SUICIDE";
case REMOVE_PROVIDER: return "REMOVE_PROVIDER";
- case ENABLE_JIT: return "ENABLE_JIT";
case DISPATCH_PACKAGE_BROADCAST: return "DISPATCH_PACKAGE_BROADCAST";
case SCHEDULE_CRASH: return "SCHEDULE_CRASH";
case DUMP_HEAP: return "DUMP_HEAP";
@@ -1858,9 +1855,6 @@
completeRemoveProvider((ProviderRefCount)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
- case ENABLE_JIT:
- ensureJitEnabled();
- break;
case DISPATCH_PACKAGE_BROADCAST:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastPackage");
handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj);
@@ -1996,7 +1990,6 @@
if (stopProfiling) {
mProfiler.stopProfiling();
}
- ensureJitEnabled();
return false;
}
}
@@ -2330,13 +2323,6 @@
}
}
- void ensureJitEnabled() {
- if (!mJitEnabled) {
- mJitEnabled = true;
- dalvik.system.VMRuntime.getRuntime().startJitCompilation();
- }
- }
-
@UnsupportedAppUsage
void scheduleGcIdler() {
if (!mGcIdlerScheduled) {
@@ -3782,7 +3768,6 @@
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
}
- ensureJitEnabled();
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -3896,7 +3881,6 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- ensureJitEnabled();
} catch (Exception e) {
if (!mInstrumentation.onException(s, e)) {
throw new RuntimeException(
@@ -6177,9 +6161,6 @@
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
installContentProviders(app, data.providers);
- // For process that contains content providers, we want to
- // ensure that the JIT is enabled "at some point".
- mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
}
}
@@ -6812,12 +6793,6 @@
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
- ViewRootImpl.addFirstDrawHandler(new Runnable() {
- @Override
- public void run() {
- ensureJitEnabled();
- }
- });
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
RuntimeInit.setApplicationObject(mAppThread.asBinder());
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 4a826d1..010a900 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -16,18 +16,15 @@
package android.app;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.NotificationManager.InterruptionFilter;
import android.content.ComponentName;
-import android.content.Intent;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
-import android.service.notification.ZenPolicy;
import android.service.notification.Condition;
-
-import com.android.internal.util.Preconditions;
+import android.service.notification.ZenPolicy;
import java.util.Objects;
@@ -92,8 +89,9 @@
* action ({@link Condition#STATE_TRUE}).
* @param enabled Whether the rule is enabled.
*/
- public AutomaticZenRule(String name, ComponentName owner, ComponentName configurationActivity,
- Uri conditionId, ZenPolicy policy, int interruptionFilter, boolean enabled) {
+ public AutomaticZenRule(@NonNull String name, @Nullable ComponentName owner,
+ @Nullable ComponentName configurationActivity, @NonNull Uri conditionId,
+ @Nullable ZenPolicy policy, int interruptionFilter, boolean enabled) {
this.name = name;
this.owner = owner;
this.configurationActivity = configurationActivity;
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index c665cb23..f91d878 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -187,6 +187,7 @@
* @hide
*/
@SystemApi
+ @TestApi
public static final int MANAGE_HOLDERS_FLAG_DONT_KILL_APP = 1;
/**
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index d3b8e29..0715572 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2124,6 +2124,7 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.REVIEW_ACCESSIBILITY_SERVICES)
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_REVIEW_ACCESSIBILITY_SERVICES =
"android.intent.action.REVIEW_ACCESSIBILITY_SERVICES";
diff --git a/core/java/android/content/LocusId.java b/core/java/android/content/LocusId.java
index 2142cf3..3d1ddc3 100644
--- a/core/java/android/content/LocusId.java
+++ b/core/java/android/content/LocusId.java
@@ -16,7 +16,6 @@
package android.content;
import android.annotation.NonNull;
-import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -34,28 +33,28 @@
// TODO(b/123577059): make sure this is well documented and understandable
public final class LocusId implements Parcelable {
- private final Uri mUri;
+ private final String mId;
/**
* Default constructor.
*/
- public LocusId(@NonNull Uri uri) {
- mUri = Preconditions.checkNotNull(uri);
+ public LocusId(@NonNull String id) {
+ mId = Preconditions.checkNotNull(id);
}
/**
- * Gets the {@code uri} associated with the locus.
+ * Gets the {@code id} associated with the locus.
*/
@NonNull
- public Uri getUri() {
- return mUri;
+ public String getId() {
+ return mId;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
- result = prime * result + ((mUri == null) ? 0 : mUri.hashCode());
+ result = prime * result + ((mId == null) ? 0 : mId.hashCode());
return result;
}
@@ -65,26 +64,27 @@
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
final LocusId other = (LocusId) obj;
- if (mUri == null) {
- if (other.mUri != null) return false;
+ if (mId == null) {
+ if (other.mId != null) return false;
} else {
- if (!mUri.equals(other.mUri)) return false;
+ if (!mId.equals(other.mId)) return false;
}
return true;
}
@Override
public String toString() {
- return "LocusId[uri=" + getSanitizedUri() + "]";
+ return "LocusId[" + getSanitizedId() + "]";
}
/** @hide */
public void dump(@NonNull PrintWriter pw) {
- pw.print("uri:"); pw.println(getSanitizedUri());
+ pw.print("id:"); pw.println(getSanitizedId());
}
- private String getSanitizedUri() {
- final int size = mUri.toString().length();
+ @NonNull
+ private String getSanitizedId() {
+ final int size = mId.length();
return size + "_chars";
}
@@ -94,8 +94,8 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(mUri, flags);
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mId);
}
public static final @android.annotation.NonNull Parcelable.Creator<LocusId> CREATOR =
@@ -103,9 +103,8 @@
@NonNull
@Override
- public LocusId createFromParcel(Parcel source) {
- final Uri uri = source.readParcelable(null);
- return new LocusId(uri);
+ public LocusId createFromParcel(Parcel parcel) {
+ return new LocusId(parcel.readString());
}
@NonNull
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 38ea43e..0cc5f39 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -792,7 +792,7 @@
*
* @return an {@link AppUsageLimit} object describing the app time limit containing
* the given package with the smallest time remaining, or {@code null} if none exist.
- * @throws SecurityException when the caller is not the active launcher.
+ * @throws SecurityException when the caller is not the recents app.
*/
@Nullable
public LauncherApps.AppUsageLimit getAppUsageLimit(@NonNull String packageName,
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 0304f19..b20cce9 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1343,6 +1343,7 @@
*/
public boolean areHiddenOptionsSet() {
return (installFlags & (PackageManager.INSTALL_ALLOW_DOWNGRADE
+ | PackageManager.INSTALL_RESPECT_ALLOW_DOWNGRADE
| PackageManager.INSTALL_DONT_KILL_APP
| PackageManager.INSTALL_INSTANT_APP
| PackageManager.INSTALL_FULL_APP
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a5464c2..c133fba 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -721,6 +721,7 @@
INSTALL_VIRTUAL_PRELOAD,
INSTALL_APEX,
INSTALL_ENABLE_ROLLBACK,
+ INSTALL_RESPECT_ALLOW_DOWNGRADE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface InstallFlags {}
@@ -865,6 +866,15 @@
*/
public static final int INSTALL_DISABLE_VERIFICATION = 0x00080000;
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that
+ * {@link #INSTALL_ALLOW_DOWNGRADE} should be respected.
+ *
+ * @hide
+ */
+ // TODO(b/127322579): rename
+ public static final int INSTALL_RESPECT_ALLOW_DOWNGRADE = 0x00100000;
+
/** @hide */
@IntDef(flag = true, prefix = { "DONT_KILL_APP" }, value = {
DONT_KILL_APP
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index a25bbdb..9da8e4e 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -85,7 +85,8 @@
*
* @hide
*/
- public Key(String name, String fallbackName, Class<T> type) {
+ @UnsupportedAppUsage
+ public Key(@NonNull String name, @NonNull String fallbackName, @NonNull Class<T> type) {
mKey = new CameraMetadataNative.Key<T>(name, fallbackName, type);
}
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 53d4dd3..bb0987d 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -89,7 +89,8 @@
*
* @hide
*/
- public Key(String name, String fallbackName, Class<T> type) {
+ @UnsupportedAppUsage
+ public Key(@NonNull String name, @NonNull String fallbackName, @NonNull Class<T> type) {
mKey = new CameraMetadataNative.Key<T>(name, fallbackName, type);
}
@@ -4251,6 +4252,7 @@
* @see CaptureResult#SENSOR_TIMESTAMP
* @hide
*/
+ @UnsupportedAppUsage
public static final Key<long[]> STATISTICS_OIS_TIMESTAMPS =
new Key<long[]>("android.statistics.oisTimestamps", long[].class);
@@ -4270,6 +4272,7 @@
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
* @hide
*/
+ @UnsupportedAppUsage
public static final Key<float[]> STATISTICS_OIS_X_SHIFTS =
new Key<float[]>("android.statistics.oisXShifts", float[].class);
@@ -4289,6 +4292,7 @@
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
* @hide
*/
+ @UnsupportedAppUsage
public static final Key<float[]> STATISTICS_OIS_Y_SHIFTS =
new Key<float[]>("android.statistics.oisYShifts", float[].class);
diff --git a/core/java/android/os/IncidentReportArgs.java b/core/java/android/os/IncidentReportArgs.java
index 1bdfd94..a1f2430 100644
--- a/core/java/android/os/IncidentReportArgs.java
+++ b/core/java/android/os/IncidentReportArgs.java
@@ -36,6 +36,8 @@
private final ArrayList<byte[]> mHeaders = new ArrayList<byte[]>();
private boolean mAll;
private int mPrivacyPolicy;
+ private String mReceiverPkg;
+ private String mReceiverCls;
/**
* Construct an incident report args with no fields.
@@ -73,6 +75,10 @@
}
out.writeInt(mPrivacyPolicy);
+
+ out.writeString(mReceiverPkg);
+
+ out.writeString(mReceiverCls);
}
public void readFromParcel(Parcel in) {
@@ -91,6 +97,10 @@
}
mPrivacyPolicy = in.readInt();
+
+ mReceiverPkg = in.readString();
+
+ mReceiverCls = in.readString();
}
public static final @android.annotation.NonNull Parcelable.Creator<IncidentReportArgs> CREATOR
@@ -126,6 +136,8 @@
sb.append(mHeaders.size());
sb.append(" headers), ");
sb.append("privacy: ").append(mPrivacyPolicy);
+ sb.append("receiver pkg: ").append(mReceiverPkg);
+ sb.append("receiver cls: ").append(mReceiverCls);
return sb.toString();
}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index a34ac70..917b5c2 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -146,6 +146,8 @@
public static final String RETRANSLATE_CALL = "update_titles";
/** {@hide} */
+ public static final String GET_VERSION_CALL = "get_version";
+ /** {@hide} */
public static final String GET_DOCUMENT_URI_CALL = "get_document_uri";
/** {@hide} */
public static final String GET_MEDIA_URI_CALL = "get_media_uri";
@@ -3318,21 +3320,41 @@
public static final String MEDIA_IGNORE_FILENAME = ".nomedia";
/**
- * Get the media provider's version.
- * Applications that import data from the media provider into their own caches
- * can use this to detect that the media provider changed, and reimport data
- * as needed. No other assumptions should be made about the meaning of the version.
- * @param context Context to use for performing the query.
- * @return A version string, or null if the version could not be determined.
+ * Return an opaque version string describing the {@link MediaStore} state.
+ * <p>
+ * Applications that import data from {@link MediaStore} into their own
+ * caches can use this to detect that {@link MediaStore} has undergone
+ * substantial changes, and that data should be rescanned.
+ * <p>
+ * No other assumptions should be made about the meaning of the version.
+ * <p>
+ * This method returns the version for {@link MediaStore#VOLUME_EXTERNAL};
+ * to obtain a version for a different volume, use
+ * {@link #getVersion(Context, String)}.
*/
- public static String getVersion(Context context) {
- final Uri uri = AUTHORITY_URI.buildUpon().appendPath("none").appendPath("version").build();
- try (Cursor c = context.getContentResolver().query(uri, null, null, null, null)) {
- if (c.moveToFirst()) {
- return c.getString(0);
- }
+ public static @NonNull String getVersion(@NonNull Context context) {
+ return getVersion(context, VOLUME_EXTERNAL);
+ }
+
+ /**
+ * Return an opaque version string describing the {@link MediaStore} state.
+ * <p>
+ * Applications that import data from {@link MediaStore} into their own
+ * caches can use this to detect that {@link MediaStore} has undergone
+ * substantial changes, and that data should be rescanned.
+ * <p>
+ * No other assumptions should be made about the meaning of the version.
+ */
+ public static @NonNull String getVersion(@NonNull Context context, @NonNull String volumeName) {
+ final ContentResolver resolver = context.getContentResolver();
+ try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
+ final Bundle in = new Bundle();
+ in.putString(Intent.EXTRA_TEXT, volumeName);
+ final Bundle out = client.call(GET_VERSION_CALL, null, in);
+ return out.getString(Intent.EXTRA_TEXT);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
- return null;
}
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 45219d6..027525b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11453,6 +11453,15 @@
"background_activity_starts_enabled";
/**
+ * The packages temporarily whitelisted to be able so start activities from background.
+ * The list of packages is {@code ":"} colon delimited.
+ *
+ * @hide
+ */
+ public static final String BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST =
+ "background_activity_starts_package_names_whitelist";
+
+ /**
* @hide
* @see com.android.server.appbinding.AppBindingConstants
*/
diff --git a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
index 40333bf..2814300 100644
--- a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
+++ b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java
@@ -52,6 +52,10 @@
/**
* The action for the intent used to define the content suggestions service.
+ *
+ * <p>To be supported, the service must also require the
+ * * {@link android.Manifest.permission#BIND_CONTENT_SUGGESTIONS_SERVICE} permission so
+ * * that other applications can not abuse it.
*/
public static final String SERVICE_INTERFACE =
"android.service.contentsuggestions.ContentSuggestionsService";
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 74e6c6e..7ef40cd 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -17,6 +17,7 @@
package android.service.notification;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationChannel;
import android.os.Parcel;
@@ -378,14 +379,14 @@
/**
* Builds the current ZenPolicy.
*/
- public ZenPolicy build() {
+ public @NonNull ZenPolicy build() {
return mZenPolicy.copy();
}
/**
* Allows all notifications to bypass DND and unmutes all streams.
*/
- public Builder allowAllSounds() {
+ public @NonNull Builder allowAllSounds() {
for (int i = 0; i < mZenPolicy.mPriorityCategories.size(); i++) {
mZenPolicy.mPriorityCategories.set(i, STATE_ALLOW);
}
@@ -401,7 +402,7 @@
* {@link NotificationChannel#canBypassDnd can bypass DND}. If no channels can bypass DND,
* the ringer stream is also muted.
*/
- public Builder disallowAllSounds() {
+ public @NonNull Builder disallowAllSounds() {
for (int i = 0; i < mZenPolicy.mPriorityCategories.size(); i++) {
mZenPolicy.mPriorityCategories.set(i, STATE_DISALLOW);
}
@@ -413,7 +414,7 @@
/**
* Allows notifications intercepted by DND to show on all surfaces when DND is active.
*/
- public Builder showAllVisualEffects() {
+ public @NonNull Builder showAllVisualEffects() {
for (int i = 0; i < mZenPolicy.mVisualEffects.size(); i++) {
mZenPolicy.mVisualEffects.set(i, STATE_ALLOW);
}
@@ -423,7 +424,7 @@
/**
* Disallows notifications intercepted by DND from showing when DND is active.
*/
- public Builder hideAllVisualEffects() {
+ public @NonNull Builder hideAllVisualEffects() {
for (int i = 0; i < mZenPolicy.mVisualEffects.size(); i++) {
mZenPolicy.mVisualEffects.set(i, STATE_DISALLOW);
}
@@ -435,7 +436,7 @@
* unset categories will default to the current applied policy.
* @hide
*/
- public Builder unsetPriorityCategory(@PriorityCategory int category) {
+ public @NonNull Builder unsetPriorityCategory(@PriorityCategory int category) {
mZenPolicy.mPriorityCategories.set(category, STATE_UNSET);
if (category == PRIORITY_CATEGORY_MESSAGES) {
@@ -452,7 +453,7 @@
* unset effects will default to the current applied policy.
* @hide
*/
- public Builder unsetVisualEffect(@VisualEffect int effect) {
+ public @NonNull Builder unsetVisualEffect(@VisualEffect int effect) {
mZenPolicy.mVisualEffects.set(effect, STATE_UNSET);
return this;
}
@@ -461,7 +462,7 @@
* Whether to allow notifications with category {@link Notification#CATEGORY_REMINDER}
* to play sounds and visually appear or to intercept them when DND is active.
*/
- public Builder allowReminders(boolean allow) {
+ public @NonNull Builder allowReminders(boolean allow) {
mZenPolicy.mPriorityCategories.set(PRIORITY_CATEGORY_REMINDERS,
allow ? STATE_ALLOW : STATE_DISALLOW);
return this;
@@ -471,7 +472,7 @@
* Whether to allow notifications with category {@link Notification#CATEGORY_EVENT}
* to play sounds and visually appear or to intercept them when DND is active.
*/
- public Builder allowEvents(boolean allow) {
+ public @NonNull Builder allowEvents(boolean allow) {
mZenPolicy.mPriorityCategories.set(PRIORITY_CATEGORY_EVENTS,
allow ? STATE_ALLOW : STATE_DISALLOW);
return this;
@@ -483,7 +484,7 @@
* them when DND is active.
* @param audienceType message senders that are allowed to bypass DND
*/
- public Builder allowMessages(@PeopleType int audienceType) {
+ public @NonNull Builder allowMessages(@PeopleType int audienceType) {
if (audienceType == STATE_UNSET) {
return unsetPriorityCategory(PRIORITY_CATEGORY_MESSAGES);
}
@@ -507,7 +508,7 @@
* them when DND is active.
* @param audienceType callers that are allowed to bypass DND
*/
- public Builder allowCalls(@PeopleType int audienceType) {
+ public @NonNull Builder allowCalls(@PeopleType int audienceType) {
if (audienceType == STATE_UNSET) {
return unsetPriorityCategory(PRIORITY_CATEGORY_CALLS);
}
@@ -530,7 +531,7 @@
* {@link Notification#CATEGORY_CALL} that have recently called
* to play sounds and visually appear.
*/
- public Builder allowRepeatCallers(boolean allow) {
+ public @NonNull Builder allowRepeatCallers(boolean allow) {
mZenPolicy.mPriorityCategories.set(PRIORITY_CATEGORY_REPEAT_CALLERS,
allow ? STATE_ALLOW : STATE_DISALLOW);
return this;
@@ -542,7 +543,7 @@
* to play sounds and visually appear or to intercept them when DND is active.
* Disallowing alarms will mute the alarm stream when DND is active.
*/
- public Builder allowAlarms(boolean allow) {
+ public @NonNull Builder allowAlarms(boolean allow) {
mZenPolicy.mPriorityCategories.set(PRIORITY_CATEGORY_ALARMS,
allow ? STATE_ALLOW : STATE_DISALLOW);
return this;
@@ -553,7 +554,7 @@
* appear or to intercept them when DND is active.
* Disallowing media will mute the media stream when DND is active.
*/
- public Builder allowMedia(boolean allow) {
+ public @NonNull Builder allowMedia(boolean allow) {
mZenPolicy.mPriorityCategories.set(PRIORITY_CATEGORY_MEDIA,
allow ? STATE_ALLOW : STATE_DISALLOW);
return this;
@@ -563,7 +564,7 @@
* Whether to allow system sounds to play when DND is active.
* Disallowing system sounds will mute the system stream when DND is active.
*/
- public Builder allowSystem(boolean allow) {
+ public @NonNull Builder allowSystem(boolean allow) {
mZenPolicy.mPriorityCategories.set(PRIORITY_CATEGORY_SYSTEM,
allow ? STATE_ALLOW : STATE_DISALLOW);
return this;
@@ -573,7 +574,7 @@
* Whether to allow {@link PriorityCategory} sounds to play when DND is active.
* @hide
*/
- public Builder allowCategory(@PriorityCategory int category, boolean allow) {
+ public @NonNull Builder allowCategory(@PriorityCategory int category, boolean allow) {
switch (category) {
case PRIORITY_CATEGORY_ALARMS:
allowAlarms(allow);
@@ -601,7 +602,7 @@
* Whether {@link Notification#fullScreenIntent full screen intents} that are intercepted
* by DND are shown.
*/
- public Builder showFullScreenIntent(boolean show) {
+ public @NonNull Builder showFullScreenIntent(boolean show) {
mZenPolicy.mVisualEffects.set(VISUAL_EFFECT_FULL_SCREEN_INTENT,
show ? STATE_ALLOW : STATE_DISALLOW);
return this;
@@ -611,7 +612,7 @@
* Whether {@link NotificationChannel#shouldShowLights() notification lights} from
* notifications intercepted by DND are blocked.
*/
- public Builder showLights(boolean show) {
+ public @NonNull Builder showLights(boolean show) {
mZenPolicy.mVisualEffects.set(VISUAL_EFFECT_LIGHTS,
show ? STATE_ALLOW : STATE_DISALLOW);
return this;
@@ -620,7 +621,7 @@
/**
* Whether notifications intercepted by DND are prevented from peeking.
*/
- public Builder showPeeking(boolean show) {
+ public @NonNull Builder showPeeking(boolean show) {
mZenPolicy.mVisualEffects.set(VISUAL_EFFECT_PEEK,
show ? STATE_ALLOW : STATE_DISALLOW);
return this;
@@ -630,7 +631,7 @@
* Whether notifications intercepted by DND are prevented from appearing in the status bar
* on devices that support status bars.
*/
- public Builder showStatusBarIcons(boolean show) {
+ public @NonNull Builder showStatusBarIcons(boolean show) {
mZenPolicy.mVisualEffects.set(VISUAL_EFFECT_STATUS_BAR,
show ? STATE_ALLOW : STATE_DISALLOW);
return this;
@@ -640,7 +641,7 @@
* Whether {@link NotificationChannel#canShowBadge() badges} from
* notifications intercepted by DND are allowed on devices that support badging.
*/
- public Builder showBadges(boolean show) {
+ public @NonNull Builder showBadges(boolean show) {
mZenPolicy.mVisualEffects.set(VISUAL_EFFECT_BADGE,
show ? STATE_ALLOW : STATE_DISALLOW);
return this;
@@ -650,7 +651,7 @@
* Whether notification intercepted by DND are prevented from appearing on ambient displays
* on devices that support ambient display.
*/
- public Builder showInAmbientDisplay(boolean show) {
+ public @NonNull Builder showInAmbientDisplay(boolean show) {
mZenPolicy.mVisualEffects.set(VISUAL_EFFECT_AMBIENT,
show ? STATE_ALLOW : STATE_DISALLOW);
return this;
@@ -661,7 +662,7 @@
* list views like the notification shade or lockscreen on devices that support those
* views.
*/
- public Builder showInNotificationList(boolean show) {
+ public @NonNull Builder showInNotificationList(boolean show) {
mZenPolicy.mVisualEffects.set(VISUAL_EFFECT_NOTIFICATION_LIST,
show ? STATE_ALLOW : STATE_DISALLOW);
return this;
@@ -672,7 +673,7 @@
* {@link VisualEffect}
* @hide
*/
- public Builder showVisualEffect(@VisualEffect int effect, boolean show) {
+ public @NonNull Builder showVisualEffect(@VisualEffect int effect, boolean show) {
switch (effect) {
case VISUAL_EFFECT_FULL_SCREEN_INTENT:
showFullScreenIntent(show);
@@ -1001,7 +1002,7 @@
* Makes deep copy of this ZenPolicy.
* @hide
*/
- public ZenPolicy copy() {
+ public @NonNull ZenPolicy copy() {
final Parcel parcel = Parcel.obtain();
try {
writeToParcel(parcel, 0);
diff --git a/core/java/android/service/textclassifier/IConversationActionsCallback.aidl b/core/java/android/service/textclassifier/IConversationActionsCallback.aidl
deleted file mode 100644
index c35d424..0000000
--- a/core/java/android/service/textclassifier/IConversationActionsCallback.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.textclassifier;
-
-import android.view.textclassifier.ConversationActions;
-
-/**
- * Callback for a ConversationActions request.
- * @hide
- */
-oneway interface IConversationActionsCallback {
- void onSuccess(in ConversationActions conversationActions);
- void onFailure();
-}
\ No newline at end of file
diff --git a/core/java/android/service/textclassifier/ITextClassificationCallback.aidl b/core/java/android/service/textclassifier/ITextClassificationCallback.aidl
deleted file mode 100644
index 10bfe63..0000000
--- a/core/java/android/service/textclassifier/ITextClassificationCallback.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.textclassifier;
-
-import android.view.textclassifier.TextClassification;
-
-/**
- * Callback for a TextClassification request.
- * @hide
- */
-oneway interface ITextClassificationCallback {
- void onSuccess(in TextClassification classification);
- void onFailure();
-}
diff --git a/core/java/android/service/textclassifier/ITextSelectionCallback.aidl b/core/java/android/service/textclassifier/ITextClassifierCallback.aidl
similarity index 82%
rename from core/java/android/service/textclassifier/ITextSelectionCallback.aidl
rename to core/java/android/service/textclassifier/ITextClassifierCallback.aidl
index 1b4c4d1..2926734 100644
--- a/core/java/android/service/textclassifier/ITextSelectionCallback.aidl
+++ b/core/java/android/service/textclassifier/ITextClassifierCallback.aidl
@@ -16,13 +16,14 @@
package android.service.textclassifier;
+import android.os.Bundle;
import android.view.textclassifier.TextSelection;
/**
- * Callback for a TextSelection request.
+ * Callback for all requests from SystemTextClassifier.
* @hide
*/
-oneway interface ITextSelectionCallback {
- void onSuccess(in TextSelection selection);
+oneway interface ITextClassifierCallback {
+ void onSuccess(in Bundle result);
void onFailure();
}
\ No newline at end of file
diff --git a/core/java/android/service/textclassifier/ITextClassifierService.aidl b/core/java/android/service/textclassifier/ITextClassifierService.aidl
index 7941794..2f8d67b 100644
--- a/core/java/android/service/textclassifier/ITextClassifierService.aidl
+++ b/core/java/android/service/textclassifier/ITextClassifierService.aidl
@@ -16,11 +16,7 @@
package android.service.textclassifier;
-import android.service.textclassifier.IConversationActionsCallback;
-import android.service.textclassifier.ITextClassificationCallback;
-import android.service.textclassifier.ITextLanguageCallback;
-import android.service.textclassifier.ITextLinksCallback;
-import android.service.textclassifier.ITextSelectionCallback;
+import android.service.textclassifier.ITextClassifierCallback;
import android.view.textclassifier.ConversationActions;
import android.view.textclassifier.SelectionEvent;
import android.view.textclassifier.TextClassification;
@@ -41,17 +37,17 @@
void onSuggestSelection(
in TextClassificationSessionId sessionId,
in TextSelection.Request request,
- in ITextSelectionCallback callback);
+ in ITextClassifierCallback callback);
void onClassifyText(
in TextClassificationSessionId sessionId,
in TextClassification.Request request,
- in ITextClassificationCallback callback);
+ in ITextClassifierCallback callback);
void onGenerateLinks(
in TextClassificationSessionId sessionId,
in TextLinks.Request request,
- in ITextLinksCallback callback);
+ in ITextClassifierCallback callback);
// TODO: Remove
void onSelectionEvent(
@@ -72,10 +68,10 @@
void onDetectLanguage(
in TextClassificationSessionId sessionId,
in TextLanguage.Request request,
- in ITextLanguageCallback callback);
+ in ITextClassifierCallback callback);
void onSuggestConversationActions(
in TextClassificationSessionId sessionId,
in ConversationActions.Request request,
- in IConversationActionsCallback callback);
+ in ITextClassifierCallback callback);
}
diff --git a/core/java/android/service/textclassifier/ITextLanguageCallback.aidl b/core/java/android/service/textclassifier/ITextLanguageCallback.aidl
deleted file mode 100644
index 263d99af..0000000
--- a/core/java/android/service/textclassifier/ITextLanguageCallback.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.textclassifier;
-
-import android.view.textclassifier.TextLanguage;
-
-/**
- * Callback for a TextLanguage request.
- * @hide
- */
-oneway interface ITextLanguageCallback {
- void onSuccess(in TextLanguage textLanguage);
- void onFailure();
-}
\ No newline at end of file
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 235b8e8..4088ce8 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -17,6 +17,7 @@
package android.service.textclassifier;
import android.Manifest;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -27,8 +28,12 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.os.Bundle;
import android.os.CancellationSignal;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Slog;
@@ -46,6 +51,10 @@
import com.android.internal.util.Preconditions;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
/**
* Abstract base class for the TextClassifier service.
*
@@ -68,6 +77,11 @@
* </intent-filter>
* </service>}</pre>
*
+ * <p>From {@link android.os.Build.VERSION_CODES#Q} onward, all callbacks are called on the main
+ * thread. Prior to Q, there is no guarantee on what thread the callback will happen. You should
+ * make sure the callbacks are executed in your desired thread by using a executor, a handler or
+ * something else along the line.
+ *
* @see TextClassifier
* @hide
*/
@@ -85,201 +99,102 @@
public static final String SERVICE_INTERFACE =
"android.service.textclassifier.TextClassifierService";
+ /** @hide **/
+ private static final String KEY_RESULT = "key_result";
+
+ private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper(), null, true);
+ private final ExecutorService mSingleThreadExecutor = Executors.newSingleThreadExecutor();
+
private final ITextClassifierService.Stub mBinder = new ITextClassifierService.Stub() {
// TODO(b/72533911): Implement cancellation signal
@NonNull private final CancellationSignal mCancellationSignal = new CancellationSignal();
- /** {@inheritDoc} */
@Override
public void onSuggestSelection(
TextClassificationSessionId sessionId,
- TextSelection.Request request, ITextSelectionCallback callback) {
+ TextSelection.Request request, ITextClassifierCallback callback) {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
- TextClassifierService.this.onSuggestSelection(
- sessionId, request, mCancellationSignal,
- new Callback<TextSelection>() {
- @Override
- public void onSuccess(TextSelection result) {
- try {
- callback.onSuccess(result);
- } catch (RemoteException e) {
- Slog.d(LOG_TAG, "Error calling callback");
- }
- }
+ mMainThreadHandler.post(() -> TextClassifierService.this.onSuggestSelection(
+ sessionId, request, mCancellationSignal, new ProxyCallback<>(callback)));
- @Override
- public void onFailure(CharSequence error) {
- try {
- if (callback.asBinder().isBinderAlive()) {
- callback.onFailure();
- }
- } catch (RemoteException e) {
- Slog.d(LOG_TAG, "Error calling callback");
- }
- }
- });
}
- /** {@inheritDoc} */
@Override
public void onClassifyText(
TextClassificationSessionId sessionId,
- TextClassification.Request request, ITextClassificationCallback callback) {
+ TextClassification.Request request, ITextClassifierCallback callback) {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
- TextClassifierService.this.onClassifyText(
- sessionId, request, mCancellationSignal,
- new Callback<TextClassification>() {
- @Override
- public void onSuccess(TextClassification result) {
- try {
- callback.onSuccess(result);
- } catch (RemoteException e) {
- Slog.d(LOG_TAG, "Error calling callback");
- }
- }
-
- @Override
- public void onFailure(CharSequence error) {
- try {
- callback.onFailure();
- } catch (RemoteException e) {
- Slog.d(LOG_TAG, "Error calling callback");
- }
- }
- });
+ mMainThreadHandler.post(() -> TextClassifierService.this.onClassifyText(
+ sessionId, request, mCancellationSignal, new ProxyCallback<>(callback)));
}
- /** {@inheritDoc} */
@Override
public void onGenerateLinks(
TextClassificationSessionId sessionId,
- TextLinks.Request request, ITextLinksCallback callback) {
+ TextLinks.Request request, ITextClassifierCallback callback) {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
- TextClassifierService.this.onGenerateLinks(
- sessionId, request,
- mCancellationSignal,
- new Callback<TextLinks>() {
- @Override
- public void onSuccess(TextLinks result) {
- try {
- callback.onSuccess(result);
- } catch (RemoteException e) {
- Slog.d(LOG_TAG, "Error calling callback");
- }
- }
-
- @Override
- public void onFailure(CharSequence error) {
- try {
- callback.onFailure();
- } catch (RemoteException e) {
- Slog.d(LOG_TAG, "Error calling callback");
- }
- }
- });
+ mMainThreadHandler.post(() -> TextClassifierService.this.onGenerateLinks(
+ sessionId, request, mCancellationSignal, new ProxyCallback<>(callback)));
}
- /** {@inheritDoc} */
@Override
public void onSelectionEvent(
TextClassificationSessionId sessionId,
SelectionEvent event) {
Preconditions.checkNotNull(event);
- TextClassifierService.this.onSelectionEvent(sessionId, event);
+ mMainThreadHandler.post(
+ () -> TextClassifierService.this.onSelectionEvent(sessionId, event));
}
- /** {@inheritDoc} */
@Override
public void onTextClassifierEvent(
TextClassificationSessionId sessionId,
TextClassifierEvent event) {
Preconditions.checkNotNull(event);
- TextClassifierService.this.onTextClassifierEvent(sessionId, event);
+ mMainThreadHandler.post(
+ () -> TextClassifierService.this.onTextClassifierEvent(sessionId, event));
}
- /** {@inheritDoc} */
@Override
public void onDetectLanguage(
TextClassificationSessionId sessionId,
TextLanguage.Request request,
- ITextLanguageCallback callback) {
+ ITextClassifierCallback callback) {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
- TextClassifierService.this.onDetectLanguage(
- sessionId,
- request,
- mCancellationSignal,
- new Callback<TextLanguage>() {
- @Override
- public void onSuccess(TextLanguage result) {
- try {
- callback.onSuccess(result);
- } catch (RemoteException e) {
- Slog.d(LOG_TAG, "Error calling callback");
- }
- }
-
- @Override
- public void onFailure(CharSequence error) {
- try {
- callback.onFailure();
- } catch (RemoteException e) {
- Slog.d(LOG_TAG, "Error calling callback");
- }
- };
- });
+ mMainThreadHandler.post(() -> TextClassifierService.this.onDetectLanguage(
+ sessionId, request, mCancellationSignal, new ProxyCallback<>(callback)));
}
- /** {@inheritDoc} */
@Override
public void onSuggestConversationActions(
TextClassificationSessionId sessionId,
ConversationActions.Request request,
- IConversationActionsCallback callback) {
+ ITextClassifierCallback callback) {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
- TextClassifierService.this.onSuggestConversationActions(
- sessionId,
- request,
- mCancellationSignal,
- new Callback<ConversationActions>() {
- @Override
- public void onSuccess(ConversationActions result) {
- try {
- callback.onSuccess(result);
- } catch (RemoteException e) {
- Slog.d(LOG_TAG, "Error calling callback");
- }
- }
-
- @Override
- public void onFailure(CharSequence error) {
- try {
- callback.onFailure();
- } catch (RemoteException e) {
- Slog.d(LOG_TAG, "Error calling callback");
- }
- }
- });
+ mMainThreadHandler.post(() -> TextClassifierService.this.onSuggestConversationActions(
+ sessionId, request, mCancellationSignal, new ProxyCallback<>(callback)));
}
- /** {@inheritDoc} */
@Override
public void onCreateTextClassificationSession(
TextClassificationContext context, TextClassificationSessionId sessionId) {
Preconditions.checkNotNull(context);
Preconditions.checkNotNull(sessionId);
- TextClassifierService.this.onCreateTextClassificationSession(context, sessionId);
+ mMainThreadHandler.post(
+ () -> TextClassifierService.this.onCreateTextClassificationSession(
+ context, sessionId));
}
- /** {@inheritDoc} */
@Override
public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId) {
- TextClassifierService.this.onDestroyTextClassificationSession(sessionId);
+ mMainThreadHandler.post(
+ () -> TextClassifierService.this.onDestroyTextClassificationSession(sessionId));
}
};
@@ -301,6 +216,7 @@
* @param cancellationSignal object to watch for canceling the current operation
* @param callback the callback to return the result to
*/
+ @MainThread
public abstract void onSuggestSelection(
@Nullable TextClassificationSessionId sessionId,
@NonNull TextSelection.Request request,
@@ -316,6 +232,7 @@
* @param cancellationSignal object to watch for canceling the current operation
* @param callback the callback to return the result to
*/
+ @MainThread
public abstract void onClassifyText(
@Nullable TextClassificationSessionId sessionId,
@NonNull TextClassification.Request request,
@@ -331,6 +248,7 @@
* @param cancellationSignal object to watch for canceling the current operation
* @param callback the callback to return the result to
*/
+ @MainThread
public abstract void onGenerateLinks(
@Nullable TextClassificationSessionId sessionId,
@NonNull TextLinks.Request request,
@@ -345,12 +263,14 @@
* @param cancellationSignal object to watch for canceling the current operation
* @param callback the callback to return the result to
*/
+ @MainThread
public void onDetectLanguage(
@Nullable TextClassificationSessionId sessionId,
@NonNull TextLanguage.Request request,
@NonNull CancellationSignal cancellationSignal,
@NonNull Callback<TextLanguage> callback) {
- callback.onSuccess(getLocalTextClassifier().detectLanguage(request));
+ mSingleThreadExecutor.submit(() ->
+ callback.onSuccess(getLocalTextClassifier().detectLanguage(request)));
}
/**
@@ -361,12 +281,14 @@
* @param cancellationSignal object to watch for canceling the current operation
* @param callback the callback to return the result to
*/
+ @MainThread
public void onSuggestConversationActions(
@Nullable TextClassificationSessionId sessionId,
@NonNull ConversationActions.Request request,
@NonNull CancellationSignal cancellationSignal,
@NonNull Callback<ConversationActions> callback) {
- callback.onSuccess(getLocalTextClassifier().suggestConversationActions(request));
+ mSingleThreadExecutor.submit(() ->
+ callback.onSuccess(getLocalTextClassifier().suggestConversationActions(request)));
}
/**
@@ -383,6 +305,7 @@
* instead
*/
@Deprecated
+ @MainThread
public void onSelectionEvent(
@Nullable TextClassificationSessionId sessionId, @NonNull SelectionEvent event) {}
@@ -396,6 +319,7 @@
* @param sessionId the session id
* @param event the TextClassifier event
*/
+ @MainThread
public void onTextClassifierEvent(
@Nullable TextClassificationSessionId sessionId, @NonNull TextClassifierEvent event) {}
@@ -405,6 +329,7 @@
* @param context the text classification context
* @param sessionId the session's Id
*/
+ @MainThread
public void onCreateTextClassificationSession(
@NonNull TextClassificationContext context,
@NonNull TextClassificationSessionId sessionId) {}
@@ -414,6 +339,7 @@
*
* @param sessionId the id of the session to destroy
*/
+ @MainThread
public void onDestroyTextClassificationSession(
@NonNull TextClassificationSessionId sessionId) {}
@@ -441,6 +367,11 @@
return TextClassifier.NO_OP;
}
+ /** @hide **/
+ public static <T extends Parcelable> T getResponse(Bundle bundle) {
+ return bundle.getParcelable(KEY_RESULT);
+ }
+
/**
* Callbacks for TextClassifierService results.
*
@@ -494,4 +425,44 @@
si.permission));
return null;
}
+
+ /**
+ * Forwards the callback result to a wrapped binder callback.
+ */
+ private static final class ProxyCallback<T extends Parcelable> implements Callback<T> {
+ private WeakReference<ITextClassifierCallback> mTextClassifierCallback;
+
+ private ProxyCallback(ITextClassifierCallback textClassifierCallback) {
+ mTextClassifierCallback =
+ new WeakReference<>(Preconditions.checkNotNull(textClassifierCallback));
+ }
+
+ @Override
+ public void onSuccess(T result) {
+ ITextClassifierCallback callback = mTextClassifierCallback.get();
+ if (callback == null) {
+ return;
+ }
+ try {
+ Bundle bundle = new Bundle(1);
+ bundle.putParcelable(KEY_RESULT, result);
+ callback.onSuccess(bundle);
+ } catch (RemoteException e) {
+ Slog.d(LOG_TAG, "Error calling callback");
+ }
+ }
+
+ @Override
+ public void onFailure(CharSequence error) {
+ ITextClassifierCallback callback = mTextClassifierCallback.get();
+ if (callback == null) {
+ return;
+ }
+ try {
+ callback.onFailure();
+ } catch (RemoteException e) {
+ Slog.d(LOG_TAG, "Error calling callback");
+ }
+ }
+ }
}
diff --git a/core/java/android/text/style/LineHeightSpan.java b/core/java/android/text/style/LineHeightSpan.java
index a5d5af2..7fb0f95 100644
--- a/core/java/android/text/style/LineHeightSpan.java
+++ b/core/java/android/text/style/LineHeightSpan.java
@@ -88,7 +88,7 @@
/**
* Constructor called from {@link TextUtils} to restore the span from a parcel
*/
- public Standard(Parcel src) {
+ public Standard(@NonNull Parcel src) {
mHeight = src.readInt();
}
diff --git a/core/java/android/view/TEST_MAPPING b/core/java/android/view/TEST_MAPPING
new file mode 100644
index 0000000..87d428a
--- /dev/null
+++ b/core/java/android/view/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsUiRenderingTestCases"
+ },
+ {
+ "name": "CtsAccelerationTestCases"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 135a891..f3bbca3 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -22,6 +22,7 @@
import static android.view.WindowInsets.Type.LAST;
import static android.view.WindowInsets.Type.SIDE_BARS;
import static android.view.WindowInsets.Type.SIZE;
+import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
import static android.view.WindowInsets.Type.TOP_BAR;
import static android.view.WindowInsets.Type.all;
import static android.view.WindowInsets.Type.compatSystemInsets;
@@ -35,7 +36,6 @@
import android.graphics.Insets;
import android.graphics.Rect;
import android.util.SparseArray;
-import android.view.InsetsState.InternalInsetType;
import android.view.WindowInsets.Type.InsetType;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethod;
@@ -229,6 +229,7 @@
static void assignCompatInsets(Insets[] typeInsetMap, Rect insets) {
typeInsetMap[indexOf(TOP_BAR)] = Insets.of(0, insets.top, 0, 0);
typeInsetMap[indexOf(SIDE_BARS)] = Insets.of(insets.left, 0, insets.right, insets.bottom);
+ typeInsetMap[indexOf(SYSTEM_GESTURES)] = Insets.of(insets);
}
private static boolean[] createCompatVisibilityMap(@Nullable Insets[] typeInsetMap) {
@@ -630,6 +631,28 @@
}
/**
+ * Returns the system gesture insets.
+ *
+ * <p>The system gesture insets represent the area of a window where system gestures have
+ * priority and may consume some or all touch input, e.g. due to the a system bar
+ * occupying it, or it being reserved for touch-only gestures.
+ *
+ * <p>Simple taps are guaranteed to reach the window even within the system gesture insets,
+ * as long as they are outside the {@link #getSystemWindowInsets() system window insets}.
+ *
+ * <p>When {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} is requested, an inset will be returned
+ * even when the system gestures are inactive due to
+ * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} or
+ * {@link View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.
+ *
+ * <p>This inset does not affect the result of {@link #isConsumed()} and cannot be consumed.
+ */
+ @NonNull
+ public Insets getSystemGestureInsets() {
+ return getInsets(mTypeInsetsMap, SYSTEM_GESTURES);
+ }
+
+ /**
* Returns a copy of this WindowInsets with the stable insets fully consumed.
*
* @return A modified copy of this WindowInsets
@@ -853,6 +876,22 @@
}
/**
+ * Sets system gesture insets in pixels.
+ *
+ * <p>The system gesture insets represent the area of a window where system gestures have
+ * priority and may consume some or all touch input, e.g. due to the a system bar
+ * occupying it, or it being reserved for touch-only gestures.
+ *
+ * @see #getSystemGestureInsets()
+ * @return itself
+ */
+ @NonNull
+ public Builder setSystemGestureInsets(@NonNull Insets insets) {
+ WindowInsets.setInsets(mTypeInsetsMap, SYSTEM_GESTURES, insets);
+ return this;
+ }
+
+ /**
* Sets the insets of a specific window type in pixels.
*
* <p>The insets represents the area of a a window that is partially or fully obscured by
@@ -1005,8 +1044,10 @@
static final int IME = 0x2;
static final int SIDE_BARS = 0x4;
- static final int LAST = 0x8;
- static final int SIZE = 4;
+ static final int SYSTEM_GESTURES = 0x8;
+
+ static final int LAST = 0x10;
+ static final int SIZE = 5;
static final int WINDOW_DECOR = LAST;
static int indexOf(@InsetType int type) {
@@ -1017,8 +1058,10 @@
return 1;
case SIDE_BARS:
return 2;
- case WINDOW_DECOR:
+ case SYSTEM_GESTURES:
return 3;
+ case WINDOW_DECOR:
+ return 4;
default:
throw new IllegalArgumentException("type needs to be >= FIRST and <= LAST,"
+ " type=" + type);
@@ -1030,7 +1073,7 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, value = { TOP_BAR, IME, SIDE_BARS, WINDOW_DECOR })
+ @IntDef(flag = true, value = { TOP_BAR, IME, SIDE_BARS, WINDOW_DECOR, SYSTEM_GESTURES })
public @interface InsetType {
}
@@ -1064,6 +1107,27 @@
}
/**
+ * Returns an inset type representing the system gesture insets.
+ *
+ * <p>The system gesture insets represent the area of a window where system gestures have
+ * priority and may consume some or all touch input, e.g. due to the a system bar
+ * occupying it, or it being reserved for touch-only gestures.
+ *
+ * <p>Simple taps are guaranteed to reach the window even within the system gesture insets,
+ * as long as they are outside the {@link #getSystemWindowInsets() system window insets}.
+ *
+ * <p>When {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} is requested, an inset will be returned
+ * even when the system gestures are inactive due to
+ * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} or
+ * {@link View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.
+ *
+ * @see #getSystemGestureInsets()
+ */
+ public static @InsetType int systemGestures() {
+ return SYSTEM_GESTURES;
+ }
+
+ /**
* @return All system bars. Includes {@link #topBar()} as well as {@link #sideBars()}, but
* not {@link #ime()}.
*/
@@ -1082,6 +1146,9 @@
/**
* @return All inset types combined.
+ *
+ * TODO: Figure out if this makes sense at all, mixing e.g {@link #systemGestures()} and
+ * {@link #ime()} does not seem very useful.
*/
public static @InsetType int all() {
return 0xFFFFFFFF;
diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java
index 86f85bf..b9dc0dd 100644
--- a/core/java/android/view/contentcapture/ContentCaptureContext.java
+++ b/core/java/android/view/contentcapture/ContentCaptureContext.java
@@ -24,7 +24,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.LocusId;
-import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -212,11 +211,11 @@
}
/**
- * Helper that creates a {@link ContentCaptureContext} associated with the given {@code uri}.
+ * Helper that creates a {@link ContentCaptureContext} associated with the given {@code id}.
*/
@NonNull
- public static ContentCaptureContext forLocusId(@NonNull Uri uri) {
- return new Builder(new LocusId(uri)).build();
+ public static ContentCaptureContext forLocusId(@NonNull String id) {
+ return new Builder(new LocusId(id)).build();
}
/**
diff --git a/core/java/android/view/contentcapture/UserDataRemovalRequest.java b/core/java/android/view/contentcapture/UserDataRemovalRequest.java
index b273f7c..3e1e4ab 100644
--- a/core/java/android/view/contentcapture/UserDataRemovalRequest.java
+++ b/core/java/android/view/contentcapture/UserDataRemovalRequest.java
@@ -15,6 +15,7 @@
*/
package android.view.contentcapture;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.ActivityThread;
import android.content.LocusId;
@@ -24,6 +25,8 @@
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -33,6 +36,19 @@
*/
public final class UserDataRemovalRequest implements Parcelable {
+ /**
+ * When set, service should use the {@link LocusId#getId()} as prefix for the data to be
+ * removed.
+ */
+ public static final int FLAG_IS_PREFIX = 0x1;
+
+ /** @hide */
+ @IntDef(prefix = { "FLAG" }, flag = true, value = {
+ FLAG_IS_PREFIX
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Flags {}
+
private final String mPackageName;
private final boolean mForEverything;
@@ -46,7 +62,7 @@
mLocusIdRequests = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
mLocusIdRequests.add(new LocusIdRequest(builder.mLocusIds.get(i),
- builder.mRecursive.get(i) == 1));
+ builder.mFlags.get(i)));
}
}
}
@@ -59,7 +75,7 @@
mLocusIdRequests = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
mLocusIdRequests.add(new LocusIdRequest((LocusId) parcel.readValue(null),
- parcel.readBoolean()));
+ parcel.readInt()));
}
}
}
@@ -94,7 +110,7 @@
private boolean mForEverything;
private ArrayList<LocusId> mLocusIds;
- private IntArray mRecursive;
+ private IntArray mFlags;
private boolean mDestroyed;
@@ -116,24 +132,24 @@
* Request service to remove data associated with a given {@link LocusId}.
*
* @param locusId the {@link LocusId} being requested to be removed.
- * @param recursive whether it should remove the data associated with just the
- * {@code LocusId} or its tree of descendants.
+ * @param flags either {@link UserDataRemovalRequest#FLAG_IS_PREFIX} or {@code 0}
*
* @return this builder
*/
@NonNull
- public Builder addLocusId(@NonNull LocusId locusId, boolean recursive) {
+ public Builder addLocusId(@NonNull LocusId locusId, @Flags int flags) {
throwIfDestroyed();
Preconditions.checkState(!mForEverything, "Already is for everything");
Preconditions.checkNotNull(locusId);
+ // felipeal: check flags
if (mLocusIds == null) {
mLocusIds = new ArrayList<>();
- mRecursive = new IntArray();
+ mFlags = new IntArray();
}
mLocusIds.add(locusId);
- mRecursive.add(recursive ? 1 : 0);
+ mFlags.add(flags);
return this;
}
@@ -144,7 +160,8 @@
public UserDataRemovalRequest build() {
throwIfDestroyed();
- Preconditions.checkState(mForEverything || mLocusIds != null);
+ Preconditions.checkState(mForEverything || mLocusIds != null,
+ "must call either #forEverything() or add one #addLocusId()");
mDestroyed = true;
return new UserDataRemovalRequest(this);
@@ -170,7 +187,7 @@
for (int i = 0; i < size; i++) {
final LocusIdRequest request = mLocusIdRequests.get(i);
parcel.writeValue(request.getLocusId());
- parcel.writeBoolean(request.isRecursive());
+ parcel.writeInt(request.getFlags());
}
}
}
@@ -196,11 +213,11 @@
*/
public final class LocusIdRequest {
private final @NonNull LocusId mLocusId;
- private final boolean mRecursive;
+ private final @Flags int mFlags;
- private LocusIdRequest(@NonNull LocusId locusId, boolean recursive) {
+ private LocusIdRequest(@NonNull LocusId locusId, @Flags int flags) {
this.mLocusId = locusId;
- this.mRecursive = recursive;
+ this.mFlags = flags;
}
/**
@@ -212,12 +229,13 @@
}
/**
- * Checks whether the request is to remove just the data associated with the {@link LocusId}
- * per se, or also its descendants.
+ * Gets the flags associates with request.
+ *
+ * @return either {@link UserDataRemovalRequest#FLAG_IS_PREFIX} or {@code 0}.
*/
@NonNull
- public boolean isRecursive() {
- return mRecursive;
+ public @Flags int getFlags() {
+ return mFlags;
}
}
}
diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java
index 8b370f5..8f8766e 100644
--- a/core/java/android/view/textclassifier/SystemTextClassifier.java
+++ b/core/java/android/view/textclassifier/SystemTextClassifier.java
@@ -20,15 +20,14 @@
import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.content.Context;
+import android.os.Bundle;
import android.os.Looper;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.service.textclassifier.IConversationActionsCallback;
-import android.service.textclassifier.ITextClassificationCallback;
+import android.service.textclassifier.ITextClassifierCallback;
import android.service.textclassifier.ITextClassifierService;
-import android.service.textclassifier.ITextLanguageCallback;
-import android.service.textclassifier.ITextLinksCallback;
-import android.service.textclassifier.ITextSelectionCallback;
+import android.service.textclassifier.TextClassifierService;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
@@ -73,9 +72,10 @@
Utils.checkMainThread();
try {
request.setCallingPackageName(mPackageName);
- final TextSelectionCallback callback = new TextSelectionCallback();
+ final BlockingCallback<TextSelection> callback =
+ new BlockingCallback<>("textselection");
mManagerService.onSuggestSelection(mSessionId, request, callback);
- final TextSelection selection = callback.mReceiver.get();
+ final TextSelection selection = callback.get();
if (selection != null) {
return selection;
}
@@ -95,9 +95,10 @@
Utils.checkMainThread();
try {
request.setCallingPackageName(mPackageName);
- final TextClassificationCallback callback = new TextClassificationCallback();
+ final BlockingCallback<TextClassification> callback =
+ new BlockingCallback<>("textclassification");
mManagerService.onClassifyText(mSessionId, request, callback);
- final TextClassification classification = callback.mReceiver.get();
+ final TextClassification classification = callback.get();
if (classification != null) {
return classification;
}
@@ -122,9 +123,10 @@
try {
request.setCallingPackageName(mPackageName);
- final TextLinksCallback callback = new TextLinksCallback();
+ final BlockingCallback<TextLinks> callback =
+ new BlockingCallback<>("textlinks");
mManagerService.onGenerateLinks(mSessionId, request, callback);
- final TextLinks links = callback.mReceiver.get();
+ final TextLinks links = callback.get();
if (links != null) {
return links;
}
@@ -165,9 +167,10 @@
try {
request.setCallingPackageName(mPackageName);
- final TextLanguageCallback callback = new TextLanguageCallback();
+ final BlockingCallback<TextLanguage> callback =
+ new BlockingCallback<>("textlanguage");
mManagerService.onDetectLanguage(mSessionId, request, callback);
- final TextLanguage textLanguage = callback.mReceiver.get();
+ final TextLanguage textLanguage = callback.get();
if (textLanguage != null) {
return textLanguage;
}
@@ -184,9 +187,10 @@
try {
request.setCallingPackageName(mPackageName);
- final ConversationActionsCallback callback = new ConversationActionsCallback();
+ final BlockingCallback<ConversationActions> callback =
+ new BlockingCallback<>("conversation-actions");
mManagerService.onSuggestConversationActions(mSessionId, request, callback);
- final ConversationActions conversationActions = callback.mReceiver.get();
+ final ConversationActions conversationActions = callback.get();
if (conversationActions != null) {
return conversationActions;
}
@@ -245,82 +249,28 @@
}
}
- private static final class TextSelectionCallback extends ITextSelectionCallback.Stub {
+ private static final class BlockingCallback<T extends Parcelable>
+ extends ITextClassifierCallback.Stub {
+ private final ResponseReceiver<T> mReceiver;
- final ResponseReceiver<TextSelection> mReceiver = new ResponseReceiver<>("textselection");
+ BlockingCallback(String name) {
+ mReceiver = new ResponseReceiver<>(name);
+ }
@Override
- public void onSuccess(TextSelection selection) {
- mReceiver.onSuccess(selection);
+ public void onSuccess(Bundle result) {
+ mReceiver.onSuccess(TextClassifierService.getResponse(result));
}
@Override
public void onFailure() {
mReceiver.onFailure();
}
- }
- private static final class TextClassificationCallback extends ITextClassificationCallback.Stub {
-
- final ResponseReceiver<TextClassification> mReceiver =
- new ResponseReceiver<>("textclassification");
-
- @Override
- public void onSuccess(TextClassification classification) {
- mReceiver.onSuccess(classification);
+ public T get() {
+ return mReceiver.get();
}
- @Override
- public void onFailure() {
- mReceiver.onFailure();
- }
- }
-
- private static final class TextLinksCallback extends ITextLinksCallback.Stub {
-
- final ResponseReceiver<TextLinks> mReceiver = new ResponseReceiver<>("textlinks");
-
- @Override
- public void onSuccess(TextLinks links) {
- mReceiver.onSuccess(links);
- }
-
- @Override
- public void onFailure() {
- mReceiver.onFailure();
- }
- }
-
- private static final class TextLanguageCallback extends ITextLanguageCallback.Stub {
-
- final ResponseReceiver<TextLanguage> mReceiver = new ResponseReceiver<>("textlanguage");
-
- @Override
- public void onSuccess(TextLanguage textLanguage) {
- mReceiver.onSuccess(textLanguage);
- }
-
- @Override
- public void onFailure() {
- mReceiver.onFailure();
- }
- }
-
- private static final class ConversationActionsCallback
- extends IConversationActionsCallback.Stub {
-
- final ResponseReceiver<ConversationActions> mReceiver =
- new ResponseReceiver<>("conversationaction");
-
- @Override
- public void onSuccess(ConversationActions conversationActions) {
- mReceiver.onSuccess(conversationActions);
- }
-
- @Override
- public void onFailure() {
- mReceiver.onFailure();
- }
}
private static final class ResponseReceiver<T> {
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 457be34..a3e89c8 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -1474,7 +1474,7 @@
*
* @param child the View to scroll to
*/
- public void scrollToDescendant(View child) {
+ public void scrollToDescendant(@NonNull View child) {
if (!mIsLayoutDirty) {
child.getDrawingRect(mTempRect);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 73792b0..04bcb14 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12744,7 +12744,7 @@
* return value may not be the same as the one TextView uses if the View's layout direction is
* not resolved or detached from parent root view.
*/
- public TextDirectionHeuristic getTextDirectionHeuristic() {
+ public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() {
if (hasPasswordTransformationMethod()) {
// passwords fields should be LTR
return TextDirectionHeuristics.LTR;
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 5064d23..445075d 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -616,7 +616,7 @@
// OS: Q
ACTION_PANEL_INTERACTION = 1658;
- // ACTION: Show Contextual homepage, log latency in loading cards
+ // ACTION: Show Contextual homepage. Log total loading latency.
ACTION_CONTEXTUAL_HOME_SHOW = 1662;
// ACTION: Contextual card displays
@@ -649,6 +649,15 @@
ACTION_ATCSCUC = 1680;
ACTION_ATCHNUC = 1681;
+
+ // ACTION: Individual contextual card loading time
+ ACTION_CONTEXTUAL_CARD_LOAD = 1684;
+
+ //ACTION: Contextual card loading timeout
+ ACTION_CONTEXTUAL_CARD_LOAD_TIMEOUT = 1685;
+
+ //ACTION: Log result for each card's eligibility check
+ ACTION_CONTEXTUAL_CARD_ELIGIBILITY = 1686;
}
/**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ae3e714..4c4393d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3139,6 +3139,14 @@
<permission android:name="android.permission.BIND_CONTENT_CAPTURE_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be required by a android.service.contentsuggestions.ContentSuggestionsService,
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- Must be required by a android.service.autofill.augmented.AugmentedAutofillService,
to ensure that only the system can bind to it.
@SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -4455,6 +4463,11 @@
@hide -->
<permission android:name="android.permission.MANAGE_SENSOR_PRIVACY"
android:protectionLevel="signature" />
+ <!-- @SystemApi Permission that protects the {@link Intent#ACTION_REVIEW_ACCESSIBILITY_SERVICES}
+ intent.
+ @hide -->
+ <permission android:name="android.permission.REVIEW_ACCESSIBILITY_SERVICES"
+ android:protectionLevel="signature" />
<!-- @SystemApi Allows an activity to replace the app name and icon displayed in share targets
in the sharesheet for the Q-release and later.
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index ebc6be7..ad1403d 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -132,6 +132,7 @@
Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
Settings.Global.AUTOMATIC_POWER_SAVER_MODE,
Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED,
+ Settings.Global.BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST,
Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
Settings.Global.BROADCAST_BG_CONSTANTS,
Settings.Global.BROADCAST_FG_CONSTANTS,
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java
index a5ac270..de2edc3 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java
@@ -25,7 +25,6 @@
import static org.testng.Assert.assertThrows;
import android.content.LocusId;
-import android.net.Uri;
import android.os.Parcel;
import android.os.SystemClock;
import android.view.autofill.AutofillId;
@@ -47,7 +46,7 @@
private static final long MY_EPOCH = SystemClock.uptimeMillis();
- private static final LocusId ID = new LocusId(Uri.parse("WHATEVER"));
+ private static final LocusId ID = new LocusId("WHATEVER");
// Not using @Mock because it's final - no need to be fancy here....
private final ContentCaptureContext mClientContext =
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index 2110a8f..3e53a38 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -33,6 +33,7 @@
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" />
<permission name="android.permission.MASTER_CLEAR"/>
+ <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<permission name="android.permission.MOVE_PACKAGE"/>
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index e623354..7345ea4 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -193,7 +193,7 @@
*
* @param name The debug name to use for this HardwareRenderer instance
*/
- public void setName(String name) {
+ public void setName(@NonNull String name) {
nSetName(mNativeProxy, name);
}
@@ -330,7 +330,7 @@
*
* @return this instance
*/
- public FrameRenderRequest setVsyncTime(long vsyncTime) {
+ public @NonNull FrameRenderRequest setVsyncTime(long vsyncTime) {
mFrameInfo.setVsync(vsyncTime, vsyncTime);
mFrameInfo.addFlags(FrameInfo.FLAG_SURFACE_CANVAS);
return this;
@@ -351,7 +351,7 @@
*
* @return this instance
*/
- public FrameRenderRequest setFrameCommitCallback(@NonNull Executor executor,
+ public @NonNull FrameRenderRequest setFrameCommitCallback(@NonNull Executor executor,
@NonNull Runnable frameCommitCallback) {
setFrameCompleteCallback(frameNr -> executor.execute(frameCommitCallback));
return this;
@@ -372,7 +372,7 @@
* completion.
* @return this instance
*/
- public FrameRenderRequest setWaitForPresent(boolean shouldWait) {
+ public @NonNull FrameRenderRequest setWaitForPresent(boolean shouldWait) {
mWaitForPresent = shouldWait;
return this;
}
@@ -406,7 +406,7 @@
* @return An instance of {@link FrameRenderRequest}. The instance may be reused for every
* frame, so the caller should not hold onto it for longer than a single render request.
*/
- public FrameRenderRequest createRenderRequest() {
+ public @NonNull FrameRenderRequest createRenderRequest() {
mRenderRequest.reset();
return mRenderRequest;
}
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 7016cc7..2cf802b 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -59,6 +59,8 @@
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.nio.ByteBuffer;
+import java.util.Locale;
+import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -838,6 +840,40 @@
}
/**
+ * Return if the given MIME type is a supported file format that can be
+ * decoded by this class. This can be useful to determine if a file can be
+ * decoded directly, or if it needs to be converted into a more general
+ * format using an API like {@link ContentResolver#openTypedAssetFile}.
+ */
+ public static boolean isMimeTypeSupported(@NonNull String mimeType) {
+ Objects.requireNonNull(mimeType);
+ switch (mimeType.toLowerCase(Locale.US)) {
+ case "image/png":
+ case "image/jpeg":
+ case "image/webp":
+ case "image/gif":
+ case "image/heif":
+ case "image/heic":
+ case "image/bmp":
+ case "image/x-ico":
+ case "image/vnd.wap.wbmp":
+ case "image/x-sony-arw":
+ case "image/x-canon-cr2":
+ case "image/x-adobe-dng":
+ case "image/x-nikon-nef":
+ case "image/x-nikon-nrw":
+ case "image/x-olympus-orf":
+ case "image/x-fuji-raf":
+ case "image/x-panasonic-rw2":
+ case "image/x-pentax-pef":
+ case "image/x-samsung-srw":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
* Create a new {@link Source Source} from a resource.
*
* @param res the {@link Resources} object containing the image data.
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 42b6acd..c3bcbb4 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -62,7 +62,7 @@
*
* <h3>Creating a RenderNode</h3>
* <pre class="prettyprint">
- * RenderNode renderNode = RenderNode.create("myRenderNode");
+ * RenderNode renderNode = new RenderNode("myRenderNode");
* renderNode.setLeftTopRightBottom(0, 0, 50, 50); // Set the size to 50x50
* RecordingCanvas canvas = renderNode.beginRecording();
* try {
@@ -106,7 +106,7 @@
*
* <pre class="prettyprint">
* private void createDisplayList() {
- * mRenderNode = RenderNode.create("MyRenderNode");
+ * mRenderNode = new RenderNode("MyRenderNode");
* mRenderNode.setLeftTopRightBottom(0, 0, width, height);
* RecordingCanvas canvas = mRenderNode.beginRecording();
* try {
@@ -338,7 +338,7 @@
* @see #endRecording()
* @see #hasDisplayList()
*/
- public RecordingCanvas beginRecording(int width, int height) {
+ public @NonNull RecordingCanvas beginRecording(int width, int height) {
if (mCurrentRecordingCanvas != null) {
throw new IllegalStateException(
"Recording currently in progress - missing #endRecording() call?");
@@ -358,7 +358,7 @@
* @see #endRecording()
* @see #hasDisplayList()
*/
- public RecordingCanvas beginRecording() {
+ public @NonNull RecordingCanvas beginRecording() {
return beginRecording(nGetWidth(mNativeRenderNode), nGetHeight(mNativeRenderNode));
}
@@ -1285,7 +1285,7 @@
* @param position The position rectangle in pixels
* @return True if the value changed, false if the new value was the same as the previous value.
*/
- public boolean setPosition(Rect position) {
+ public boolean setPosition(@NonNull Rect position) {
return nSetLeftTopRightBottom(mNativeRenderNode,
position.left, position.top, position.right, position.bottom);
}
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 1894e32..e58e802 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -17,9 +17,11 @@
package android.graphics.drawable;
import android.annotation.ColorInt;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.Px;
import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
@@ -670,7 +672,7 @@
* @see #mutate()
* @see #setColor(int)
*/
- public void setColors(@ColorInt int[] colors) {
+ public void setColors(@Nullable @ColorInt int[] colors) {
setColors(colors, null);
}
@@ -690,7 +692,7 @@
* @see #mutate()
* @see #setColors(int[])
*/
- public void setColors(@ColorInt int[] colors, @Nullable float[] offsets) {
+ public void setColors(@Nullable @ColorInt int[] colors, @Nullable float[] offsets) {
mGradientState.setGradientColors(colors);
mGradientState.mPositions = offsets;
mGradientIsDirty = true;
@@ -877,7 +879,11 @@
* @see #getInnerRadiusRatio()
* @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio
*/
- public void setInnerRadiusRatio(float innerRadiusRatio) {
+ public void setInnerRadiusRatio(
+ @FloatRange(from = 0.0f, fromInclusive = false) float innerRadiusRatio) {
+ if (innerRadiusRatio <= 0) {
+ throw new IllegalArgumentException("Ratio must be greater than zero");
+ }
mGradientState.mInnerRadiusRatio = innerRadiusRatio;
mPathIsDirty = true;
invalidateSelf();
@@ -899,7 +905,7 @@
* @see #getInnerRadius()
* @attr ref android.R.styleable#GradientDrawable_innerRadius
*/
- public void setInnerRadius(int innerRadius) {
+ public void setInnerRadius(@Px int innerRadius) {
mGradientState.mInnerRadius = innerRadius;
mPathIsDirty = true;
invalidateSelf();
@@ -911,7 +917,7 @@
* @see #setInnerRadius(int)
* @attr ref android.R.styleable#GradientDrawable_innerRadius
*/
- public int getInnerRadius() {
+ public @Px int getInnerRadius() {
return mGradientState.mInnerRadius;
}
@@ -921,7 +927,11 @@
* @see #getThicknessRatio()
* @attr ref android.R.styleable#GradientDrawable_thicknessRatio
*/
- public void setThicknessRatio(float thicknessRatio) {
+ public void setThicknessRatio(
+ @FloatRange(from = 0.0f, fromInclusive = false) float thicknessRatio) {
+ if (thicknessRatio <= 0) {
+ throw new IllegalArgumentException("Ratio must be greater than zero");
+ }
mGradientState.mThicknessRatio = thicknessRatio;
mPathIsDirty = true;
invalidateSelf();
@@ -942,7 +952,7 @@
*
* @attr ref android.R.styleable#GradientDrawable_thickness
*/
- public void setThickness(int thickness) {
+ public void setThickness(@Px int thickness) {
mGradientState.mThickness = thickness;
mPathIsDirty = true;
invalidateSelf();
@@ -954,7 +964,7 @@
* @see #setThickness(int)
* @attr ref android.R.styleable#GradientDrawable_thickness
*/
- public int getThickness() {
+ public @Px int getThickness() {
return mGradientState.mThickness;
}
@@ -970,7 +980,7 @@
* @attr ref android.R.styleable#GradientDrawablePadding_right
* @attr ref android.R.styleable#GradientDrawablePadding_bottom
*/
- public void setPadding(int left, int top, int right, int bottom) {
+ public void setPadding(@Px int left, @Px int top, @Px int right, @Px int bottom) {
if (mGradientState.mPadding == null) {
mGradientState.mPadding = new Rect();
}
diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java
index 2855227..f67188c 100644
--- a/graphics/java/android/graphics/drawable/StateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/StateListDrawable.java
@@ -256,7 +256,7 @@
* @see #getStateCount()
* @see #getStateDrawable(int)
*/
- public int[] getStateSet(int index) {
+ public @NonNull int[] getStateSet(int index) {
return mStateListState.mStateSets[index];
}
@@ -268,7 +268,7 @@
* @see #getStateCount()
* @see #getStateSet(int)
*/
- public Drawable getStateDrawable(int index) {
+ public @Nullable Drawable getStateDrawable(int index) {
return mStateListState.getChild(index);
}
@@ -280,7 +280,7 @@
* @see #getStateDrawable(int)
* @see #getStateSet(int)
*/
- public int findStateDrawableIndex(int[] stateSet) {
+ public int findStateDrawableIndex(@NonNull int[] stateSet) {
return mStateListState.indexOfStateSet(stateSet);
}
diff --git a/libs/hwui/TEST_MAPPING b/libs/hwui/TEST_MAPPING
new file mode 100644
index 0000000..d9f2acb
--- /dev/null
+++ b/libs/hwui/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsUiRenderingTestCases"
+ },
+ {
+ "name": "CtsGraphicsTestCases"
+ },
+ {
+ "name": "CtsAccelerationTestCases"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/libs/incident/include/android/os/IncidentReportArgs.h b/libs/incident/include/android/os/IncidentReportArgs.h
index 5e8eac1..f056d3b 100644
--- a/libs/incident/include/android/os/IncidentReportArgs.h
+++ b/libs/incident/include/android/os/IncidentReportArgs.h
@@ -47,12 +47,16 @@
void setAll(bool all);
void setDest(int dest);
void addSection(int section);
+ void setReceiverPkg(const string& pkg);
+ void setReceiverCls(const string& cls);
void addHeader(const vector<uint8_t>& headerProto);
inline bool all() const { return mAll; }
bool containsSection(int section) const;
inline int dest() const { return mDest; }
inline const set<int>& sections() const { return mSections; }
+ inline const String16& receiverPkg() const { return mReceiverPkg; }
+ inline const String16& receiverCls() const { return mReceiverCls; }
inline const vector<vector<uint8_t>>& headers() const { return mHeaders; }
void merge(const IncidentReportArgs& that);
@@ -62,6 +66,8 @@
vector<vector<uint8_t>> mHeaders;
bool mAll;
int mDest;
+ String16 mReceiverPkg;
+ String16 mReceiverCls;
};
}
diff --git a/libs/incident/src/IncidentReportArgs.cpp b/libs/incident/src/IncidentReportArgs.cpp
index 06b7a5b..46c8dcf9 100644
--- a/libs/incident/src/IncidentReportArgs.cpp
+++ b/libs/incident/src/IncidentReportArgs.cpp
@@ -81,6 +81,16 @@
return err;
}
+ err = out->writeString16(mReceiverPkg);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ err = out->writeString16(mReceiverCls);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
return NO_ERROR;
}
@@ -134,6 +144,9 @@
}
mDest = dest;
+ mReceiverPkg = in->readString16();
+ mReceiverCls = in->readString16();
+
return OK;
}
@@ -161,6 +174,18 @@
}
void
+IncidentReportArgs::setReceiverPkg(const string& pkg)
+{
+ mReceiverPkg = String16(pkg.c_str());
+}
+
+void
+IncidentReportArgs::setReceiverCls(const string& cls)
+{
+ mReceiverCls = String16(cls.c_str());
+}
+
+void
IncidentReportArgs::addHeader(const vector<uint8_t>& headerProto)
{
mHeaders.push_back(headerProto);
diff --git a/media/apex/java/android/media/MediaItem2.java b/media/apex/java/android/media/MediaItem2.java
index fc0f08e..ff0d43e 100644
--- a/media/apex/java/android/media/MediaItem2.java
+++ b/media/apex/java/android/media/MediaItem2.java
@@ -245,7 +245,7 @@
/**
* Builder for {@link MediaItem2}.
*/
- public static class Builder {
+ public static final class Builder {
@SuppressWarnings("WeakerAccess") /* synthetic access */
MediaMetadata mMetadata;
@SuppressWarnings("WeakerAccess") /* synthetic access */
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 46d4204..aed8e4e 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -630,7 +630,7 @@
* true to allow apps to capture the audio
* @return the same Builder instance
*/
- public Builder setAllowCapture(boolean allowCapture) {
+ public @NonNull Builder setAllowCapture(boolean allowCapture) {
if (allowCapture) {
mFlags &= ~FLAG_NO_CAPTURE;
} else {
diff --git a/media/java/android/media/AudioPlaybackCaptureConfiguration.java b/media/java/android/media/AudioPlaybackCaptureConfiguration.java
index 9a16aea..333cd2d 100644
--- a/media/java/android/media/AudioPlaybackCaptureConfiguration.java
+++ b/media/java/android/media/AudioPlaybackCaptureConfiguration.java
@@ -72,7 +72,7 @@
*
* @param audioFormat The format in which to capture the audio.
*/
- AudioMix createAudioMix(AudioFormat audioFormat) {
+ @NonNull AudioMix createAudioMix(@NonNull AudioFormat audioFormat) {
return new AudioMix.Builder(mAudioMixingRule)
.setFormat(audioFormat)
.setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK | AudioMix.ROUTE_FLAG_RENDER)
@@ -123,7 +123,7 @@
* @throws IllegalStateException if called in conjunction with
* {@link #excludeUsage(AudioAttributes)}.
*/
- public Builder addMatchingUsage(@NonNull AudioAttributes audioAttributes) {
+ public @NonNull Builder addMatchingUsage(@NonNull AudioAttributes audioAttributes) {
Preconditions.checkNotNull(audioAttributes);
Preconditions.checkState(
mUsageMatchType != MATCH_TYPE_EXCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES);
@@ -141,7 +141,7 @@
*
* @throws IllegalStateException if called in conjunction with {@link #excludeUid(int)}.
*/
- public Builder addMatchingUid(int uid) {
+ public @NonNull Builder addMatchingUid(int uid) {
Preconditions.checkState(
mUidMatchType != MATCH_TYPE_EXCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES);
mAudioMixingRuleBuilder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid);
@@ -158,7 +158,7 @@
* @throws IllegalStateException if called in conjunction with
* {@link #addMatchingUsage(AudioAttributes)}.
*/
- public Builder excludeUsage(@NonNull AudioAttributes audioAttributes) {
+ public @NonNull Builder excludeUsage(@NonNull AudioAttributes audioAttributes) {
Preconditions.checkNotNull(audioAttributes);
Preconditions.checkState(
mUsageMatchType != MATCH_TYPE_INCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES);
@@ -176,7 +176,7 @@
*
* @throws IllegalStateException if called in conjunction with {@link #addMatchingUid(int)}.
*/
- public Builder excludeUid(int uid) {
+ public @NonNull Builder excludeUid(int uid) {
Preconditions.checkState(
mUidMatchType != MATCH_TYPE_INCLUSIVE, ERROR_MESSAGE_MISMATCHED_RULES);
mAudioMixingRuleBuilder.excludeMixRule(AudioMixingRule.RULE_MATCH_UID, uid);
@@ -189,7 +189,7 @@
*
* @throws UnsupportedOperationException if the parameters set are incompatible.
*/
- public AudioPlaybackCaptureConfiguration build() {
+ public @NonNull AudioPlaybackCaptureConfiguration build() {
return new AudioPlaybackCaptureConfiguration(mAudioMixingRuleBuilder.build(),
mProjection);
}
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 3d5120f..28937a6 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -618,7 +618,7 @@
* @throws IllegalStateException if called in conjunction with {@link #setAudioSource(int)}.
* @throws NullPointerException if {@code config} is null.
*/
- public Builder setAudioPlaybackCaptureConfig(
+ public @NonNull Builder setAudioPlaybackCaptureConfig(
@NonNull AudioPlaybackCaptureConfiguration config) {
Preconditions.checkNotNull(
config, "Illegal null AudioPlaybackCaptureConfiguration argument");
@@ -647,7 +647,7 @@
return this;
}
- private AudioRecord buildAudioPlaybackCaptureRecord() {
+ private @NonNull AudioRecord buildAudioPlaybackCaptureRecord() {
AudioMix audioMix = mAudioPlaybackCaptureConfiguration.createAudioMix(mFormat);
MediaProjection projection = mAudioPlaybackCaptureConfiguration.getMediaProjection();
AudioPolicy audioPolicy = new AudioPolicy.Builder(/*context=*/ null)
diff --git a/packages/CaptivePortalLogin/Android.bp b/packages/CaptivePortalLogin/Android.bp
index a71977f..bc614e9 100644
--- a/packages/CaptivePortalLogin/Android.bp
+++ b/packages/CaptivePortalLogin/Android.bp
@@ -18,6 +18,7 @@
name: "CaptivePortalLogin",
srcs: ["src/**/*.java"],
sdk_version: "system_current",
+ min_sdk_version: "28",
certificate: "networkstack",
static_libs: [
"androidx.legacy_legacy-support-v4",
diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml
index a5f3b88fe..44e0a65 100644
--- a/packages/CaptivePortalLogin/AndroidManifest.xml
+++ b/packages/CaptivePortalLogin/AndroidManifest.xml
@@ -18,7 +18,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.captiveportallogin"
- android:versionCode="10"
+ android:versionCode="11"
android:versionName="Q-initial">
<uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
diff --git a/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/DynamicAndroidInstallationService.java b/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/DynamicAndroidInstallationService.java
index 7755cbc..719417e 100644
--- a/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/DynamicAndroidInstallationService.java
+++ b/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/DynamicAndroidInstallationService.java
@@ -114,6 +114,7 @@
private NotificationManager mNM;
private long mSystemSize;
+ private long mUserdataSize;
private long mInstalledSize;
private boolean mJustCancelledByUser;
@@ -220,9 +221,11 @@
String url = intent.getStringExtra(DynamicAndroidClient.KEY_SYSTEM_URL);
mSystemSize = intent.getLongExtra(DynamicAndroidClient.KEY_SYSTEM_SIZE, 0);
- long userdata = intent.getLongExtra(DynamicAndroidClient.KEY_USERDATA_SIZE, 0);
+ mUserdataSize = intent.getLongExtra(DynamicAndroidClient.KEY_USERDATA_SIZE, 0);
- mInstallTask = new InstallationAsyncTask(url, mSystemSize, userdata, mDynAndroid, this);
+ mInstallTask = new InstallationAsyncTask(
+ url, mSystemSize, mUserdataSize, mDynAndroid, this);
+
mInstallTask.execute();
// start fore ground
@@ -332,8 +335,8 @@
case STATUS_IN_PROGRESS:
builder.setContentText(getString(R.string.notification_install_inprogress));
- int max = (int) Math.max(mSystemSize >> 20, 1);
- int progress = (int) mInstalledSize >> 20;
+ int max = (int) Math.max((mSystemSize + mUserdataSize) >> 20, 1);
+ int progress = (int) (mInstalledSize >> 20);
builder.setProgress(max, progress, false);
diff --git a/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java b/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java
index 3c759e9..03fc773 100644
--- a/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java
+++ b/packages/DynamicAndroidInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java
@@ -16,6 +16,7 @@
package com.android.dynandroid;
+import android.gsi.GsiProgress;
import android.os.AsyncTask;
import android.os.DynamicAndroidManager;
import android.util.Log;
@@ -63,8 +64,6 @@
private final InstallStatusListener mListener;
private DynamicAndroidManager.Session mInstallationSession;
- private long mInstalledSize;
- private long mReportedInstalledSize;
private int mResult = NO_RESULT;
private InputStream mStream;
@@ -89,8 +88,40 @@
Log.d(TAG, "Start doInBackground(), URL: " + mUrl);
try {
- // call start in background
- mInstallationSession = mDynamicAndroid.startInstallation(mSystemSize, mUserdataSize);
+ long installedSize = 0;
+ long reportedInstalledSize = 0;
+
+ long minStepToReport = (mSystemSize + mUserdataSize) / 100;
+
+ // init input stream before calling startInstallation(), which takes 90 seconds.
+ initInputStream();
+
+ Thread thread = new Thread(() -> {
+ mInstallationSession =
+ mDynamicAndroid.startInstallation(mSystemSize, mUserdataSize);
+ });
+
+
+ thread.start();
+
+ while (thread.isAlive()) {
+ if (isCancelled()) {
+ boolean aborted = mDynamicAndroid.abort();
+ Log.d(TAG, "Called DynamicAndroidManager.abort(), result = " + aborted);
+ return RESULT_OK;
+ }
+
+ GsiProgress progress = mDynamicAndroid.getInstallationProgress();
+ installedSize = progress.bytes_processed;
+
+ if (installedSize > reportedInstalledSize + minStepToReport) {
+ publishProgress(installedSize);
+ reportedInstalledSize = installedSize;
+ }
+
+ Thread.sleep(10);
+ }
+
if (mInstallationSession == null) {
Log.e(TAG, "Failed to start installation with requested size: "
@@ -99,12 +130,11 @@
return RESULT_ERROR_IO;
}
- initInputStream();
+ installedSize = mUserdataSize;
byte[] bytes = new byte[READ_BUFFER_SIZE];
int numBytesRead;
- long minStepToReport = mSystemSize / 100;
Log.d(TAG, "Start installation loop");
while ((numBytesRead = mStream.read(bytes, 0, READ_BUFFER_SIZE)) != -1) {
@@ -119,11 +149,11 @@
throw new IOException("Failed write() to DynamicAndroid");
}
- mInstalledSize += numBytesRead;
+ installedSize += numBytesRead;
- if (mInstalledSize > mReportedInstalledSize + minStepToReport) {
- publishProgress(mInstalledSize);
- mReportedInstalledSize = mInstalledSize;
+ if (installedSize > reportedInstalledSize + minStepToReport) {
+ publishProgress(installedSize);
+ reportedInstalledSize = installedSize;
}
}
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
index 5f1f26d..f210840 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -36,6 +36,7 @@
android_app {
name: "NetworkStack",
sdk_version: "system_current",
+ min_sdk_version: "28",
certificate: "networkstack",
privileged: true,
static_libs: [
diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml
index 740c573..003f1e5 100644
--- a/packages/NetworkStack/AndroidManifest.xml
+++ b/packages/NetworkStack/AndroidManifest.xml
@@ -19,7 +19,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.networkstack"
android:sharedUserId="android.uid.networkstack"
- android:versionCode="10"
+ android:versionCode="11"
android:versionName="Q-initial">
<uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
<uses-permission android:name="android.permission.INTERNET" />
diff --git a/packages/NetworkStackPermissionStub/Android.bp b/packages/NetworkStackPermissionStub/Android.bp
index dd70cf5..8cee92e 100644
--- a/packages/NetworkStackPermissionStub/Android.bp
+++ b/packages/NetworkStackPermissionStub/Android.bp
@@ -21,6 +21,7 @@
// a classes.dex.
srcs: ["src/**/*.java"],
platform_apis: true,
+ min_sdk_version: "28",
certificate: "networkstack",
privileged: true,
manifest: "AndroidManifest.xml",
diff --git a/packages/NetworkStackPermissionStub/AndroidManifest.xml b/packages/NetworkStackPermissionStub/AndroidManifest.xml
index 62bf3de..e83f050 100644
--- a/packages/NetworkStackPermissionStub/AndroidManifest.xml
+++ b/packages/NetworkStackPermissionStub/AndroidManifest.xml
@@ -19,7 +19,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.networkstack.permissionstub"
android:sharedUserId="android.uid.networkstack"
- android:versionCode="10"
+ android:versionCode="11"
android:versionName="Q-initial">
<uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
<!--
diff --git a/packages/SystemUI/res/drawable/ic_volume_remote.xml b/packages/SettingsLib/res/drawable/ic_volume_remote.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/ic_volume_remote.xml
rename to packages/SettingsLib/res/drawable/ic_volume_remote.xml
diff --git a/packages/SystemUI/res/drawable/ic_volume_remote_mute.xml b/packages/SettingsLib/res/drawable/ic_volume_remote_mute.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/ic_volume_remote_mute.xml
rename to packages/SettingsLib/res/drawable/ic_volume_remote_mute.xml
diff --git a/core/java/android/service/textclassifier/ITextLinksCallback.aidl b/packages/SettingsLib/src/com/android/settingslib/volume/D.java
similarity index 65%
rename from core/java/android/service/textclassifier/ITextLinksCallback.aidl
rename to packages/SettingsLib/src/com/android/settingslib/volume/D.java
index a9e0dde..7e0654d 100644
--- a/core/java/android/service/textclassifier/ITextLinksCallback.aidl
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/D.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open 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,15 +14,10 @@
* limitations under the License.
*/
-package android.service.textclassifier;
+package com.android.settingslib.volume;
-import android.view.textclassifier.TextLinks;
+import android.util.Log;
-/**
- * Callback for a TextLinks request.
- * @hide
- */
-oneway interface ITextLinksCallback {
- void onSuccess(in TextLinks links);
- void onFailure();
-}
\ No newline at end of file
+class D {
+ public static boolean BUG = Log.isLoggable("volume", Log.DEBUG);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
rename to packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
index 8b00eee..4ed1154 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open 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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.volume;
+package com.android.settingslib.volume;
import android.app.PendingIntent;
import android.content.Context;
@@ -27,7 +27,6 @@
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaController.PlaybackInfo;
-import android.media.session.MediaSession;
import android.media.session.MediaSession.QueueItem;
import android.media.session.MediaSession.Token;
import android.media.session.MediaSessionManager;
@@ -41,7 +40,6 @@
import android.util.Log;
import java.io.PrintWriter;
-import java.io.StringWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -73,16 +71,24 @@
mCallbacks = callbacks;
}
+ /**
+ * Dump to {@code writer}
+ */
public void dump(PrintWriter writer) {
writer.println(getClass().getSimpleName() + " state:");
- writer.print(" mInit: "); writer.println(mInit);
- writer.print(" mRecords.size: "); writer.println(mRecords.size());
+ writer.print(" mInit: ");
+ writer.println(mInit);
+ writer.print(" mRecords.size: ");
+ writer.println(mRecords.size());
int i = 0;
for (MediaControllerRecord r : mRecords.values()) {
dump(++i, writer, r.controller);
}
}
+ /**
+ * init MediaSessions
+ */
public void init() {
if (D.BUG) Log.d(TAG, "init");
// will throw if no permission
@@ -97,12 +103,18 @@
mHandler.sendEmptyMessage(H.UPDATE_SESSIONS);
}
+ /**
+ * Destroy MediaSessions
+ */
public void destroy() {
if (D.BUG) Log.d(TAG, "destroy");
mInit = false;
mMgr.removeOnActiveSessionsChangedListener(mSessionsListener);
}
+ /**
+ * Set volume {@code level} to remote media {@code token}
+ */
public void setVolume(Token token, int level) {
final MediaControllerRecord r = mRecords.get(token);
if (r == null) {
@@ -113,15 +125,17 @@
r.controller.setVolumeTo(level, 0);
}
- private void onRemoteVolumeChangedH(MediaSession.Token sessionToken, int flags) {
+ private void onRemoteVolumeChangedH(Token sessionToken, int flags) {
final MediaController controller = new MediaController(mContext, sessionToken);
- if (D.BUG) Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " "
- + Util.audioManagerFlagsToString(flags));
+ if (D.BUG) {
+ Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " "
+ + Util.audioManagerFlagsToString(flags));
+ }
final Token token = controller.getSessionToken();
mCallbacks.onRemoteVolumeChanged(token, flags);
}
- private void onUpdateRemoteControllerH(MediaSession.Token sessionToken) {
+ private void onUpdateRemoteControllerH(Token sessionToken) {
final MediaController controller =
sessionToken != null ? new MediaController(mContext, sessionToken) : null;
final String pkg = controller != null ? controller.getPackageName() : null;
@@ -191,7 +205,8 @@
if (appLabel.length() > 0) {
return appLabel;
}
- } catch (NameNotFoundException e) { }
+ } catch (NameNotFoundException e) {
+ }
return pkg;
}
@@ -240,29 +255,11 @@
}
}
- public static void dumpMediaSessions(Context context) {
- final MediaSessionManager mgr = (MediaSessionManager) context
- .getSystemService(Context.MEDIA_SESSION_SERVICE);
- try {
- final List<MediaController> controllers = mgr.getActiveSessions(null);
- final int N = controllers.size();
- if (D.BUG) Log.d(TAG, N + " controllers");
- for (int i = 0; i < N; i++) {
- final StringWriter sw = new StringWriter();
- final PrintWriter pw = new PrintWriter(sw, true);
- dump(i + 1, pw, controllers.get(i));
- if (D.BUG) Log.d(TAG, sw.toString());
- }
- } catch (SecurityException e) {
- Log.w(TAG, "Not allowed to get sessions", e);
- }
- }
-
private final class MediaControllerRecord extends MediaController.Callback {
- private final MediaController controller;
+ public final MediaController controller;
- private boolean sentRemote;
- private String name;
+ public boolean sentRemote;
+ public String name;
private MediaControllerRecord(MediaController controller) {
this.controller = controller;
@@ -274,8 +271,10 @@
@Override
public void onAudioInfoChanged(PlaybackInfo info) {
- if (D.BUG) Log.d(TAG, cb("onAudioInfoChanged") + Util.playbackInfoToString(info)
- + " sentRemote=" + sentRemote);
+ if (D.BUG) {
+ Log.d(TAG, cb("onAudioInfoChanged") + Util.playbackInfoToString(info)
+ + " sentRemote=" + sentRemote);
+ }
final boolean remote = isRemote(info);
if (!remote && sentRemote) {
mCallbacks.onRemoteRemoved(controller.getSessionToken());
@@ -324,22 +323,22 @@
private final OnActiveSessionsChangedListener mSessionsListener =
new OnActiveSessionsChangedListener() {
- @Override
- public void onActiveSessionsChanged(List<MediaController> controllers) {
- onActiveSessionsUpdatedH(controllers);
- }
- };
+ @Override
+ public void onActiveSessionsChanged(List<MediaController> controllers) {
+ onActiveSessionsUpdatedH(controllers);
+ }
+ };
private final IRemoteVolumeController mRvc = new IRemoteVolumeController.Stub() {
@Override
- public void remoteVolumeChanged(MediaSession.Token sessionToken, int flags)
+ public void remoteVolumeChanged(Token sessionToken, int flags)
throws RemoteException {
mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0,
sessionToken).sendToTarget();
}
@Override
- public void updateRemoteController(final MediaSession.Token sessionToken)
+ public void updateRemoteController(final Token sessionToken)
throws RemoteException {
mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, sessionToken).sendToTarget();
}
@@ -361,18 +360,32 @@
onActiveSessionsUpdatedH(mMgr.getActiveSessions(null));
break;
case REMOTE_VOLUME_CHANGED:
- onRemoteVolumeChangedH((MediaSession.Token) msg.obj, msg.arg1);
+ onRemoteVolumeChangedH((Token) msg.obj, msg.arg1);
break;
case UPDATE_REMOTE_CONTROLLER:
- onUpdateRemoteControllerH((MediaSession.Token) msg.obj);
+ onUpdateRemoteControllerH((Token) msg.obj);
break;
}
}
}
+ /**
+ * Callback for remote media sessions
+ */
public interface Callbacks {
+ /**
+ * Invoked when remote media session is updated
+ */
void onRemoteUpdate(Token token, String name, PlaybackInfo pi);
+
+ /**
+ * Invoked when remote media session is removed
+ */
void onRemoteRemoved(Token t);
+
+ /**
+ * Invoked when remote volume is changed
+ */
void onRemoteVolumeChanged(Token token, int flags);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/Util.java b/packages/SettingsLib/src/com/android/settingslib/volume/Util.java
new file mode 100644
index 0000000..4e770ae
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/Util.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.volume;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.MediaMetadata;
+import android.media.VolumeProvider;
+import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.PlaybackState;
+import android.telephony.TelephonyManager;
+import android.widget.TextView;
+
+import java.util.Objects;
+
+/**
+ * Static helpers for the volume dialog.
+ */
+public class Util {
+
+ private static final int[] AUDIO_MANAGER_FLAGS = new int[]{
+ AudioManager.FLAG_SHOW_UI,
+ AudioManager.FLAG_VIBRATE,
+ AudioManager.FLAG_PLAY_SOUND,
+ AudioManager.FLAG_ALLOW_RINGER_MODES,
+ AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE,
+ AudioManager.FLAG_SHOW_VIBRATE_HINT,
+ AudioManager.FLAG_SHOW_SILENT_HINT,
+ AudioManager.FLAG_FROM_KEY,
+ AudioManager.FLAG_SHOW_UI_WARNINGS,
+ };
+
+ private static final String[] AUDIO_MANAGER_FLAG_NAMES = new String[]{
+ "SHOW_UI",
+ "VIBRATE",
+ "PLAY_SOUND",
+ "ALLOW_RINGER_MODES",
+ "REMOVE_SOUND_AND_VIBRATE",
+ "SHOW_VIBRATE_HINT",
+ "SHOW_SILENT_HINT",
+ "FROM_KEY",
+ "SHOW_UI_WARNINGS",
+ };
+
+ /**
+ * Extract log tag from {@code c}
+ */
+ public static String logTag(Class<?> c) {
+ final String tag = "vol." + c.getSimpleName();
+ return tag.length() < 23 ? tag : tag.substring(0, 23);
+ }
+
+ /**
+ * Convert media metadata to string
+ */
+ public static String mediaMetadataToString(MediaMetadata metadata) {
+ if (metadata == null) return null;
+ return metadata.getDescription().toString();
+ }
+
+ /**
+ * Convert playback info to string
+ */
+ public static String playbackInfoToString(PlaybackInfo info) {
+ if (info == null) return null;
+ final String type = playbackInfoTypeToString(info.getPlaybackType());
+ final String vc = volumeProviderControlToString(info.getVolumeControl());
+ return String.format("PlaybackInfo[vol=%s,max=%s,type=%s,vc=%s],atts=%s",
+ info.getCurrentVolume(), info.getMaxVolume(), type, vc, info.getAudioAttributes());
+ }
+
+ /**
+ * Convert type of playback info to string
+ */
+ public static String playbackInfoTypeToString(int type) {
+ switch (type) {
+ case PlaybackInfo.PLAYBACK_TYPE_LOCAL:
+ return "LOCAL";
+ case PlaybackInfo.PLAYBACK_TYPE_REMOTE:
+ return "REMOTE";
+ default:
+ return "UNKNOWN_" + type;
+ }
+ }
+
+ /**
+ * Convert state of playback info to string
+ */
+ public static String playbackStateStateToString(int state) {
+ switch (state) {
+ case PlaybackState.STATE_NONE:
+ return "STATE_NONE";
+ case PlaybackState.STATE_STOPPED:
+ return "STATE_STOPPED";
+ case PlaybackState.STATE_PAUSED:
+ return "STATE_PAUSED";
+ case PlaybackState.STATE_PLAYING:
+ return "STATE_PLAYING";
+ default:
+ return "UNKNOWN_" + state;
+ }
+ }
+
+ /**
+ * Convert volume provider control to string
+ */
+ public static String volumeProviderControlToString(int control) {
+ switch (control) {
+ case VolumeProvider.VOLUME_CONTROL_ABSOLUTE:
+ return "VOLUME_CONTROL_ABSOLUTE";
+ case VolumeProvider.VOLUME_CONTROL_FIXED:
+ return "VOLUME_CONTROL_FIXED";
+ case VolumeProvider.VOLUME_CONTROL_RELATIVE:
+ return "VOLUME_CONTROL_RELATIVE";
+ default:
+ return "VOLUME_CONTROL_UNKNOWN_" + control;
+ }
+ }
+
+ /**
+ * Convert {@link PlaybackState} to string
+ */
+ public static String playbackStateToString(PlaybackState playbackState) {
+ if (playbackState == null) return null;
+ return playbackStateStateToString(playbackState.getState()) + " " + playbackState;
+ }
+
+ /**
+ * Convert audio manager flags to string
+ */
+ public static String audioManagerFlagsToString(int value) {
+ return bitFieldToString(value, AUDIO_MANAGER_FLAGS, AUDIO_MANAGER_FLAG_NAMES);
+ }
+
+ protected static String bitFieldToString(int value, int[] values, String[] names) {
+ if (value == 0) return "";
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < values.length; i++) {
+ if ((value & values[i]) != 0) {
+ if (sb.length() > 0) sb.append(',');
+ sb.append(names[i]);
+ }
+ value &= ~values[i];
+ }
+ if (value != 0) {
+ if (sb.length() > 0) sb.append(',');
+ sb.append("UNKNOWN_").append(value);
+ }
+ return sb.toString();
+ }
+
+ private static CharSequence emptyToNull(CharSequence str) {
+ return str == null || str.length() == 0 ? null : str;
+ }
+
+ /**
+ * Set text for specific {@link TextView}
+ */
+ public static boolean setText(TextView tv, CharSequence text) {
+ if (Objects.equals(emptyToNull(tv.getText()), emptyToNull(text))) return false;
+ tv.setText(text);
+ return true;
+ }
+
+ /**
+ * Return {@code true} if it is voice capable
+ */
+ public static boolean isVoiceCapable(Context context) {
+ final TelephonyManager telephony =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ return telephony != null && telephony.isVoiceCapable();
+ }
+}
diff --git a/packages/SystemUI/res/drawable/qs_detail_background.xml b/packages/SystemUI/res/drawable/qs_detail_background.xml
index 84c793f..672abf1 100644
--- a/packages/SystemUI/res/drawable/qs_detail_background.xml
+++ b/packages/SystemUI/res/drawable/qs_detail_background.xml
@@ -14,6 +14,20 @@
limitations under the License.
-->
<transition xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:drawable="@color/qs_detail_transition" />
- <item android:drawable="?android:attr/colorPrimary" />
+ <item>
+ <inset>
+ <shape>
+ <solid android:color="@color/qs_detail_transition"/>
+ <corners android:radius="?android:attr/dialogCornerRadius" />
+ </shape>
+ </inset>
+ </item>
+ <item>
+ <inset>
+ <shape>
+ <solid android:color="?android:attr/colorPrimary"/>
+ <corners android:radius="?android:attr/dialogCornerRadius" />
+ </shape>
+ </inset>
+ </item>
</transition>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/global_actions_grid.xml b/packages/SystemUI/res/layout-land/global_actions_grid.xml
index 480f523..235d0fc 100644
--- a/packages/SystemUI/res/layout-land/global_actions_grid.xml
+++ b/packages/SystemUI/res/layout-land/global_actions_grid.xml
@@ -10,14 +10,12 @@
android:gravity="top|right"
android:clipChildren="false"
>
-
<LinearLayout
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:gravity="top|right"
android:padding="0dp"
android:orientation="vertical"
- android:layoutDirection="ltr"
android:layout_marginRight="@dimen/global_actions_grid_container_bottom_margin"
>
<!-- Grid of action items -->
@@ -26,7 +24,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
- android:layoutDirection="ltr"
android:layout_marginTop="@dimen/global_actions_grid_side_margin"
android:translationZ="@dimen/global_actions_translate"
android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
@@ -39,25 +36,21 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
- android:layoutDirection="ltr"
- android:orientation="horizontal"
+ android:layoutDirection="locale"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
- android:layoutDirection="ltr"
- android:orientation="horizontal"
+ android:layoutDirection="locale"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
- android:layoutDirection="ltr"
- android:orientation="horizontal"
+ android:layoutDirection="locale"
/>
</com.android.systemui.globalactions.ListGridLayout>
-
<!-- For separated items-->
<LinearLayout
android:id="@+id/separated_button"
diff --git a/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml b/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml
index 4f86826..e028214 100644
--- a/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml
+++ b/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml
@@ -10,7 +10,6 @@
android:gravity="top|left"
android:clipChildren="false"
>
-
<LinearLayout
android:layout_height="match_parent"
android:layout_width="wrap_content"
@@ -37,11 +36,11 @@
android:gravity="center"
android:translationZ="@dimen/global_actions_translate"
/>
-
<!-- Grid of action items -->
<com.android.systemui.globalactions.ListGridLayout
android:id="@android:id/list"
android:layout_gravity="bottom|left"
+ android:gravity="right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
@@ -54,28 +53,22 @@
android:background="?android:attr/colorBackgroundFloating"
>
<LinearLayout
- android:layout_gravity="bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
- android:layoutDirection="rtl"
- android:orientation="horizontal"
+ android:layoutDirection="locale"
/>
<LinearLayout
- android:layout_gravity="bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
- android:layoutDirection="rtl"
- android:orientation="horizontal"
+ android:layoutDirection="locale"
/>
<LinearLayout
- android:layout_gravity="bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
- android:layoutDirection="rtl"
- android:orientation="horizontal"
+ android:layoutDirection="locale"
/>
</com.android.systemui.globalactions.ListGridLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid.xml b/packages/SystemUI/res/layout/global_actions_grid.xml
index 729e96e..a620a8e 100644
--- a/packages/SystemUI/res/layout/global_actions_grid.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid.xml
@@ -10,7 +10,6 @@
android:gravity="bottom|center"
android:clipChildren="false"
>
-
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
@@ -35,14 +34,13 @@
android:gravity="center"
android:translationZ="@dimen/global_actions_translate"
/>
-
<!-- Grid of action items -->
<com.android.systemui.globalactions.ListGridLayout
android:id="@android:id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layoutDirection="rtl"
+ android:orientation="vertical"
+ android:gravity="right"
android:layout_marginRight="@dimen/global_actions_grid_side_margin"
android:translationZ="@dimen/global_actions_translate"
android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
@@ -55,19 +53,19 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
- android:orientation="vertical"
+ android:layoutDirection="locale"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
- android:orientation="vertical"
+ android:layoutDirection="locale"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
- android:orientation="vertical"
+ android:layoutDirection="locale"
/>
</com.android.systemui.globalactions.ListGridLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 863c1cc..bd9d3fb 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -44,7 +44,7 @@
android:id="@+id/pkgname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Info"
android:layout_marginStart="3dp"
android:layout_marginEnd="2dp"
android:singleLine="true"
@@ -54,7 +54,7 @@
android:id="@+id/pkg_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Info"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:text="@*android:string/notification_header_divider_symbol"
@@ -64,7 +64,7 @@
android:id="@+id/delegate_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Info"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:ellipsize="end"
@@ -129,7 +129,7 @@
android:id="@+id/group_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="@*android:style/TextAppearance.Material.Notification.Title"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:ellipsize="end"
@@ -139,7 +139,7 @@
android:id="@+id/pkg_group_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="@*android:style/TextAppearance.Material.Notification.Title"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:text="@*android:string/notification_header_divider_symbol"
@@ -151,7 +151,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
- style="@android:style/TextAppearance.Material.Notification.Title"
+ style="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
android:layout_toEndOf="@id/pkg_group_divider"/>
</RelativeLayout>
<!-- Question prompt -->
@@ -159,7 +159,7 @@
android:id="@+id/block_prompt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Notification" />
+ style="@*android:style/TextAppearance.DeviceDefault.Notification" />
</LinearLayout>
<!-- Settings and Done buttons -->
diff --git a/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl b/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl
index 586cdf3..716e127 100644
--- a/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl
+++ b/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl
@@ -1,27 +1,76 @@
precision mediump float;
+// The actual wallpaper texture.
uniform sampler2D uTexture;
-uniform float uCenterReveal;
+
+// The 85th percenile for the luminance histogram of the image (a value between 0 and 1).
+// This value represents the point in histogram that includes 85% of the pixels of the image.
+uniform float uPer85;
+
+// Reveal is the animation value that goes from 1 (the image is hidden) to 0 (the image is visible).
uniform float uReveal;
+
+// The opacity of locked screen (constant value).
uniform float uAod2Opacity;
varying vec2 vTextureCoordinates;
+/*
+ * Calculates the relative luminance of the pixel.
+ */
vec3 luminosity(vec3 color) {
float lum = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
return vec3(lum);
}
vec4 transform(vec3 diffuse) {
- // TODO: Add well comments here, tracking on b/123615467.
+ // Getting the luminance for this pixel
vec3 lum = luminosity(diffuse);
- diffuse = mix(diffuse, lum, smoothstep(0., uCenterReveal, uReveal));
- float val = mix(uReveal, uCenterReveal, step(uCenterReveal, uReveal));
- diffuse = smoothstep(val, 1.0, diffuse);
- diffuse *= uAod2Opacity * (1. - smoothstep(uCenterReveal, 1., uReveal));
+
+ /*
+ * while the reveal > per85, it shows the luminance image (B&W image)
+ * then when moving passed that value, the image gets colored.
+ */
+ float trans = smoothstep(0., uPer85, uReveal);
+ diffuse = mix(diffuse, lum, trans);
+
+ // 'lower' value represents the capped 'reveal' value to the range [0, per85]
+ float selector = step(uPer85, uReveal);
+ float lower = mix(uReveal, uPer85, selector);
+
+ /*
+ * Remaps image:
+ * - from reveal=1 to reveal=per85 => lower=per85, diffuse=luminance
+ * That means that remaps black and white image pixel
+ * from a possible values of [0,1] to [per85, 1] (if the pixel is darker than per85,
+ * it's gonna be black, if it's between per85 and 1, it's gonna be gray
+ * and if it's 1 it's gonna be white).
+ * - from reveal=per85 to reveal=0 => lower=reveal, 'diffuse' changes from luminance to color
+ * That means that remaps each image pixel color (rgb)
+ * from a possible values of [0,1] to [lower, 1] (if the pixel color is darker than 'lower',
+ * it's gonna be 0, if it's between 'lower' and 1, it's gonna be remap to a value
+ * between 0 and 1 and if it's 1 it's gonna be 1).
+ * - if reveal=0 => lower=0, diffuse=color image
+ * The image is shown as it is, colored.
+ */
+ vec3 remaps = smoothstep(lower, 1., diffuse);
+
+ // Interpolate between diffuse and remaps using reveal to avoid over saturation.
+ diffuse = mix(diffuse, remaps, uReveal);
+
+ /*
+ * Fades in the pixel value:
+ * - if reveal=1 => fadeInOpacity=0
+ * - from reveal=1 to reveal=per85 => 0<=fadeInOpacity<=1
+ * - if reveal>per85 => fadeInOpacity=1
+ */
+ float fadeInOpacity = 1. - smoothstep(uPer85, 1., uReveal);
+ diffuse *= uAod2Opacity * fadeInOpacity;
+
return vec4(diffuse.r, diffuse.g, diffuse.b, 1.);
}
void main() {
+ // gets the pixel value of the wallpaper for this uv coordinates on screen.
vec4 fragColor = texture2D(uTexture, vTextureCoordinates);
gl_FragColor = transform(fragColor.rgb);
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 563b007..d40fa66 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -1,6 +1,7 @@
package com.android.keyguard;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.app.WallpaperManager;
@@ -31,6 +32,9 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.TimeZone;
/**
@@ -148,6 +152,7 @@
Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
Dependency.get(SysuiColorExtractor.class)
.removeOnColorsChangedListener(mColorsListener);
+ setClockPlugin(null);
}
private void setClockPlugin(ClockPlugin plugin) {
@@ -332,6 +337,19 @@
return mStateListener;
}
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("KeyguardClockSwitch:");
+ pw.println(" mClockPlugin: " + mClockPlugin);
+ pw.println(" mClockView: " + mClockView);
+ pw.println(" mSmallClockFrame: " + mSmallClockFrame);
+ pw.println(" mBigClockContainer: " + mBigClockContainer);
+ pw.println(" mKeyguardStatusArea: " + mKeyguardStatusArea);
+ pw.println(" mDarkAmount: " + mDarkAmount);
+ pw.println(" mShowingHeader: " + mShowingHeader);
+ pw.println(" mSupportsDarkText: " + mSupportsDarkText);
+ pw.println(" mColorPalette: " + Arrays.toString(mColorPalette));
+ }
+
/**
* Special layout transition that scales the clock view as its bounds change, to make it look
* like the text is shrinking.
@@ -371,11 +389,23 @@
boundsAnimator.addUpdateListener(animation -> {
float scale = MathUtils.lerp(startScale, 1f /* stop */,
animation.getAnimatedFraction());
- mClockView.setPivotX(mClockView.getWidth() / 2);
+ mClockView.setPivotX(mClockView.getWidth() / 2f);
mClockView.setPivotY(0);
mClockView.setScaleX(scale);
mClockView.setScaleY(scale);
});
+ boundsAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mClockView.setScaleX(1f);
+ mClockView.setScaleY(1f);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ onAnimationEnd(animator);
+ }
+ });
}
return animator;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 2040a76..8ebe1ae 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -64,6 +64,8 @@
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -174,6 +176,7 @@
if (mContentChangeListener != null) {
mContentChangeListener.run();
}
+ Trace.endSection();
return;
}
@@ -375,6 +378,17 @@
Trace.endSection();
}
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("KeyguardSliceView:");
+ pw.println(" mClickActions: " + mClickActions);
+ pw.println(" mTitle: " + (mTitle == null ? "null" : mTitle.getVisibility() == VISIBLE));
+ pw.println(" mRow: " + (mRow == null ? "null" : mRow.getVisibility() == VISIBLE));
+ pw.println(" mTextColor: " + Integer.toHexString(mTextColor));
+ pw.println(" mDarkAmount: " + mDarkAmount);
+ pw.println(" mSlice: " + mSlice);
+ pw.println(" mHasHeader: " + mHasHeader);
+ }
+
public static class Row extends LinearLayout {
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 17546c5..808e264 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -41,6 +41,8 @@
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.Locale;
import java.util.TimeZone;
@@ -289,6 +291,24 @@
return false;
}
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("KeyguardStatusView:");
+ pw.println(" mOwnerInfo: " + (mOwnerInfo == null
+ ? "null" : mOwnerInfo.getVisibility() == VISIBLE));
+ pw.println(" mPulsing: " + mPulsing);
+ pw.println(" mDarkAmount: " + mDarkAmount);
+ pw.println(" mTextColor: " + Integer.toHexString(mTextColor));
+ if (mLogoutView != null) {
+ pw.println(" logout visible: " + (mLogoutView.getVisibility() == VISIBLE));
+ }
+ if (mClockView != null) {
+ mClockView.dump(fd, pw, args);
+ }
+ if (mKeyguardSlice != null) {
+ mKeyguardSlice.dump(fd, pw, args);
+ }
+ }
+
// DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
// This is an optimization to ensure we only recompute the patterns when the inputs change.
private static final class Patterns {
diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
index f5451e9..26c5ef9 100644
--- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
@@ -90,7 +90,7 @@
}
@Override
- public ViewGroup getParentView(boolean separated, int index, boolean reverse) {
+ public ViewGroup getParentView(boolean separated, int index, int rotation) {
if (separated) {
return getSeparatedView();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
index 8c49d56..8259da6 100644
--- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
@@ -57,12 +57,12 @@
* @param separated Whether or not this index refers to a position in the separated or list
* container.
* @param index The index of the item within the container.
- * @param reverse If the MultiListLayout contains sub-lists within the list container, reverse
- * the order that they are filled.
+ * @param rotation Specifies the rotation of the device, which is used in some cases to
+ * determine child ordering.
* @return The parent ViewGroup which will be used to contain the specified item
* after it has been added to the layout.
*/
- public abstract ViewGroup getParentView(boolean separated, int index, boolean reverse);
+ public abstract ViewGroup getParentView(boolean separated, int index, int rotation);
/**
* Sets the divided view, which may have a differently-colored background.
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 3fa6035..c16c91b 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -96,6 +96,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
/**
* Helper to show the global actions dialog. Each item is an {@link Action} that
@@ -1546,33 +1547,40 @@
ArrayList<Action> listActions = mAdapter.getListActions(mShouldDisplaySeparatedButton);
mGlobalActionsLayout.setExpectedListItemCount(listActions.size());
mGlobalActionsLayout.setExpectedSeparatedItemCount(separatedActions.size());
+ int rotation = RotationUtils.getRotation(mContext);
+
+ boolean reverse = false; // should we add items to parents in the reverse order?
+ if (isGridEnabled(mContext)) {
+ if (rotation == RotationUtils.ROTATION_NONE
+ || rotation == RotationUtils.ROTATION_SEASCAPE) {
+ reverse = !reverse; // if we're in portrait or seascape, reverse items
+ }
+ if (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
+ == View.LAYOUT_DIRECTION_RTL) {
+ reverse = !reverse; // if we're in an RTL language, reverse items (again)
+ }
+ }
for (int i = 0; i < mAdapter.getCount(); i++) {
Action action = mAdapter.getItem(i);
int separatedIndex = separatedActions.indexOf(action);
ViewGroup parent;
if (separatedIndex != -1) {
- parent = mGlobalActionsLayout.getParentView(true, separatedIndex, false);
+ parent = mGlobalActionsLayout.getParentView(true, separatedIndex, rotation);
} else {
- boolean reverse = false;
-
- // If we're using the grid layout and we're in seascape, reverse the order
- // of sublists to make sure they render in the correct positions,
- // since we can't reverse vertical LinearLayouts through the layout xml.
-
- if (isGridEnabled(mContext) && RotationUtils.getRotation(mContext)
- == RotationUtils.ROTATION_SEASCAPE) {
- reverse = true;
- }
int listIndex = listActions.indexOf(action);
- parent = mGlobalActionsLayout.getParentView(false, listIndex, reverse);
+ parent = mGlobalActionsLayout.getParentView(false, listIndex, rotation);
}
View v = mAdapter.getView(i, null, parent);
final int pos = i;
v.setOnClickListener(view -> mClickListener.onClick(this, pos));
v.setOnLongClickListener(view ->
mLongClickListener.onItemLongClick(null, v, pos, 0));
- parent.addView(v);
+ if (reverse) {
+ parent.addView(v, 0); // reverse order of items
+ } else {
+ parent.addView(v);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
index 1d04277..036d8ca 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
@@ -23,6 +23,7 @@
import com.android.systemui.HardwareBgDrawable;
import com.android.systemui.MultiListLayout;
+import com.android.systemui.util.leak.RotationUtils;
/**
* Grid-based implementation of the button layout created by the global actions dialog.
@@ -83,11 +84,18 @@
}
@Override
- public ViewGroup getParentView(boolean separated, int index, boolean reverseOrder) {
+ public ViewGroup getParentView(boolean separated, int index, int rotation) {
if (separated) {
return getSeparatedView();
} else {
- return getListView().getParentView(index, reverseOrder);
+ switch (rotation) {
+ case RotationUtils.ROTATION_LANDSCAPE:
+ return getListView().getParentView(index, false, true);
+ case RotationUtils.ROTATION_SEASCAPE:
+ return getListView().getParentView(index, true, true);
+ default:
+ return getListView().getParentView(index, false, false);
+ }
}
}
@@ -96,6 +104,6 @@
*/
@Override
public void setDivisionView(View v) {
-
+ // do nothing
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
index d5dcd74..4df1c5a 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
@@ -62,11 +62,11 @@
/**
* Get the parent view associated with the item which should be placed at the given position.
*/
- public ViewGroup getParentView(int index, boolean reverseSublists) {
+ public ViewGroup getParentView(int index, boolean reverseSublists, boolean swapRowsAndColumns) {
if (mRows == 0) {
return null;
}
- int column = getParentViewIndex(index, reverseSublists);
+ int column = getParentViewIndex(index, reverseSublists, swapRowsAndColumns);
return (ViewGroup) getChildAt(column);
}
@@ -74,13 +74,18 @@
return getChildCount() - (index + 1);
}
- private int getParentViewIndex(int index, boolean reverseSublists) {
- int column = (int) Math.floor(index / mRows);
- int columnCount = getChildCount();
- if (reverseSublists) {
- column = reverseSublistIndex(column);
+ private int getParentViewIndex(int index, boolean reverseSublists, boolean swapRowsAndColumns) {
+ int sublistIndex;
+ ViewGroup row;
+ if (swapRowsAndColumns) {
+ sublistIndex = (int) Math.floor(index / mRows);
+ } else {
+ sublistIndex = index % mRows;
}
- return column;
+ if (reverseSublists) {
+ sublistIndex = reverseSublistIndex(sublistIndex);
+ }
+ return sublistIndex;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java
index 19d85b1..a313336 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java
@@ -50,7 +50,7 @@
static final String A_POSITION = "aPosition";
static final String A_TEXTURE_COORDINATES = "aTextureCoordinates";
- static final String U_CENTER_REVEAL = "uCenterReveal";
+ static final String U_PER85 = "uPer85";
static final String U_REVEAL = "uReveal";
static final String U_AOD2OPACITY = "uAod2Opacity";
static final String U_TEXTURE = "uTexture";
@@ -87,7 +87,7 @@
private int mAttrPosition;
private int mAttrTextureCoordinates;
private int mUniAod2Opacity;
- private int mUniCenterReveal;
+ private int mUniPer85;
private int mUniReveal;
private int mUniTexture;
private int mTextureId;
@@ -131,7 +131,7 @@
private void setupUniforms() {
mUniAod2Opacity = mProgram.getUniformHandle(U_AOD2OPACITY);
- mUniCenterReveal = mProgram.getUniformHandle(U_CENTER_REVEAL);
+ mUniPer85 = mProgram.getUniformHandle(U_PER85);
mUniReveal = mProgram.getUniformHandle(U_REVEAL);
mUniTexture = mProgram.getUniformHandle(U_TEXTURE);
}
@@ -144,8 +144,8 @@
return mAttrTextureCoordinates;
case U_AOD2OPACITY:
return mUniAod2Opacity;
- case U_CENTER_REVEAL:
- return mUniCenterReveal;
+ case U_PER85:
+ return mUniPer85;
case U_REVEAL:
return mUniReveal;
case U_TEXTURE:
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index 991b116..7295008 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -93,13 +93,13 @@
@Override
public void onDrawFrame(GL10 gl) {
- float threshold = mImageProcessHelper.getPercentile85();
+ float per85 = mImageProcessHelper.getPercentile85();
float reveal = mImageRevealHelper.getReveal();
glClear(GL_COLOR_BUFFER_BIT);
glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_AOD2OPACITY), 1);
- glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_CENTER_REVEAL), threshold);
+ glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_PER85), per85);
glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_REVEAL), reveal);
mWallpaper.useTexture();
diff --git a/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt b/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt
new file mode 100644
index 0000000..d7a2d9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt
@@ -0,0 +1,55 @@
+package com.android.systemui.power
+
+import com.android.systemui.power.PowerUI.NO_ESTIMATE_AVAILABLE
+
+/**
+ * A simple data class to snapshot battery state when a particular check for the
+ * low battery warning is running in the background.
+ */
+data class BatteryStateSnapshot(
+ val batteryLevel: Int,
+ val isPowerSaver: Boolean,
+ val plugged: Boolean,
+ val bucket: Int,
+ val batteryStatus: Int,
+ val severeLevelThreshold: Int,
+ val lowLevelThreshold: Int,
+ val timeRemainingMillis: Long,
+ val severeThresholdMillis: Long,
+ val lowThresholdMillis: Long,
+ val isBasedOnUsage: Boolean
+) {
+ /**
+ * Returns whether hybrid warning logic/copy should be used for this snapshot
+ */
+ var isHybrid: Boolean = false
+ private set
+
+ init {
+ this.isHybrid = true
+ }
+
+ constructor(
+ batteryLevel: Int,
+ isPowerSaver: Boolean,
+ plugged: Boolean,
+ bucket: Int,
+ batteryStatus: Int,
+ severeLevelThreshold: Int,
+ lowLevelThreshold: Int
+ ) : this(
+ batteryLevel,
+ isPowerSaver,
+ plugged,
+ bucket,
+ batteryStatus,
+ severeLevelThreshold,
+ lowLevelThreshold,
+ NO_ESTIMATE_AVAILABLE.toLong(),
+ NO_ESTIMATE_AVAILABLE.toLong(),
+ NO_ESTIMATE_AVAILABLE.toLong(),
+ false
+ ) {
+ this.isHybrid = false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/Estimate.java b/packages/SystemUI/src/com/android/systemui/power/Estimate.java
deleted file mode 100644
index 12a8f0a..0000000
--- a/packages/SystemUI/src/com/android/systemui/power/Estimate.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.android.systemui.power;
-
-public class Estimate {
- public final long estimateMillis;
- public final boolean isBasedOnUsage;
-
- public Estimate(long estimateMillis, boolean isBasedOnUsage) {
- this.estimateMillis = estimateMillis;
- this.isBasedOnUsage = isBasedOnUsage;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/power/Estimate.kt b/packages/SystemUI/src/com/android/systemui/power/Estimate.kt
new file mode 100644
index 0000000..dca0d45
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/Estimate.kt
@@ -0,0 +1,3 @@
+package com.android.systemui.power
+
+data class Estimate(val estimateMillis: Long, val isBasedOnUsage: Boolean)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index fdb0b36..41bcab5 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -134,10 +134,6 @@
private int mShowing;
private long mWarningTriggerTimeMs;
-
- private Estimate mEstimate;
- private long mLowWarningThreshold;
- private long mSevereWarningThreshold;
private boolean mWarning;
private boolean mShowAutoSaverSuggestion;
private boolean mPlaySound;
@@ -148,6 +144,7 @@
private SystemUIDialog mHighTempDialog;
private SystemUIDialog mThermalShutdownDialog;
@VisibleForTesting SystemUIDialog mUsbHighTempDialog;
+ private BatteryStateSnapshot mCurrentBatterySnapshot;
/**
*/
@@ -195,17 +192,8 @@
}
@Override
- public void updateEstimate(Estimate estimate) {
- mEstimate = estimate;
- if (estimate.estimateMillis <= mLowWarningThreshold) {
- mWarningTriggerTimeMs = System.currentTimeMillis();
- }
- }
-
- @Override
- public void updateThresholds(long lowThreshold, long severeThreshold) {
- mLowWarningThreshold = lowThreshold;
- mSevereWarningThreshold = severeThreshold;
+ public void updateSnapshot(BatteryStateSnapshot snapshot) {
+ mCurrentBatterySnapshot = snapshot;
}
private void updateNotification() {
@@ -254,15 +242,17 @@
protected void showWarningNotification() {
final String percentage = NumberFormat.getPercentInstance()
- .format((double) mBatteryLevel / 100.0);
+ .format((double) mCurrentBatterySnapshot.getBatteryLevel() / 100.0);
- // get standard notification copy
+ // get shared standard notification copy
String title = mContext.getString(R.string.battery_low_title);
- String contentText = mContext.getString(R.string.battery_low_percent_format, percentage);
+ String contentText;
- // override notification copy if hybrid notification enabled
- if (mEstimate != null) {
+ // get correct content text if notification is hybrid or not
+ if (mCurrentBatterySnapshot.isHybrid()) {
contentText = getHybridContentString(percentage);
+ } else {
+ contentText = mContext.getString(R.string.battery_low_percent_format, percentage);
}
final Notification.Builder nb =
@@ -282,8 +272,9 @@
}
// Make the notification red if the percentage goes below a certain amount or the time
// remaining estimate is disabled
- if (mEstimate == null || mBucket < 0
- || mEstimate.estimateMillis < mSevereWarningThreshold) {
+ if (!mCurrentBatterySnapshot.isHybrid() || mBucket < 0
+ || mCurrentBatterySnapshot.getTimeRemainingMillis()
+ < mCurrentBatterySnapshot.getSevereThresholdMillis()) {
nb.setColor(Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorError));
}
@@ -324,10 +315,10 @@
private String getHybridContentString(String percentage) {
return PowerUtil.getBatteryRemainingStringFormatted(
- mContext,
- mEstimate.estimateMillis,
- percentage,
- mEstimate.isBasedOnUsage);
+ mContext,
+ mCurrentBatterySnapshot.getTimeRemainingMillis(),
+ percentage,
+ mCurrentBatterySnapshot.isBasedOnUsage());
}
private PendingIntent pendingBroadcast(String action) {
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index e27c25e..1863860 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -55,6 +55,7 @@
import java.util.concurrent.Future;
public class PowerUI extends SystemUI {
+
static final String TAG = "PowerUI";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS;
@@ -63,6 +64,7 @@
static final long THREE_HOURS_IN_MILLIS = DateUtils.HOUR_IN_MILLIS * 3;
private static final int CHARGE_CYCLE_PERCENT_RESET = 45;
private static final long SIX_HOURS_MILLIS = Duration.ofHours(6).toMillis();
+ public static final int NO_ESTIMATE_AVAILABLE = -1;
private final Handler mHandler = new Handler();
@VisibleForTesting
@@ -71,13 +73,9 @@
private PowerManager mPowerManager;
private WarningsUI mWarnings;
private final Configuration mLastConfiguration = new Configuration();
- private long mTimeRemaining = Long.MAX_VALUE;
private int mPlugType = 0;
private int mInvalidCharger = 0;
private EnhancedEstimates mEnhancedEstimates;
- private Estimate mLastEstimate;
- private boolean mLowWarningShownThisChargeCycle;
- private boolean mSevereWarningShownThisChargeCycle;
private Future mLastShowWarningTask;
private boolean mEnableSkinTemperatureWarning;
private boolean mEnableUsbTemperatureAlarm;
@@ -87,6 +85,10 @@
private long mScreenOffTime = -1;
+ @VisibleForTesting boolean mLowWarningShownThisChargeCycle;
+ @VisibleForTesting boolean mSevereWarningShownThisChargeCycle;
+ @VisibleForTesting BatteryStateSnapshot mCurrentBatteryStateSnapshot;
+ @VisibleForTesting BatteryStateSnapshot mLastBatteryStateSnapshot;
@VisibleForTesting IThermalService mThermalService;
@VisibleForTesting int mBatteryLevel = 100;
@@ -205,6 +207,7 @@
mPlugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 1);
final int oldInvalidCharger = mInvalidCharger;
mInvalidCharger = intent.getIntExtra(BatteryManager.EXTRA_INVALID_CHARGER, 0);
+ mLastBatteryStateSnapshot = mCurrentBatteryStateSnapshot;
final boolean plugged = mPlugType != 0;
final boolean oldPlugged = oldPlugType != 0;
@@ -233,16 +236,22 @@
mWarnings.dismissInvalidChargerWarning();
} else if (mWarnings.isInvalidChargerWarningShowing()) {
// if invalid charger is showing, don't show low battery
+ if (DEBUG) {
+ Slog.d(TAG, "Bad Charger");
+ }
return;
}
// Show the correct version of low battery warning if needed
if (mLastShowWarningTask != null) {
mLastShowWarningTask.cancel(true);
+ if (DEBUG) {
+ Slog.d(TAG, "cancelled task");
+ }
}
mLastShowWarningTask = ThreadUtils.postOnBackgroundThread(() -> {
- maybeShowBatteryWarning(
- oldBatteryLevel, plugged, oldPlugged, oldBucket, bucket);
+ maybeShowBatteryWarningV2(
+ plugged, bucket);
});
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
@@ -257,53 +266,150 @@
}
}
- protected void maybeShowBatteryWarning(int oldBatteryLevel, boolean plugged, boolean oldPlugged,
- int oldBucket, int bucket) {
- boolean isPowerSaver = mPowerManager.isPowerSaveMode();
- // only play SFX when the dialog comes up or the bucket changes
- final boolean playSound = bucket != oldBucket || oldPlugged;
+ protected void maybeShowBatteryWarningV2(boolean plugged, int bucket) {
final boolean hybridEnabled = mEnhancedEstimates.isHybridNotificationEnabled();
- if (hybridEnabled) {
- Estimate estimate = mLastEstimate;
- if (estimate == null || mBatteryLevel != oldBatteryLevel) {
- estimate = mEnhancedEstimates.getEstimate();
- mLastEstimate = estimate;
- }
- // Turbo is not always booted once SysUI is running so we have to make sure we actually
- // get data back
- if (estimate != null) {
- mTimeRemaining = estimate.estimateMillis;
- mWarnings.updateEstimate(estimate);
- mWarnings.updateThresholds(mEnhancedEstimates.getLowWarningThreshold(),
- mEnhancedEstimates.getSevereWarningThreshold());
+ final boolean isPowerSaverMode = mPowerManager.isPowerSaveMode();
- // if we are now over 45% battery & 6 hours remaining we can trigger hybrid
- // notification again
- if (mBatteryLevel >= CHARGE_CYCLE_PERCENT_RESET
- && mTimeRemaining > SIX_HOURS_MILLIS) {
- mLowWarningShownThisChargeCycle = false;
- mSevereWarningShownThisChargeCycle = false;
- }
+ // Stick current battery state into an immutable container to determine if we should show
+ // a warning.
+ if (DEBUG) {
+ Slog.d(TAG, "evaluating which notification to show");
+ }
+ if (hybridEnabled) {
+ if (DEBUG) {
+ Slog.d(TAG, "using hybrid");
+ }
+ Estimate estimate = refreshEstimateIfNeeded();
+ mCurrentBatteryStateSnapshot = new BatteryStateSnapshot(mBatteryLevel, isPowerSaverMode,
+ plugged, bucket, mBatteryStatus, mLowBatteryReminderLevels[1],
+ mLowBatteryReminderLevels[0], estimate.getEstimateMillis(),
+ mEnhancedEstimates.getSevereWarningThreshold(),
+ mEnhancedEstimates.getLowWarningThreshold(), estimate.isBasedOnUsage());
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "using standard");
+ }
+ mCurrentBatteryStateSnapshot = new BatteryStateSnapshot(mBatteryLevel, isPowerSaverMode,
+ plugged, bucket, mBatteryStatus, mLowBatteryReminderLevels[1],
+ mLowBatteryReminderLevels[0]);
+ }
+
+ mWarnings.updateSnapshot(mCurrentBatteryStateSnapshot);
+ if (mCurrentBatteryStateSnapshot.isHybrid()) {
+ maybeShowHybridWarning(mCurrentBatteryStateSnapshot, mLastBatteryStateSnapshot);
+ } else {
+ maybeShowBatteryWarning(mCurrentBatteryStateSnapshot, mLastBatteryStateSnapshot);
+ }
+ }
+
+ // updates the time estimate if we don't have one or battery level has changed.
+ @VisibleForTesting
+ Estimate refreshEstimateIfNeeded() {
+ if (mLastBatteryStateSnapshot == null
+ || mLastBatteryStateSnapshot.getTimeRemainingMillis() == NO_ESTIMATE_AVAILABLE
+ || mBatteryLevel != mLastBatteryStateSnapshot.getBatteryLevel()) {
+ final Estimate estimate = mEnhancedEstimates.getEstimate();
+ if (DEBUG) {
+ Slog.d(TAG, "updated estimate: " + estimate.getEstimateMillis());
+ }
+ return estimate;
+ }
+ return new Estimate(mLastBatteryStateSnapshot.getTimeRemainingMillis(),
+ mLastBatteryStateSnapshot.isBasedOnUsage());
+ }
+
+ @VisibleForTesting
+ void maybeShowHybridWarning(BatteryStateSnapshot currentSnapshot,
+ BatteryStateSnapshot lastSnapshot) {
+ // if we are now over 45% battery & 6 hours remaining so we can trigger hybrid
+ // notification again
+ if (currentSnapshot.getBatteryLevel() >= CHARGE_CYCLE_PERCENT_RESET
+ && currentSnapshot.getTimeRemainingMillis() > SIX_HOURS_MILLIS) {
+ mLowWarningShownThisChargeCycle = false;
+ mSevereWarningShownThisChargeCycle = false;
+ if (DEBUG) {
+ Slog.d(TAG, "Charge cycle reset! Can show warnings again");
}
}
- if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket,
- mTimeRemaining, isPowerSaver, mBatteryStatus)) {
- mWarnings.showLowBatteryWarning(playSound);
+ final boolean playSound = currentSnapshot.getBucket() != lastSnapshot.getBucket()
+ || lastSnapshot.getPlugged();
+ if (shouldShowHybridWarning(currentSnapshot)) {
+ mWarnings.showLowBatteryWarning(playSound);
// mark if we've already shown a warning this cycle. This will prevent the notification
// trigger from spamming users by only showing low/critical warnings once per cycle
- if (hybridEnabled) {
- if (mTimeRemaining <= mEnhancedEstimates.getSevereWarningThreshold()
- || mBatteryLevel <= mLowBatteryReminderLevels[1]) {
- mSevereWarningShownThisChargeCycle = true;
- mLowWarningShownThisChargeCycle = true;
- } else {
- mLowWarningShownThisChargeCycle = true;
+ if (currentSnapshot.getTimeRemainingMillis()
+ <= currentSnapshot.getSevereLevelThreshold()
+ || currentSnapshot.getBatteryLevel() <= mLowBatteryReminderLevels[1]) {
+ mSevereWarningShownThisChargeCycle = true;
+ mLowWarningShownThisChargeCycle = true;
+ if (DEBUG) {
+ Slog.d(TAG, "Severe warning marked as shown this cycle");
}
+ } else {
+ Slog.d(TAG, "Low warning marked as shown this cycle");
+ mLowWarningShownThisChargeCycle = true;
}
- } else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining,
- isPowerSaver)) {
+
+ } else if (shouldDismissHybridWarning(currentSnapshot)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Dismissing warning");
+ }
+ mWarnings.dismissLowBatteryWarning();
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "Updating warning");
+ }
+ mWarnings.updateLowBatteryWarning();
+ }
+ }
+
+ @VisibleForTesting
+ boolean shouldShowHybridWarning(BatteryStateSnapshot snapshot) {
+ if (snapshot.getPlugged()
+ || snapshot.getBatteryStatus() == BatteryManager.BATTERY_STATUS_UNKNOWN) {
+ Slog.d(TAG, "can't show warning due to - plugged: " + snapshot.getPlugged()
+ + " status unknown: "
+ + (snapshot.getBatteryStatus() == BatteryManager.BATTERY_STATUS_UNKNOWN));
+ return false;
+ }
+
+ // Only show the low warning once per charge cycle & no battery saver
+ final boolean canShowWarning = !mLowWarningShownThisChargeCycle && !snapshot.isPowerSaver()
+ && (snapshot.getTimeRemainingMillis() < snapshot.getLowThresholdMillis()
+ || snapshot.getBatteryLevel() <= snapshot.getLowLevelThreshold());
+
+ // Only show the severe warning once per charge cycle
+ final boolean canShowSevereWarning = !mSevereWarningShownThisChargeCycle
+ && (snapshot.getTimeRemainingMillis() < snapshot.getSevereThresholdMillis()
+ || snapshot.getBatteryLevel() <= snapshot.getSevereLevelThreshold());
+
+ final boolean canShow = canShowWarning || canShowSevereWarning;
+ if (DEBUG) {
+ Slog.d(TAG, "Enhanced trigger is: " + canShow + "\nwith battery snapshot:"
+ + " mLowWarningShownThisChargeCycle: " + mLowWarningShownThisChargeCycle
+ + " mSevereWarningShownThisChargeCycle: " + mSevereWarningShownThisChargeCycle
+ + "\n" + snapshot.toString());
+ }
+ return canShow;
+ }
+
+ @VisibleForTesting
+ boolean shouldDismissHybridWarning(BatteryStateSnapshot snapshot) {
+ return snapshot.getPlugged()
+ || snapshot.getTimeRemainingMillis() > snapshot.getLowThresholdMillis();
+ }
+
+ protected void maybeShowBatteryWarning(
+ BatteryStateSnapshot currentSnapshot,
+ BatteryStateSnapshot lastSnapshot) {
+ final boolean playSound = currentSnapshot.getBucket() != lastSnapshot.getBucket()
+ || lastSnapshot.getPlugged();
+
+ if (shouldShowLowBatteryWarning(currentSnapshot, lastSnapshot)) {
+ mWarnings.showLowBatteryWarning(playSound);
+ } else if (shouldDismissLowBatteryWarning(currentSnapshot, lastSnapshot)) {
mWarnings.dismissLowBatteryWarning();
} else {
mWarnings.updateLowBatteryWarning();
@@ -311,64 +417,25 @@
}
@VisibleForTesting
- boolean shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
- int bucket, long timeRemaining, boolean isPowerSaver, int batteryStatus) {
- if (mEnhancedEstimates.isHybridNotificationEnabled()) {
- // triggering logic when enhanced estimate is available
- return isEnhancedTrigger(plugged, timeRemaining, isPowerSaver, batteryStatus);
- }
- // legacy triggering logic
- return !plugged
- && !isPowerSaver
- && (((bucket < oldBucket || oldPlugged) && bucket < 0))
- && batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN;
+ boolean shouldShowLowBatteryWarning(
+ BatteryStateSnapshot currentSnapshot,
+ BatteryStateSnapshot lastSnapshot) {
+ return !currentSnapshot.getPlugged()
+ && !currentSnapshot.isPowerSaver()
+ && (((currentSnapshot.getBucket() < lastSnapshot.getBucket()
+ || lastSnapshot.getPlugged())
+ && currentSnapshot.getBucket() < 0))
+ && currentSnapshot.getBatteryStatus() != BatteryManager.BATTERY_STATUS_UNKNOWN;
}
@VisibleForTesting
- boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket,
- long timeRemaining, boolean isPowerSaver) {
- final boolean hybridEnabled = mEnhancedEstimates.isHybridNotificationEnabled();
- final boolean hybridWouldDismiss = hybridEnabled
- && timeRemaining > mEnhancedEstimates.getLowWarningThreshold();
- final boolean standardWouldDismiss = (bucket > oldBucket && bucket > 0);
- return (isPowerSaver && !hybridEnabled)
- || plugged
- || (standardWouldDismiss && (!mEnhancedEstimates.isHybridNotificationEnabled()
- || hybridWouldDismiss));
- }
-
- private boolean isEnhancedTrigger(boolean plugged, long timeRemaining, boolean isPowerSaver,
- int batteryStatus) {
- if (plugged || batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
- return false;
- }
- int warnLevel = mLowBatteryReminderLevels[0];
- int critLevel = mLowBatteryReminderLevels[1];
-
- // Only show the low warning once per charge cycle & no battery saver
- final boolean canShowWarning = !mLowWarningShownThisChargeCycle && !isPowerSaver
- && (timeRemaining < mEnhancedEstimates.getLowWarningThreshold()
- || mBatteryLevel <= warnLevel);
-
- // Only show the severe warning once per charge cycle
- final boolean canShowSevereWarning = !mSevereWarningShownThisChargeCycle
- && (timeRemaining < mEnhancedEstimates.getSevereWarningThreshold()
- || mBatteryLevel <= critLevel);
-
- final boolean canShow = canShowWarning || canShowSevereWarning;
- if (DEBUG) {
- Slog.d(TAG, "Enhanced trigger is: " + canShow + "\nwith values: "
- + " mLowWarningShownThisChargeCycle: " + mLowWarningShownThisChargeCycle
- + " mSevereWarningShownThisChargeCycle: " + mSevereWarningShownThisChargeCycle
- + " mEnhancedEstimates.timeremaining: " + timeRemaining
- + " mBatteryLevel: " + mBatteryLevel
- + " canShowWarning: " + canShowWarning
- + " canShowSevereWarning: " + canShowSevereWarning
- + " plugged: " + plugged
- + " batteryStatus: " + batteryStatus
- + " isPowerSaver: " + isPowerSaver);
- }
- return canShowWarning || canShowSevereWarning;
+ boolean shouldDismissLowBatteryWarning(
+ BatteryStateSnapshot currentSnapshot,
+ BatteryStateSnapshot lastSnapshot) {
+ return currentSnapshot.isPowerSaver()
+ || currentSnapshot.getPlugged()
+ || (currentSnapshot.getBucket() > lastSnapshot.getBucket()
+ && currentSnapshot.getBucket() > 0);
}
private void initTemperature() {
@@ -453,13 +520,21 @@
mWarnings.dump(pw);
}
+ /**
+ * The interface to allow PowerUI to communicate with whatever implementation of WarningsUI
+ * is being used by the system.
+ */
public interface WarningsUI {
+
+ /**
+ * Updates battery and screen info for determining whether to trigger battery warnings or
+ * not.
+ * @param batteryLevel The current battery level
+ * @param bucket The current battery bucket
+ * @param screenOffTime How long the screen has been off in millis
+ */
void update(int batteryLevel, int bucket, long screenOffTime);
- void updateEstimate(Estimate estimate);
-
- void updateThresholds(long lowThreshold, long severeThreshold);
-
void dismissLowBatteryWarning();
void showLowBatteryWarning(boolean playSound);
@@ -486,6 +561,12 @@
void dump(PrintWriter pw);
void userSwitched();
+
+ /**
+ * Updates the snapshot of battery state used for evaluating battery warnings
+ * @param snapshot object containing relevant values for making battery warning decisions.
+ */
+ void updateSnapshot(BatteryStateSnapshot snapshot);
}
// Thermal event received from thermal service manager subsystem
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 a354885..142f398 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -20,9 +20,6 @@
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -2956,6 +2953,9 @@
if (mKeyguardStatusBar != null) {
mKeyguardStatusBar.dump(fd, pw, args);
}
+ if (mKeyguardStatusView != null) {
+ mKeyguardStatusView.dump(fd, pw, args);
+ }
}
public boolean hasActiveClearableNotifications() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index af3c96f..3fa3e1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -226,7 +226,7 @@
String percentage = NumberFormat.getPercentInstance().format((double) mLevel / 100.0);
return PowerUtil.getBatteryRemainingShortStringFormatted(
- mContext, mEstimate.estimateMillis);
+ mContext, mEstimate.getEstimateMillis());
}
private void updateEstimateInBackground() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Util.java b/packages/SystemUI/src/com/android/systemui/volume/Util.java
index c6d6218..7edb5a5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Util.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Util.java
@@ -16,52 +16,13 @@
package com.android.systemui.volume;
-import android.content.Context;
import android.media.AudioManager;
-import android.media.MediaMetadata;
-import android.media.VolumeProvider;
-import android.media.session.MediaController.PlaybackInfo;
-import android.media.session.PlaybackState;
-import android.telephony.TelephonyManager;
import android.view.View;
-import android.widget.TextView;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Objects;
/**
* Static helpers for the volume dialog.
*/
-class Util {
-
- // Note: currently not shown (only used in the text footer)
- private static final SimpleDateFormat HMMAA = new SimpleDateFormat("h:mm aa", Locale.US);
-
- private static int[] AUDIO_MANAGER_FLAGS = new int[] {
- AudioManager.FLAG_SHOW_UI,
- AudioManager.FLAG_VIBRATE,
- AudioManager.FLAG_PLAY_SOUND,
- AudioManager.FLAG_ALLOW_RINGER_MODES,
- AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE,
- AudioManager.FLAG_SHOW_VIBRATE_HINT,
- AudioManager.FLAG_SHOW_SILENT_HINT,
- AudioManager.FLAG_FROM_KEY,
- AudioManager.FLAG_SHOW_UI_WARNINGS,
- };
-
- private static String[] AUDIO_MANAGER_FLAG_NAMES = new String[] {
- "SHOW_UI",
- "VIBRATE",
- "PLAY_SOUND",
- "ALLOW_RINGER_MODES",
- "REMOVE_SOUND_AND_VIBRATE",
- "SHOW_VIBRATE_HINT",
- "SHOW_SILENT_HINT",
- "FROM_KEY",
- "SHOW_UI_WARNINGS",
- };
+class Util extends com.android.settingslib.volume.Util {
public static String logTag(Class<?> c) {
final String tag = "vol." + c.getSimpleName();
@@ -70,106 +31,19 @@
public static String ringerModeToString(int ringerMode) {
switch (ringerMode) {
- case AudioManager.RINGER_MODE_SILENT: return "RINGER_MODE_SILENT";
- case AudioManager.RINGER_MODE_VIBRATE: return "RINGER_MODE_VIBRATE";
- case AudioManager.RINGER_MODE_NORMAL: return "RINGER_MODE_NORMAL";
- default: return "RINGER_MODE_UNKNOWN_" + ringerMode;
+ case AudioManager.RINGER_MODE_SILENT:
+ return "RINGER_MODE_SILENT";
+ case AudioManager.RINGER_MODE_VIBRATE:
+ return "RINGER_MODE_VIBRATE";
+ case AudioManager.RINGER_MODE_NORMAL:
+ return "RINGER_MODE_NORMAL";
+ default:
+ return "RINGER_MODE_UNKNOWN_" + ringerMode;
}
}
- public static String mediaMetadataToString(MediaMetadata metadata) {
- if (metadata == null) return null;
- return metadata.getDescription().toString();
- }
-
- public static String playbackInfoToString(PlaybackInfo info) {
- if (info == null) return null;
- final String type = playbackInfoTypeToString(info.getPlaybackType());
- final String vc = volumeProviderControlToString(info.getVolumeControl());
- return String.format("PlaybackInfo[vol=%s,max=%s,type=%s,vc=%s],atts=%s",
- info.getCurrentVolume(), info.getMaxVolume(), type, vc, info.getAudioAttributes());
- }
-
- public static String playbackInfoTypeToString(int type) {
- switch (type) {
- case PlaybackInfo.PLAYBACK_TYPE_LOCAL: return "LOCAL";
- case PlaybackInfo.PLAYBACK_TYPE_REMOTE: return "REMOTE";
- default: return "UNKNOWN_" + type;
- }
- }
-
- public static String playbackStateStateToString(int state) {
- switch (state) {
- case PlaybackState.STATE_NONE: return "STATE_NONE";
- case PlaybackState.STATE_STOPPED: return "STATE_STOPPED";
- case PlaybackState.STATE_PAUSED: return "STATE_PAUSED";
- case PlaybackState.STATE_PLAYING: return "STATE_PLAYING";
- default: return "UNKNOWN_" + state;
- }
- }
-
- public static String volumeProviderControlToString(int control) {
- switch (control) {
- case VolumeProvider.VOLUME_CONTROL_ABSOLUTE: return "VOLUME_CONTROL_ABSOLUTE";
- case VolumeProvider.VOLUME_CONTROL_FIXED: return "VOLUME_CONTROL_FIXED";
- case VolumeProvider.VOLUME_CONTROL_RELATIVE: return "VOLUME_CONTROL_RELATIVE";
- default: return "VOLUME_CONTROL_UNKNOWN_" + control;
- }
- }
-
- public static String playbackStateToString(PlaybackState playbackState) {
- if (playbackState == null) return null;
- return playbackStateStateToString(playbackState.getState()) + " " + playbackState;
- }
-
- public static String audioManagerFlagsToString(int value) {
- return bitFieldToString(value, AUDIO_MANAGER_FLAGS, AUDIO_MANAGER_FLAG_NAMES);
- }
-
- private static String bitFieldToString(int value, int[] values, String[] names) {
- if (value == 0) return "";
- final StringBuilder sb = new StringBuilder();
- for (int i = 0; i < values.length; i++) {
- if ((value & values[i]) != 0) {
- if (sb.length() > 0) sb.append(',');
- sb.append(names[i]);
- }
- value &= ~values[i];
- }
- if (value != 0) {
- if (sb.length() > 0) sb.append(',');
- sb.append("UNKNOWN_").append(value);
- }
- return sb.toString();
- }
-
- public static String getShortTime(long millis) {
- return HMMAA.format(new Date(millis));
- }
-
- private static CharSequence emptyToNull(CharSequence str) {
- return str == null || str.length() == 0 ? null : str;
- }
-
- public static boolean setText(TextView tv, CharSequence text) {
- if (Objects.equals(emptyToNull(tv.getText()), emptyToNull(text))) return false;
- tv.setText(text);
- return true;
- }
-
public static final void setVisOrGone(View v, boolean vis) {
if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return;
v.setVisibility(vis ? View.VISIBLE : View.GONE);
}
-
- public static final void setVisOrInvis(View v, boolean vis) {
- if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return;
- v.setVisibility(vis ? View.VISIBLE : View.INVISIBLE);
- }
-
- public static boolean isVoiceCapable(Context context) {
- final TelephonyManager telephony =
- (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- return telephony != null && telephony.isVoiceCapable();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 32bc01c..4c16297 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -53,6 +53,7 @@
import android.view.accessibility.AccessibilityManager;
import com.android.internal.annotations.GuardedBy;
+import com.android.settingslib.volume.MediaSessions;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 5876ae1..58c9311 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -30,6 +30,7 @@
import android.app.Notification;
import android.app.NotificationManager;
+import android.os.BatteryManager;
import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -57,6 +58,9 @@
// Test Instance.
mContext.addMockSystemService(NotificationManager.class, mMockNotificationManager);
mPowerNotificationWarnings = new PowerNotificationWarnings(mContext);
+ BatteryStateSnapshot snapshot = new BatteryStateSnapshot(100, false, false, 1,
+ BatteryManager.BATTERY_HEALTH_GOOD, 5, 15);
+ mPowerNotificationWarnings.updateSnapshot(snapshot);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 0aed63d..f51e473 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -17,8 +17,7 @@
import static android.provider.Settings.Global.SHOW_TEMPERATURE_WARNING;
import static android.provider.Settings.Global.SHOW_USB_TEMPERATURE_ALARM;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.anyObject;
@@ -29,7 +28,6 @@
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.content.Intent;
import android.os.BatteryManager;
import android.os.IThermalEventListener;
import android.os.IThermalService;
@@ -42,22 +40,20 @@
import android.testing.TestableLooper.RunWithLooper;
import android.testing.TestableResources;
-import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.power.PowerUI.WarningsUI;
import com.android.systemui.statusbar.phone.StatusBar;
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.time.Duration;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
@@ -75,6 +71,7 @@
private static final int OLD_BATTERY_LEVEL_NINE = 9;
private static final int OLD_BATTERY_LEVEL_10 = 10;
private static final long VERY_BELOW_SEVERE_HYBRID_THRESHOLD = TimeUnit.MINUTES.toMillis(15);
+ public static final int BATTERY_LEVEL_10 = 10;
private WarningsUI mMockWarnings;
private PowerUI mPowerUI;
private EnhancedEstimates mEnhancedEstimates;
@@ -176,368 +173,333 @@
}
@Test
- public void testShouldShowLowBatteryWarning_showHybridOnly_overrideThresholdHigh_returnsNoShow() {
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold())
- .thenReturn(Duration.ofHours(1).toMillis());
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+ public void testMaybeShowHybridWarning() {
mPowerUI.start();
- // unplugged device that would not show the non-hybrid notification but would show the
- // hybrid but the threshold has been overriden to be too low
- boolean shouldShow =
- mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
- POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
- assertFalse(shouldShow);
+ // verify low warning shown this cycle noticed
+ BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
+ BatteryStateSnapshot lastState = state.get();
+ state.mTimeRemainingMillis = Duration.ofHours(2).toMillis();
+ state.mBatteryLevel = 15;
+
+ mPowerUI.maybeShowHybridWarning(state.get(), lastState);
+
+ assertThat(mPowerUI.mLowWarningShownThisChargeCycle).isTrue();
+ assertThat(mPowerUI.mSevereWarningShownThisChargeCycle).isFalse();
+
+ // verify severe warning noticed this cycle
+ lastState = state.get();
+ state.mBatteryLevel = 1;
+ state.mTimeRemainingMillis = Duration.ofMinutes(10).toMillis();
+
+ mPowerUI.maybeShowHybridWarning(state.get(), lastState);
+
+ assertThat(mPowerUI.mLowWarningShownThisChargeCycle).isTrue();
+ assertThat(mPowerUI.mSevereWarningShownThisChargeCycle).isTrue();
+
+ // verify getting past threshold resets values
+ lastState = state.get();
+ state.mBatteryLevel = 100;
+ state.mTimeRemainingMillis = Duration.ofDays(1).toMillis();
+
+ mPowerUI.maybeShowHybridWarning(state.get(), lastState);
+
+ assertThat(mPowerUI.mLowWarningShownThisChargeCycle).isFalse();
+ assertThat(mPowerUI.mSevereWarningShownThisChargeCycle).isFalse();
}
@Test
- public void testShouldShowLowBatteryWarning_showHybridOnly_overrideThresholdHigh_returnsShow() {
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold())
- .thenReturn(Duration.ofHours(5).toMillis());
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+ public void testShouldShowHybridWarning_lowLevelWarning() {
mPowerUI.start();
+ mPowerUI.mLowWarningShownThisChargeCycle = false;
+ mPowerUI.mSevereWarningShownThisChargeCycle = false;
+ BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
- // unplugged device that would not show the non-hybrid notification but would show the
- // hybrid since the threshold has been overriden to be much higher
- boolean shouldShow =
- mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD,
- POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
- assertTrue(shouldShow);
+ // sanity check to make sure we can show for a valid config
+ state.mBatteryLevel = 10;
+ state.mTimeRemainingMillis = Duration.ofHours(2).toMillis();
+ boolean shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isTrue();
+
+ // Shouldn't show if plugged in
+ state.mPlugged = true;
+ shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isFalse();
+
+ // Shouldn't show if battery is unknown
+ state.mPlugged = false;
+ state.mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
+ shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isFalse();
+
+ state.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
+ // Already shown both warnings
+ mPowerUI.mLowWarningShownThisChargeCycle = true;
+ mPowerUI.mSevereWarningShownThisChargeCycle = true;
+ shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isFalse();
+
+ // Can show low warning
+ mPowerUI.mLowWarningShownThisChargeCycle = false;
+ shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isTrue();
+
+ // Can't show if above the threshold for time & battery
+ state.mTimeRemainingMillis = Duration.ofHours(1000).toMillis();
+ state.mBatteryLevel = 100;
+ shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isFalse();
+
+ // Battery under low percentage threshold but not time
+ state.mBatteryLevel = 10;
+ state.mLowLevelThreshold = 50;
+ shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isTrue();
+
+ // Should also trigger if both level and time remaining under low threshold
+ state.mTimeRemainingMillis = Duration.ofHours(2).toMillis();
+ shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isTrue();
+
+ // battery saver should block the low level warning though
+ state.mIsPowerSaver = true;
+ shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isFalse();
}
@Test
- public void testShouldShowLowBatteryWarning_showHybridOnly_returnsShow() {
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+ public void testShouldShowHybridWarning_severeLevelWarning() {
mPowerUI.start();
+ mPowerUI.mLowWarningShownThisChargeCycle = false;
+ mPowerUI.mSevereWarningShownThisChargeCycle = false;
+ BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
- // unplugged device that would not show the non-hybrid notification but would show the
- // hybrid
- boolean shouldShow =
- mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
- POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
- assertTrue(shouldShow);
+ // sanity check to make sure we can show for a valid config
+ state.mBatteryLevel = 1;
+ state.mTimeRemainingMillis = Duration.ofMinutes(1).toMillis();
+ boolean shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isTrue();
+
+ // Shouldn't show if plugged in
+ state.mPlugged = true;
+ shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isFalse();
+
+ // Shouldn't show if battery is unknown
+ state.mPlugged = false;
+ state.mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
+ shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isFalse();
+
+ state.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
+ // Already shown both warnings
+ mPowerUI.mLowWarningShownThisChargeCycle = true;
+ mPowerUI.mSevereWarningShownThisChargeCycle = true;
+ shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isFalse();
+
+ // Can show severe warning
+ mPowerUI.mSevereWarningShownThisChargeCycle = false;
+ shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isTrue();
+
+ // Can't show if above the threshold for time & battery
+ state.mTimeRemainingMillis = Duration.ofHours(1000).toMillis();
+ state.mBatteryLevel = 100;
+ shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isFalse();
+
+ // Battery under low percentage threshold but not time
+ state.mBatteryLevel = 1;
+ state.mSevereLevelThreshold = 5;
+ shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isTrue();
+
+ // Should also trigger if both level and time remaining under low threshold
+ state.mTimeRemainingMillis = Duration.ofHours(2).toMillis();
+ shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isTrue();
+
+ // battery saver should not block the severe level warning though
+ state.mIsPowerSaver = true;
+ shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+ assertThat(shouldShow).isTrue();
}
@Test
- public void testShouldShowLowBatteryWarning_showHybrid_showStandard_returnsShow() {
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
- mPowerUI.mBatteryLevel = 10;
+ public void testShouldDismissHybridWarning() {
mPowerUI.start();
+ BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
- // unplugged device that would show the non-hybrid notification and the hybrid
- boolean shouldShow =
- mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
- POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
- assertTrue(shouldShow);
+ // We should dismiss if the device is plugged in
+ state.mPlugged = true;
+ state.mTimeRemainingMillis = Duration.ofHours(1).toMillis();
+ state.mLowThresholdMillis = Duration.ofHours(2).toMillis();
+ boolean shouldDismiss = mPowerUI.shouldDismissHybridWarning(state.get());
+ assertThat(shouldDismiss).isTrue();
+
+ // If not plugged in and below the threshold we should not dismiss
+ state.mPlugged = false;
+ shouldDismiss = mPowerUI.shouldDismissHybridWarning(state.get());
+ assertThat(shouldDismiss).isFalse();
+
+ // If we go over the low warning threshold we should dismiss
+ state.mTimeRemainingMillis = Duration.ofHours(3).toMillis();
+ shouldDismiss = mPowerUI.shouldDismissHybridWarning(state.get());
+ assertThat(shouldDismiss).isTrue();
}
@Test
- public void testShouldShowLowBatteryWarning_showStandardOnly_returnsShow() {
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
- mPowerUI.mBatteryLevel = 10;
- mPowerUI.start();
-
- // unplugged device that would show the non-hybrid but not the hybrid
- boolean shouldShow =
- mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD,
- POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
- assertTrue(shouldShow);
- }
-
- @Test
- public void testShouldShowLowBatteryWarning_deviceHighBattery_returnsNoShow() {
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
- mPowerUI.start();
-
- // unplugged device that would show the neither due to battery level being good
- boolean shouldShow =
- mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD,
- POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
- assertFalse(shouldShow);
- }
-
- @Test
- public void testShouldShowLowBatteryWarning_devicePlugged_returnsNoShow() {
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
- mPowerUI.start();
-
- // plugged device that would show the neither due to being plugged
- boolean shouldShow =
- mPowerUI.shouldShowLowBatteryWarning(!UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
- POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
- assertFalse(shouldShow);
- }
-
- @Test
- public void testShouldShowLowBatteryWarning_deviceBatteryStatusUnknown_returnsNoShow() {
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
- mPowerUI.start();
-
- // Unknown battery status device that would show the neither due to the battery status being
- // unknown
- boolean shouldShow =
- mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
- !POWER_SAVER_OFF, BatteryManager.BATTERY_STATUS_UNKNOWN);
- assertFalse(shouldShow);
- }
-
- @Test
- public void testShouldShowLowBatteryWarning_batterySaverEnabled_returnsNoShow() {
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
- mPowerUI.start();
-
- // BatterySaverEnabled device that would show the neither due to battery saver
- boolean shouldShow =
- mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
- !POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
- assertFalse(shouldShow);
- }
-
- @Test
- public void testShouldShowLowBatteryWarning_onlyShowsOncePerChargeCycle() {
- mPowerUI.start();
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
- when(mEnhancedEstimates.getEstimate())
- .thenReturn(new Estimate(BELOW_HYBRID_THRESHOLD, true));
- mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
-
- mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED,
- ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
-
- // reduce battery level to handle time based trigger -> level trigger interactions
- mPowerUI.mBatteryLevel = 10;
- boolean shouldShow =
- mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
- POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
- assertFalse(shouldShow);
- }
-
- @Test
- public void testShouldDismissLowBatteryWarning_dismissWhenPowerSaverEnabledLegacy() {
- mPowerUI.start();
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(false);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-
- // device that gets power saver turned on should dismiss
- boolean shouldDismiss =
- mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
- BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, !POWER_SAVER_OFF);
- assertTrue(shouldDismiss);
- }
-
- @Test
- public void testShouldNotDismissLowBatteryWarning_dismissWhenPowerSaverEnabledHybrid() {
- mPowerUI.start();
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-
- // device that gets power saver turned on should dismiss
- boolean shouldDismiss =
- mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
- BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, !POWER_SAVER_OFF);
- assertFalse(shouldDismiss);
- }
-
- @Test
- public void testShouldDismissLowBatteryWarning_dismissWhenPlugged() {
- mPowerUI.start();
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-
- // device that gets plugged in should dismiss
- boolean shouldDismiss =
- mPowerUI.shouldDismissLowBatteryWarning(!UNPLUGGED, BELOW_WARNING_BUCKET,
- BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
- assertTrue(shouldDismiss);
- }
-
- @Test
- public void testShouldDismissLowBatteryWarning_dismissHybridSignal_showStandardSignal_shouldShow() {
- mPowerUI.start();
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-
- // would dismiss hybrid but not non-hybrid should not dismiss
- boolean shouldDismiss =
- mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
- BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
- assertFalse(shouldDismiss);
- }
-
- @Test
- public void testShouldDismissLowBatteryWarning_showHybridSignal_dismissStandardSignal_shouldShow() {
- mPowerUI.start();
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-
- // would dismiss non-hybrid but not hybrid should not dismiss
- boolean shouldDismiss =
- mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
- ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, POWER_SAVER_OFF);
- assertFalse(shouldDismiss);
- }
-
- @Test
- public void testShouldDismissLowBatteryWarning_showBothSignal_shouldShow() {
- mPowerUI.start();
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-
- // should not dismiss when both would not dismiss
- boolean shouldDismiss =
- mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
- BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, POWER_SAVER_OFF);
- assertFalse(shouldDismiss);
- }
-
- @Test
- public void testShouldDismissLowBatteryWarning_dismissBothSignal_shouldDismiss() {
- mPowerUI.start();
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-
- //should dismiss if both would dismiss
- boolean shouldDismiss =
- mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
- ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
- assertTrue(shouldDismiss);
- }
-
- @Test
- public void testShouldDismissLowBatteryWarning_dismissStandardSignal_hybridDisabled_shouldDismiss() {
- mPowerUI.start();
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(false);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
-
- // would dismiss non-hybrid with hybrid disabled should dismiss
- boolean shouldDismiss =
- mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
- ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
- assertTrue(shouldDismiss);
- }
-
- @Test
- public void testShouldDismissLowBatteryWarning_powerSaverModeEnabled()
- throws InterruptedException {
- when(mPowerManager.isPowerSaveMode()).thenReturn(true);
-
- mPowerUI.start();
- mPowerUI.mReceiver.onReceive(mContext,
- new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
-
- CountDownLatch latch = new CountDownLatch(1);
- ThreadUtils.postOnBackgroundThread(() -> latch.countDown());
- latch.await(5, TimeUnit.SECONDS);
-
- verify(mMockWarnings).dismissLowBatteryWarning();
- }
-
- @Test
- public void testShouldNotDismissLowBatteryWarning_powerSaverModeDisabled()
- throws InterruptedException {
- when(mPowerManager.isPowerSaveMode()).thenReturn(false);
-
- mPowerUI.start();
- mPowerUI.mReceiver.onReceive(mContext,
- new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
-
- CountDownLatch latch = new CountDownLatch(1);
- ThreadUtils.postOnBackgroundThread(() -> latch.countDown());
- latch.await(5, TimeUnit.SECONDS);
-
- verify(mMockWarnings, never()).dismissLowBatteryWarning();
- }
-
- @Test
- public void testSevereWarning_countsAsLowAndSevere_WarningOnlyShownOnce() {
- mPowerUI.start();
- when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
- when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
- when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
- when(mEnhancedEstimates.getEstimate())
- .thenReturn(new Estimate(BELOW_SEVERE_HYBRID_THRESHOLD, true));
- mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
-
- // reduce battery level to handle time based trigger -> level trigger interactions
- mPowerUI.mBatteryLevel = 5;
- boolean shouldShow =
- mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- ABOVE_WARNING_BUCKET, BELOW_SEVERE_HYBRID_THRESHOLD,
- POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
- assertTrue(shouldShow);
-
- // actually run the end to end since it handles changing the internal state.
- mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_10, UNPLUGGED, UNPLUGGED,
- ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
-
- shouldShow =
- mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- ABOVE_WARNING_BUCKET, VERY_BELOW_SEVERE_HYBRID_THRESHOLD,
- POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
- assertFalse(shouldShow);
- }
-
- @Test
- public void testMaybeShowBatteryWarning_onlyQueriesEstimateOnBatteryLevelChangeOrNull() {
+ public void testRefreshEstimateIfNeeded_onlyQueriesEstimateOnBatteryLevelChangeOrNull() {
mPowerUI.start();
Estimate estimate = new Estimate(BELOW_HYBRID_THRESHOLD, true);
when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
when(mEnhancedEstimates.getEstimate()).thenReturn(estimate);
- mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
+ mPowerUI.mBatteryLevel = 10;
- // we expect that the first time it will query even if the level is the same
+ // we expect that the first time it will query since there is no last battery snapshot.
+ // However an invalid estimate (-1) is returned.
+ Estimate refreshedEstimate = mPowerUI.refreshEstimateIfNeeded();
+ assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_HYBRID_THRESHOLD);
+ BatteryStateSnapshot snapshot = new BatteryStateSnapshot(
+ BATTERY_LEVEL_10, false, false, 0, BatteryManager.BATTERY_HEALTH_GOOD,
+ 0, 0, -1, 0, 0, false);
+ mPowerUI.mLastBatteryStateSnapshot = snapshot;
+
+ // query again since the estimate was -1
+ estimate = new Estimate(BELOW_SEVERE_HYBRID_THRESHOLD, true);
+ when(mEnhancedEstimates.getEstimate()).thenReturn(estimate);
+ refreshedEstimate = mPowerUI.refreshEstimateIfNeeded();
+ assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_SEVERE_HYBRID_THRESHOLD);
+ snapshot = new BatteryStateSnapshot(
+ BATTERY_LEVEL_10, false, false, 0, BatteryManager.BATTERY_HEALTH_GOOD, 0,
+ 0, BELOW_SEVERE_HYBRID_THRESHOLD, 0, 0, false);
+ mPowerUI.mLastBatteryStateSnapshot = snapshot;
+
+ // Battery level hasn't changed, so we don't query again
+ estimate = new Estimate(BELOW_HYBRID_THRESHOLD, true);
+ when(mEnhancedEstimates.getEstimate()).thenReturn(estimate);
+ refreshedEstimate = mPowerUI.refreshEstimateIfNeeded();
+ assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_SEVERE_HYBRID_THRESHOLD);
+
+ // Battery level changes so we update again
mPowerUI.mBatteryLevel = 9;
- mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED,
- ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
- verify(mEnhancedEstimates, times(1)).getEstimate();
+ refreshedEstimate = mPowerUI.refreshEstimateIfNeeded();
+ assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_HYBRID_THRESHOLD);
+ }
- // We should NOT query again if the battery level hasn't changed
- mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED,
- ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
- verify(mEnhancedEstimates, times(1)).getEstimate();
+ @Test
+ public void testShouldShowStandardWarning() {
+ mPowerUI.start();
+ BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
+ state.mIsHybrid = false;
+ BatteryStateSnapshot lastState = state.get();
- // Battery level has changed, so we should query again
- mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_10, UNPLUGGED, UNPLUGGED,
- ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
- verify(mEnhancedEstimates, times(2)).getEstimate();
+ // sanity check to make sure we can show for a valid config
+ state.mBatteryLevel = 10;
+ state.mBucket = -1;
+ boolean shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
+ assertThat(shouldShow).isTrue();
+ lastState = state.get();
+
+ // Shouldn't show if plugged in
+ state.mPlugged = true;
+ shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
+ assertThat(shouldShow).isFalse();
+
+ state.mPlugged = false;
+ // Shouldn't show if battery saver
+ state.mIsPowerSaver = true;
+ shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
+ assertThat(shouldShow).isFalse();
+
+ state.mIsPowerSaver = false;
+ // Shouldn't show if battery is unknown
+ state.mPlugged = false;
+ state.mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
+ shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
+ assertThat(shouldShow).isFalse();
+
+ state.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
+ // show if plugged -> unplugged, bucket -1 -> -1
+ state.mPlugged = true;
+ state.mBucket = -1;
+ lastState = state.get();
+ state.mPlugged = false;
+ state.mBucket = -1;
+ shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
+ assertThat(shouldShow).isTrue();
+
+ // don't show if plugged -> unplugged, bucket 0 -> 0
+ state.mPlugged = true;
+ state.mBucket = 0;
+ lastState = state.get();
+ state.mPlugged = false;
+ state.mBucket = 0;
+ shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
+ assertThat(shouldShow).isFalse();
+
+ // show if unplugged -> unplugged, bucket 0 -> -1
+ state.mPlugged = false;
+ state.mBucket = 0;
+ lastState = state.get();
+ state.mPlugged = false;
+ state.mBucket = -1;
+ shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
+ assertThat(shouldShow).isTrue();
+
+ // don't show if unplugged -> unplugged, bucket -1 -> 1
+ state.mPlugged = false;
+ state.mBucket = -1;
+ lastState = state.get();
+ state.mPlugged = false;
+ state.mBucket = 1;
+ shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState);
+ assertThat(shouldShow).isFalse();
+ }
+
+ @Test
+ public void testShouldDismissStandardWarning() {
+ mPowerUI.start();
+ BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper();
+ state.mIsHybrid = false;
+ BatteryStateSnapshot lastState = state.get();
+
+ // should dismiss if battery saver
+ state.mIsPowerSaver = true;
+ boolean shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState);
+ assertThat(shouldDismiss).isTrue();
+
+ state.mIsPowerSaver = false;
+ // should dismiss if plugged
+ state.mPlugged = true;
+ shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState);
+ assertThat(shouldDismiss).isTrue();
+
+ state.mPlugged = false;
+ // should dismiss if bucket 0 -> 1
+ state.mBucket = 0;
+ lastState = state.get();
+ state.mBucket = 1;
+ shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState);
+ assertThat(shouldDismiss).isTrue();
+
+ // shouldn't dismiss if bucket -1 -> 0
+ state.mBucket = -1;
+ lastState = state.get();
+ state.mBucket = 0;
+ shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState);
+ assertThat(shouldDismiss).isFalse();
+
+ // should dismiss if powersaver & bucket 0 -> 1
+ state.mIsPowerSaver = true;
+ state.mBucket = 0;
+ lastState = state.get();
+ state.mBucket = 1;
+ shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState);
+ assertThat(shouldDismiss).isTrue();
}
private Temperature getEmergencyStatusTemp(int type, String name) {
@@ -556,4 +518,35 @@
mPowerUI.mComponents = mContext.getComponents();
mPowerUI.mThermalService = mThermalServiceMock;
}
+
+ /**
+ * A simple wrapper class that sets values by default and makes them not final to improve
+ * test clarity.
+ */
+ private class BatteryStateSnapshotWrapper {
+ public int mBatteryLevel = 100;
+ public boolean mIsPowerSaver = false;
+ public boolean mPlugged = false;
+ public long mSevereThresholdMillis = Duration.ofHours(1).toMillis();
+ public long mLowThresholdMillis = Duration.ofHours(3).toMillis();
+ public int mSevereLevelThreshold = 5;
+ public int mLowLevelThreshold = 15;
+ public int mBucket = 1;
+ public int mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
+ public long mTimeRemainingMillis = Duration.ofHours(24).toMillis();
+ public boolean mIsBasedOnUsage = true;
+ public boolean mIsHybrid = true;
+
+ public BatteryStateSnapshot get() {
+ if (mIsHybrid) {
+ return new BatteryStateSnapshot(mBatteryLevel, mIsPowerSaver, mPlugged, mBucket,
+ mBatteryStatus, mSevereLevelThreshold, mLowLevelThreshold,
+ mTimeRemainingMillis, mSevereThresholdMillis, mLowThresholdMillis,
+ mIsBasedOnUsage);
+ } else {
+ return new BatteryStateSnapshot(mBatteryLevel, mIsPowerSaver, mPlugged, mBucket,
+ mBatteryStatus, mSevereLevelThreshold, mLowLevelThreshold);
+ }
+ }
+ }
}
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 0a6e923..91b161d 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -7078,6 +7078,15 @@
// Open: Settings will show the conditional when Grayscale mode is on
SETTINGS_CONDITION_GRAYSCALE_MODE = 1683;
+ // ACTION: Individual contextual card loading time
+ ACTION_CONTEXTUAL_CARD_LOAD = 1684;
+
+ //ACTION: Contextual card loading timeout
+ ACTION_CONTEXTUAL_CARD_LOAD_TIMEOUT = 1685;
+
+ //ACTION: Log result for each card's eligibility check
+ ACTION_CONTEXTUAL_CARD_ELIGIBILITY = 1686;
+
// ---- End Q Constants, all Q constants go above this line ----
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
index a18686d..9b70272 100644
--- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java
@@ -16,6 +16,7 @@
package com.android.server.contentsuggestions;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -73,6 +74,13 @@
throw new PackageManager.NameNotFoundException(
"Could not get service for " + serviceComponent);
}
+ if (!Manifest.permission.BIND_CONTENT_SUGGESTIONS_SERVICE.equals(si.permission)) {
+ Slog.w(TAG, "ContentSuggestionsService from '" + si.packageName
+ + "' does not require permission "
+ + Manifest.permission.BIND_CONTENT_SUGGESTIONS_SERVICE);
+ throw new SecurityException("Service does not require permission "
+ + Manifest.permission.BIND_CONTENT_SUGGESTIONS_SERVICE);
+ }
return si;
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d2c39ea..da89116 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1575,8 +1575,7 @@
public boolean isActiveNetworkMetered() {
enforceAccessPermission();
- final int uid = Binder.getCallingUid();
- final NetworkCapabilities caps = getUnfilteredActiveNetworkState(uid).networkCapabilities;
+ final NetworkCapabilities caps = getNetworkCapabilities(getActiveNetwork());
if (caps != null) {
return !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
} else {
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 18c2722..d266635 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -38,9 +38,11 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.server.am.SettingsToPropertiesMapper;
import com.android.server.utils.FlagNamespaceUtils;
import java.io.File;
+import java.util.Arrays;
/**
* Utilities to help rescue the system from crash loops. Callers are expected to
@@ -158,6 +160,7 @@
* opportunity to reset any settings depending on our rescue level.
*/
public static void onSettingsProviderPublished(Context context) {
+ handleNativeRescuePartyResets();
executeRescueLevel(context);
}
@@ -176,6 +179,13 @@
return SystemClock.elapsedRealtime();
}
+ private static void handleNativeRescuePartyResets() {
+ if (SettingsToPropertiesMapper.isNativeFlagsResetPerformed()) {
+ FlagNamespaceUtils.resetDeviceConfig(Settings.RESET_MODE_TRUSTED_DEFAULTS,
+ Arrays.asList(SettingsToPropertiesMapper.getResetNativeCategories()));
+ }
+ }
+
/**
* Escalate to the next rescue level. After incrementing the level you'll
* probably want to call {@link #executeRescueLevel(Context)}.
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 415a892..f9fcef6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -27,6 +27,8 @@
import android.provider.DeviceConfig.OnPropertyChangedListener;
import android.provider.Settings;
import android.text.TextUtils;
+import android.text.TextUtils.SimpleStringSplitter;
+import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Slog;
@@ -235,6 +237,8 @@
// Controlled by Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED
volatile boolean mFlagBackgroundActivityStartsEnabled;
+ volatile ArraySet<String> mPackageNamesWhitelistedForBgActivityStarts = new ArraySet<>();
+
private final ActivityManagerService mService;
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -273,6 +277,10 @@
Settings.Global.getUriFor(
Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED);
+ private static final Uri BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST_URI =
+ Settings.Global.getUriFor(
+ Settings.Global.BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST);
+
private final OnPropertyChangedListener mOnDeviceConfigChangedListener =
new OnPropertyChangedListener() {
@Override
@@ -293,9 +301,12 @@
mResolver.registerContentObserver(ACTIVITY_MANAGER_CONSTANTS_URI, false, this);
mResolver.registerContentObserver(ACTIVITY_STARTS_LOGGING_ENABLED_URI, false, this);
mResolver.registerContentObserver(BACKGROUND_ACTIVITY_STARTS_ENABLED_URI, false, this);
+ mResolver.registerContentObserver(BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST_URI,
+ false, this);
updateConstants();
updateActivityStartsLoggingEnabled();
updateBackgroundActivityStartsEnabled();
+ updateBackgroundActivityStartsPackageNamesWhitelist();
DeviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
ActivityThread.currentApplication().getMainExecutor(),
mOnDeviceConfigChangedListener);
@@ -325,6 +336,8 @@
updateActivityStartsLoggingEnabled();
} else if (BACKGROUND_ACTIVITY_STARTS_ENABLED_URI.equals(uri)) {
updateBackgroundActivityStartsEnabled();
+ } else if (BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST_URI.equals(uri)) {
+ updateBackgroundActivityStartsPackageNamesWhitelist();
}
}
@@ -414,6 +427,21 @@
Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED, 1) == 1;
}
+ private void updateBackgroundActivityStartsPackageNamesWhitelist() {
+ final String setting = Settings.Global.getString(mResolver,
+ Settings.Global.BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST);
+ if (TextUtils.isEmpty(setting)) {
+ return;
+ }
+ ArraySet<String> newSet = new ArraySet<>();
+ SimpleStringSplitter splitter = new SimpleStringSplitter(':');
+ splitter.setString(setting);
+ while (splitter.hasNext()) {
+ newSet.add(splitter.next());
+ }
+ mPackageNamesWhitelistedForBgActivityStarts = newSet;
+ }
+
private void updateMaxCachedProcesses() {
String maxCachedProcessesFlag = DeviceConfig.getProperty(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MAX_CACHED_PROCESSES);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ccb9d82..0a68ce0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17862,6 +17862,10 @@
return mConstants.mFlagActivityStartsLoggingEnabled;
}
+ public boolean isPackageNameWhitelistedForBgActivityStarts(String packageName) {
+ return mConstants.mPackageNamesWhitelistedForBgActivityStarts.contains(packageName);
+ }
+
public boolean isBackgroundActivityStartsEnabled() {
return mConstants.mFlagBackgroundActivityStartsEnabled;
}
diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java
index 8e2ca06..34fca23 100644
--- a/services/core/java/com/android/server/am/BroadcastFilter.java
+++ b/services/core/java/com/android/server/am/BroadcastFilter.java
@@ -81,7 +81,9 @@
StringBuilder sb = new StringBuilder();
sb.append("BroadcastFilter{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" u");
+ sb.append(' ');
+ sb.append(owningUid);
+ sb.append("/u");
sb.append(owningUserId);
sb.append(' ');
sb.append(receiverList);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index efb1c44..c1ed54e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -445,7 +445,7 @@
final long elapsed = finishTime - r.receiverTime;
r.state = BroadcastRecord.IDLE;
if (state == BroadcastRecord.IDLE) {
- Slog.w(TAG, "finishReceiver [" + mQueueName + "] called but state is IDLE");
+ Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
}
if (r.allowBackgroundActivityStarts && r.curApp != null) {
if (elapsed > mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT) {
@@ -478,12 +478,13 @@
if (!r.timeoutExempt) {
if (mConstants.SLOW_TIME > 0 && elapsed > mConstants.SLOW_TIME) {
if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Broadcast receiver was slow: " + receiver + " br=" + r);
+ Slog.i(TAG_BROADCAST, "Broadcast receiver " + (r.nextReceiver - 1)
+ + " was slow: " + receiver + " br=" + r);
}
if (r.curApp != null) {
mDispatcher.startDeferring(r.curApp.uid);
} else {
- Slog.d(TAG, "finish receiver curApp is null? " + r);
+ Slog.d(TAG_BROADCAST, "finish receiver curApp is null? " + r);
}
}
} else {
@@ -796,9 +797,7 @@
skipReceiverLocked(r);
}
} else {
- if (r.receiverTime == 0) {
- r.receiverTime = SystemClock.uptimeMillis();
- }
+ r.receiverTime = SystemClock.uptimeMillis();
maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
new Intent(r.intent), r.resultCode, r.resultData,
@@ -1083,16 +1082,19 @@
if (newCount == 0) {
// done! clear out this record's bookkeeping and deliver
if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Sending broadcast completion for split token "
- + r.splitToken);
+ Slog.i(TAG_BROADCAST,
+ "Sending broadcast completion for split token "
+ + r.splitToken + " : " + r.intent.getAction());
}
mSplitRefcounts.delete(r.splitToken);
} else {
// still have some split broadcast records in flight; update refcount
// and hold off on the callback
if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Result refcount " + newCount + " for split token "
- + r.splitToken + " - not sending completion yet");
+ Slog.i(TAG_BROADCAST,
+ "Result refcount now " + newCount + " for split token "
+ + r.splitToken + " : " + r.intent.getAction()
+ + " - not sending completion yet");
}
sendResult = false;
mSplitRefcounts.put(r.splitToken, newCount);
@@ -1155,7 +1157,7 @@
BroadcastRecord defer;
if (r.nextReceiver + 1 == numReceivers) {
if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Sole receiver of " + r
+ Slog.i(TAG_BROADCAST, "Sole receiver of " + r
+ " is under deferral; setting aside and proceeding");
}
defer = r;
@@ -1185,15 +1187,25 @@
// first split of this record; refcount for 'r' and 'deferred'
r.splitToken = defer.splitToken = nextSplitTokenLocked();
mSplitRefcounts.put(r.splitToken, 2);
+ if (DEBUG_BROADCAST_DEFERRAL) {
+ Slog.i(TAG_BROADCAST,
+ "Broadcast needs split refcount; using new token "
+ + r.splitToken);
+ }
} else {
// new split from an already-refcounted situation; increment count
final int curCount = mSplitRefcounts.get(token);
if (DEBUG_BROADCAST_DEFERRAL) {
if (curCount == 0) {
- Slog.wtf(TAG, "Split refcount is zero with token for " + r);
+ Slog.wtf(TAG_BROADCAST,
+ "Split refcount is zero with token for " + r);
}
}
mSplitRefcounts.put(token, curCount + 1);
+ if (DEBUG_BROADCAST_DEFERRAL) {
+ Slog.i(TAG_BROADCAST, "New split count for token " + token
+ + " is " + (curCount + 1));
+ }
}
}
}
@@ -1529,7 +1541,7 @@
if (skip) {
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Skipping delivery of ordered [" + mQueueName + "] "
- + r + " for whatever reason");
+ + r + " for reason described above");
r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
r.receiver = null;
r.curFilter = null;
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index fa9b79d..1352504 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -332,7 +332,6 @@
}
splitReceivers.add(o);
receivers.remove(i);
- break;
} else {
i++;
}
@@ -350,6 +349,7 @@
resultData, resultExtras, ordered, sticky, initialSticky, userId,
allowBackgroundActivityStarts, timeoutExempt);
+ split.splitToken = this.splitToken;
return split;
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 5da1ce6..194549f 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import android.annotation.NonNull;
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.Uri;
@@ -167,7 +168,7 @@
* booting.
* @return
*/
- public static String[] getResetNativeCategories() {
+ public static @NonNull String[] getResetNativeCategories() {
if (!isNativeFlagsResetPerformed()) {
return new String[0];
}
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 280bc02..d723c7b 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -146,7 +146,7 @@
* Whether history is enabled.
*/
@GuardedBy("mInMemoryLock")
- private int mMode = AppOpsManager.HISTORICAL_MODE_DISABLED;
+ private int mMode = AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE;
/**
* This granularity has been chosen to allow clean delineation for intervals
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 47b9c27..1b14ce2 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -53,6 +53,7 @@
import com.android.server.locksettings.recoverablekeystore.certificate.CertXml;
import com.android.server.locksettings.recoverablekeystore.certificate.SigXml;
import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
+import com.android.server.locksettings.recoverablekeystore.storage.CleanupManager;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
@@ -100,6 +101,7 @@
private final PlatformKeyManager mPlatformKeyManager;
private final ApplicationKeyStorage mApplicationKeyStorage;
private final TestOnlyInsecureCertificateHelper mTestCertHelper;
+ private final CleanupManager mCleanupManager;
/**
* Returns a new or existing instance.
@@ -122,16 +124,24 @@
throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
}
+ RecoverySnapshotStorage snapshotStorage =
+ RecoverySnapshotStorage.newInstance();
+ CleanupManager cleanupManager = CleanupManager.getInstance(
+ context.getApplicationContext(),
+ snapshotStorage,
+ db,
+ applicationKeyStorage);
mInstance = new RecoverableKeyStoreManager(
context.getApplicationContext(),
db,
new RecoverySessionStorage(),
Executors.newSingleThreadExecutor(),
- RecoverySnapshotStorage.newInstance(),
+ snapshotStorage,
new RecoverySnapshotListenersStorage(),
platformKeyManager,
applicationKeyStorage,
- new TestOnlyInsecureCertificateHelper());
+ new TestOnlyInsecureCertificateHelper(),
+ cleanupManager);
}
return mInstance;
}
@@ -146,7 +156,8 @@
RecoverySnapshotListenersStorage listenersStorage,
PlatformKeyManager platformKeyManager,
ApplicationKeyStorage applicationKeyStorage,
- TestOnlyInsecureCertificateHelper TestOnlyInsecureCertificateHelper) {
+ TestOnlyInsecureCertificateHelper testOnlyInsecureCertificateHelper,
+ CleanupManager cleanupManager) {
mContext = context;
mDatabase = recoverableKeyStoreDb;
mRecoverySessionStorage = recoverySessionStorage;
@@ -155,8 +166,10 @@
mSnapshotStorage = snapshotStorage;
mPlatformKeyManager = platformKeyManager;
mApplicationKeyStorage = applicationKeyStorage;
- mTestCertHelper = TestOnlyInsecureCertificateHelper;
-
+ mTestCertHelper = testOnlyInsecureCertificateHelper;
+ mCleanupManager = cleanupManager;
+ // Clears data for removed users.
+ mCleanupManager.verifyKnownUsers();
try {
mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
} catch (NoSuchAlgorithmException e) {
@@ -955,6 +968,9 @@
mContext.enforceCallingOrSelfPermission(
Manifest.permission.RECOVER_KEYSTORE,
"Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission.");
+ int userId = UserHandle.getCallingUserId();
+ int uid = Binder.getCallingUid();
+ mCleanupManager.registerRecoveryAgent(userId, uid);
}
private boolean publicKeysMatch(PublicKey publicKey, byte[] vaultParams) {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/CleanupManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/CleanupManager.java
new file mode 100644
index 0000000..be35b50
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/CleanupManager.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.locksettings.recoverablekeystore.storage;
+
+import android.content.Context;
+import android.os.ServiceSpecificException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.locksettings.recoverablekeystore.WrappedKey;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Cleans up data when user is removed.
+ */
+public class CleanupManager {
+ private static final String TAG = "CleanupManager";
+
+ private final Context mContext;
+ private final UserManager mUserManager;
+ private final RecoverableKeyStoreDb mDatabase;
+ private final RecoverySnapshotStorage mSnapshotStorage;
+ private final ApplicationKeyStorage mApplicationKeyStorage;
+
+ // Serial number can not be changed at runtime.
+ private Map<Integer, Long> mSerialNumbers; // Always in sync with the database.
+
+ /**
+ * Creates a new instance of the class.
+ * IMPORTANT: {@code verifyKnownUsers} must be called before the first data access.
+ */
+ public static CleanupManager getInstance(
+ Context context,
+ RecoverySnapshotStorage snapshotStorage,
+ RecoverableKeyStoreDb recoverableKeyStoreDb,
+ ApplicationKeyStorage applicationKeyStorage) {
+ return new CleanupManager(
+ context,
+ snapshotStorage,
+ recoverableKeyStoreDb,
+ UserManager.get(context),
+ applicationKeyStorage);
+ }
+
+ @VisibleForTesting
+ CleanupManager(
+ Context context,
+ RecoverySnapshotStorage snapshotStorage,
+ RecoverableKeyStoreDb recoverableKeyStoreDb,
+ UserManager userManager,
+ ApplicationKeyStorage applicationKeyStorage) {
+ mContext = context;
+ mSnapshotStorage = snapshotStorage;
+ mDatabase = recoverableKeyStoreDb;
+ mUserManager = userManager;
+ mApplicationKeyStorage = applicationKeyStorage;
+ }
+
+ /**
+ * Registers recovery agent in the system, if necessary.
+ */
+ public synchronized void registerRecoveryAgent(int userId, int uid) {
+ if (mSerialNumbers == null) {
+ // Table was uninitialized.
+ verifyKnownUsers();
+ }
+ // uid is ignored since recovery agent is a system app.
+ Long storedSerialNumber = mSerialNumbers.get(userId);
+ if (storedSerialNumber == null) {
+ storedSerialNumber = -1L;
+ }
+ if (storedSerialNumber != -1) {
+ // User was already registered.
+ return;
+ }
+ // User was added after {@code verifyAllUsers} call.
+ long currentSerialNumber = mUserManager.getSerialNumberForUser(UserHandle.of(userId));
+ if (currentSerialNumber != -1) {
+ storeUserSerialNumber(userId, currentSerialNumber);
+ }
+ }
+
+ /**
+ * Removes data if serial number for a user was changed.
+ */
+ public synchronized void verifyKnownUsers() {
+ mSerialNumbers = mDatabase.getUserSerialNumbers();
+ List<Integer> deletedUserIds = new ArrayList<Integer>(){};
+ for (Map.Entry<Integer, Long> entry : mSerialNumbers.entrySet()) {
+ Integer userId = entry.getKey();
+ Long storedSerialNumber = entry.getValue();
+ if (storedSerialNumber == null) {
+ storedSerialNumber = -1L;
+ }
+ long currentSerialNumber = mUserManager.getSerialNumberForUser(UserHandle.of(userId));
+ if (currentSerialNumber == -1) {
+ // User was removed.
+ deletedUserIds.add(userId);
+ removeDataForUser(userId);
+ } else if (storedSerialNumber == -1) {
+ // User is detected for the first time
+ storeUserSerialNumber(userId, currentSerialNumber);
+ } else if (storedSerialNumber != currentSerialNumber) {
+ // User has unexpected serial number - delete data related to old serial number.
+ deletedUserIds.add(userId);
+ removeDataForUser(userId);
+ // Register new user.
+ storeUserSerialNumber(userId, currentSerialNumber);
+ }
+ }
+
+ for (Integer deletedUser : deletedUserIds) {
+ mSerialNumbers.remove(deletedUser);
+ }
+ }
+
+ private void storeUserSerialNumber(int userId, long userSerialNumber) {
+ Log.d(TAG, "Storing serial number for user " + userId + ".");
+ mSerialNumbers.put(userId, userSerialNumber);
+ mDatabase.setUserSerialNumber(userId, userSerialNumber);
+ }
+
+ /**
+ * Removes all data for given user, including
+ *
+ * <ul>
+ * <li> Recovery snapshots for all agents belonging to the {@code userId}.
+ * <li> Entries with data related to {@code userId} from the database.
+ * </ul>
+ */
+ private void removeDataForUser(int userId) {
+ Log.d(TAG, "Removing data for user " + userId + ".");
+ List<Integer> recoveryAgents = mDatabase.getRecoveryAgents(userId);
+ for (Integer uid : recoveryAgents) {
+ mSnapshotStorage.remove(uid);
+ removeAllKeysForRecoveryAgent(userId, uid);
+ }
+
+ mDatabase.removeUserFromAllTables(userId);
+ }
+
+ /**
+ * Removes keys from Android KeyStore for the recovery agent;
+ * Doesn't remove encrypted key material from the database.
+ */
+ private void removeAllKeysForRecoveryAgent(int userId, int uid) {
+ int generationId = mDatabase.getPlatformKeyGenerationId(userId);
+ Map<String, WrappedKey> allKeys = mDatabase.getAllKeys(userId, uid, generationId);
+ for (String alias : allKeys.keySet()) {
+ try {
+ // Delete KeyStore copy.
+ mApplicationKeyStorage.deleteEntry(userId, uid, alias);
+ } catch (ServiceSpecificException e) {
+ // Ignore errors during key removal.
+ Log.e(TAG, "Error while removing recoverable key " + alias + " : " + e);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
index dffaffe..3f5ac8e 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -24,6 +24,7 @@
import android.database.sqlite.SQLiteDatabase;
import android.security.keystore.recovery.RecoveryController;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Log;
import com.android.server.locksettings.recoverablekeystore.TestOnlyInsecureCertificateHelper;
@@ -261,7 +262,7 @@
*
* @hide
*/
- public Map<String, WrappedKey> getAllKeys(int userId, int recoveryAgentUid,
+ public @NonNull Map<String, WrappedKey> getAllKeys(int userId, int recoveryAgentUid,
int platformKeyGenerationId) {
SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
String[] projection = {
@@ -337,6 +338,58 @@
}
/**
+ * Returns serial numbers associated with all known users.
+ * -1 is used for uninitialized serial numbers.
+ *
+ * See {@code UserHandle.getSerialNumberForUser}.
+ * @return Map from userId to serial numbers.
+ */
+ public @NonNull Map<Integer, Long> getUserSerialNumbers() {
+ SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+ String[] projection = {
+ UserMetadataEntry.COLUMN_NAME_USER_ID,
+ UserMetadataEntry.COLUMN_NAME_USER_SERIAL_NUMBER};
+ String selection = null; // get all rows.
+ String[] selectionArguments = {};
+
+ try (
+ Cursor cursor = db.query(
+ UserMetadataEntry.TABLE_NAME,
+ projection,
+ selection,
+ selectionArguments,
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ null)
+ ) {
+ Map<Integer, Long> serialNumbers = new ArrayMap<>();
+ while (cursor.moveToNext()) {
+ int userId = cursor.getInt(
+ cursor.getColumnIndexOrThrow(UserMetadataEntry.COLUMN_NAME_USER_ID));
+ long serialNumber = cursor.getLong(cursor.getColumnIndexOrThrow(
+ UserMetadataEntry.COLUMN_NAME_USER_SERIAL_NUMBER));
+ serialNumbers.put(userId, serialNumber);
+ }
+ return serialNumbers;
+ }
+ }
+
+ /**
+ * Sets the {@code serialNumber} for the user {@code userId}.
+ *
+ * @return The primary key of the inserted row, or -1 if failed.
+ */
+ public long setUserSerialNumber(int userId, long serialNumber) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId);
+ values.put(UserMetadataEntry.COLUMN_NAME_USER_SERIAL_NUMBER, serialNumber);
+ long result = db.replace(
+ UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
+ return result;
+ }
+
+ /**
* Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}.
*/
public void invalidateKeysWithOldGenerationId(int userId, int newGenerationId) {
@@ -424,8 +477,7 @@
*/
@Nullable
public Long getRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias) {
- return getLong(userId, uid, rootAlias,
- RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL);
+ return getLong(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_SERIAL);
}
/**
@@ -441,7 +493,7 @@
*/
public long setRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias,
long serial) {
- return setLong(userId, uid, rootAlias, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL,
+ return setLong(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_SERIAL,
serial);
}
@@ -457,8 +509,7 @@
*/
@Nullable
public CertPath getRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias) {
- byte[] bytes = getBytes(userId, uid, rootAlias,
- RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH);
+ byte[] bytes = getBytes(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_PATH);
if (bytes == null) {
return null;
}
@@ -489,7 +540,7 @@
if (certPath.getCertificates().size() == 0) {
throw new CertificateEncodingException("No certificate contained in the cert path.");
}
- return setBytes(userId, uid, rootAlias, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH,
+ return setBytes(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_PATH,
certPath.getEncoded(CERT_PATH_ENCODING));
}
@@ -1189,6 +1240,63 @@
RootOfTrustEntry.TABLE_NAME, values, selection, selectionArguments);
}
+ /**
+ * Removes all entries for given {@code userId}.
+ */
+ public void removeUserFromAllTables(int userId) {
+ removeUserFromKeysTable(userId);
+ removeUserFromUserMetadataTable(userId);
+ removeUserFromRecoveryServiceMetadataTable(userId);
+ removeUserFromRootOfTrustTable(userId);
+ }
+
+ /**
+ * Removes all entries for given userId from Keys table.
+ *
+ * @return {@code true} if deleted a row.
+ */
+ private boolean removeUserFromKeysTable(int userId) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+ String selection = KeysEntry.COLUMN_NAME_USER_ID + " = ?";
+ String[] selectionArgs = {Integer.toString(userId)};
+ return db.delete(KeysEntry.TABLE_NAME, selection, selectionArgs) > 0;
+ }
+
+ /**
+ * Removes all entries for given userId from UserMetadata table.
+ *
+ * @return {@code true} if deleted a row.
+ */
+ private boolean removeUserFromUserMetadataTable(int userId) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+ String selection = UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
+ String[] selectionArgs = {Integer.toString(userId)};
+ return db.delete(UserMetadataEntry.TABLE_NAME, selection, selectionArgs) > 0;
+ }
+
+ /**
+ * Removes all entries for given userId from RecoveryServiceMetadata table.
+ *
+ * @return {@code true} if deleted a row.
+ */
+ private boolean removeUserFromRecoveryServiceMetadataTable(int userId) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+ String selection = RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
+ String[] selectionArgs = {Integer.toString(userId)};
+ return db.delete(RecoveryServiceMetadataEntry.TABLE_NAME, selection, selectionArgs) > 0;
+ }
+
+ /**
+ * Removes all entries for given userId from RootOfTrust table.
+ *
+ * @return {@code true} if deleted a row.
+ */
+ private boolean removeUserFromRootOfTrustTable(int userId) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+ String selection = RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ?";
+ String[] selectionArgs = {Integer.toString(userId)};
+ return db.delete(RootOfTrustEntry.TABLE_NAME, selection, selectionArgs) > 0;
+ }
/**
* Creates an empty row in the recovery service metadata table if such a row doesn't exist for
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
index b58ee4b..e79d117 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -20,6 +20,8 @@
/**
* Contract for recoverable key database. Describes the tables present.
+ *
+ * Make sure that {@code removeUserFromAllKnownTables} is updated, when new table is added.
*/
class RecoverableKeyStoreDbContract {
/**
@@ -91,6 +93,11 @@
* is used to wrap recoverable keys on disk.
*/
static final String COLUMN_NAME_PLATFORM_KEY_GENERATION_ID = "platform_key_generation_id";
+
+ /**
+ * Serial number for the user which can not be reused. Default value is {@code -1}.
+ */
+ static final String COLUMN_NAME_USER_SERIAL_NUMBER = "user_serial_number";
}
/**
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
index b0613da..cd5e8cf 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -32,7 +32,7 @@
class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper {
private static final String TAG = "RecoverableKeyStoreDbHp";
- static final int DATABASE_VERSION = 5;
+ static final int DATABASE_VERSION = 6; // Added user id serial number.
private static final String DATABASE_NAME = "recoverablekeystore.db";
private static final String SQL_CREATE_KEYS_ENTRY =
@@ -54,7 +54,8 @@
"CREATE TABLE " + UserMetadataEntry.TABLE_NAME + "( "
+ UserMetadataEntry._ID + " INTEGER PRIMARY KEY,"
+ UserMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER UNIQUE,"
- + UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER)";
+ + UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER,"
+ + UserMetadataEntry.COLUMN_NAME_USER_SERIAL_NUMBER + " INTEGER DEFAULT -1)";
private static final String SQL_CREATE_RECOVERY_SERVICE_METADATA_ENTRY =
"CREATE TABLE " + RecoveryServiceMetadataEntry.TABLE_NAME + " ("
@@ -141,6 +142,11 @@
oldVersion = 5;
}
+ if (oldVersion < 6 && newVersion >= 6) {
+ upgradeDbForVersion6(db);
+ oldVersion = 6;
+ }
+
if (oldVersion != newVersion) {
Log.e(TAG, "Failed to update recoverablekeystore database to the most recent version");
}
@@ -179,6 +185,15 @@
KeysEntry.COLUMN_NAME_KEY_METADATA, "BLOB", /*defaultStr=*/ null);
}
+ private void upgradeDbForVersion6(SQLiteDatabase db) {
+ Log.d(TAG, "Updating recoverable keystore database to version 6");
+ // adds a column to store the user serial number
+ addColumnToTable(db, UserMetadataEntry.TABLE_NAME,
+ UserMetadataEntry.COLUMN_NAME_USER_SERIAL_NUMBER,
+ "INTEGER DEFAULT -1",
+ /*defaultStr=*/ null);
+ }
+
private static void addColumnToTable(
SQLiteDatabase db, String tableName, String column, String columnType,
String defaultStr) {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 9e912843..05af13a 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -744,12 +744,8 @@
if (!canAccessProfile(user.getIdentifier(), "Cannot access usage limit")) {
return null;
}
-
- final PackageManagerInternal pmi =
- LocalServices.getService(PackageManagerInternal.class);
- final ComponentName cn = pmi.getDefaultHomeActivity(user.getIdentifier());
- if (!cn.getPackageName().equals(callingPackage)) {
- throw new SecurityException("Caller is not the active launcher");
+ if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
+ throw new SecurityException("Caller is not the recents app");
}
final UsageStatsManagerInternal.AppUsageLimitData data =
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index b72e836..a3b72fd 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -481,6 +481,12 @@
}
}
+ if (callingUid == Process.SYSTEM_UID) {
+ params.installFlags |= PackageManager.INSTALL_RESPECT_ALLOW_DOWNGRADE;
+ } else {
+ params.installFlags &= ~PackageManager.INSTALL_RESPECT_ALLOW_DOWNGRADE;
+ }
+
boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0;
if (params.isStaged || isApex) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES, TAG);
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 3218c86..ff81ad5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -806,7 +806,7 @@
*/
public static boolean isDowngradePermitted(int installFlags, int applicationFlags) {
// If installed, the package will get access to data left on the device by its
- // predecessor. As a security measure, this is permited only if this is not a
+ // predecessor. As a security measure, this is permitted only if this is not a
// version downgrade or if the predecessor package is marked as debuggable and
// a downgrade is explicitly requested.
//
@@ -818,12 +818,21 @@
// installFlags. This is because we aim to keep the behavior of debuggable
// platform builds as close as possible to the behavior of non-debuggable
// platform builds.
+ //
+ // In case of user builds, downgrade is permitted only for the system server initiated
+ // sessions. This is enforced by INSTALL_RESPECT_ALLOW_DOWNGRADE flag parameter.
final boolean downgradeRequested =
(installFlags & PackageManager.INSTALL_ALLOW_DOWNGRADE) != 0;
- final boolean packageDebuggable =
- (applicationFlags
- & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
- return (downgradeRequested) && ((Build.IS_DEBUGGABLE) || (packageDebuggable));
+ if (!downgradeRequested) {
+ return false;
+ }
+ final boolean isDebuggable =
+ Build.IS_DEBUGGABLE || ((applicationFlags
+ & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
+ if (isDebuggable) {
+ return true;
+ }
+ return (installFlags & PackageManager.INSTALL_RESPECT_ALLOW_DOWNGRADE) != 0;
}
/**
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index f52f3a3..e241ba6 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -599,6 +599,8 @@
}
}
}
+
+ mPackageHealthObserver.onBootCompleted();
});
}
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index d24f217..d8f07fe 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -26,6 +26,8 @@
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
+import android.os.Environment;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.PowerManager;
@@ -39,6 +41,12 @@
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
import java.util.Collections;
import java.util.List;
@@ -50,14 +58,19 @@
public final class RollbackPackageHealthObserver implements PackageHealthObserver {
private static final String TAG = "RollbackPackageHealthObserver";
private static final String NAME = "rollback-observer";
- private Context mContext;
- private Handler mHandler;
+ private static final int INVALID_ROLLBACK_ID = -1;
+ private final Context mContext;
+ private final Handler mHandler;
+ private final File mLastStagedRollbackIdFile;
RollbackPackageHealthObserver(Context context) {
mContext = context;
HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
handlerThread.start();
mHandler = handlerThread.getThreadHandler();
+ File dataDir = new File(Environment.getDataDirectory(), "rollback-observer");
+ dataDir.mkdirs();
+ mLastStagedRollbackIdFile = new File(dataDir, "last-staged-rollback-id");
PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
}
@@ -112,15 +125,19 @@
int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
RollbackManager.STATUS_FAILURE);
if (status == RollbackManager.STATUS_SUCCESS) {
- StatsLog.write(StatsLog.WATCHDOG_ROLLBACK_OCCURRED,
- StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
- moduleMetadataPackage.getPackageName(),
- moduleMetadataPackage.getVersionCode());
if (rollback.isStaged()) {
int rollbackId = rollback.getRollbackId();
BroadcastReceiver listener =
- listenForStagedSessionReady(rollbackManager, rollbackId);
- handleStagedSessionChange(rollbackManager, rollbackId, listener);
+ listenForStagedSessionReady(rollbackManager, rollbackId,
+ moduleMetadataPackage);
+ handleStagedSessionChange(rollbackManager, rollbackId, listener,
+ moduleMetadataPackage);
+ } else {
+ StatsLog.write(StatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+ StatsLog
+ .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
+ moduleMetadataPackage.getPackageName(),
+ moduleMetadataPackage.getVersionCode());
}
} else {
StatsLog.write(StatsLog.WATCHDOG_ROLLBACK_OCCURRED,
@@ -152,6 +169,71 @@
PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
}
+ /** Verifies the rollback state after a reboot. */
+ public void onBootCompleted() {
+ int rollbackId = popLastStagedRollbackId();
+ if (rollbackId == INVALID_ROLLBACK_ID) {
+ // No staged rollback before reboot
+ return;
+ }
+
+ RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
+ PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
+ RollbackInfo rollback = null;
+ for (RollbackInfo info : rollbackManager.getRecentlyCommittedRollbacks()) {
+ if (rollbackId == info.getRollbackId()) {
+ rollback = info;
+ break;
+ }
+ }
+
+ if (rollback == null) {
+ Slog.e(TAG, "rollback info not found for last staged rollback: " + rollbackId);
+ return;
+ }
+
+ String moduleMetadataPackageName = getModuleMetadataPackageName();
+ if (moduleMetadataPackageName == null) {
+ // Only log mainline staged rollbacks
+ return;
+ }
+
+ // Use the version of the metadata package that was installed before
+ // we rolled back for logging purposes.
+ VersionedPackage moduleMetadataPackage = null;
+ for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
+ if (moduleMetadataPackageName.equals(packageRollback.getPackageName())) {
+ moduleMetadataPackage = packageRollback.getVersionRolledBackFrom();
+ break;
+ }
+ }
+
+ if (moduleMetadataPackage == null) {
+ // Only log mainline staged rollbacks
+ return;
+ }
+
+ int sessionId = rollback.getCommittedSessionId();
+ PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
+ if (sessionInfo == null) {
+ Slog.e(TAG, "On boot completed, could not load session id " + sessionId);
+ return;
+ }
+ if (sessionInfo.isStagedSessionApplied()) {
+ StatsLog.write(StatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+ StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
+ moduleMetadataPackage.getPackageName(),
+ moduleMetadataPackage.getVersionCode());
+ } else if (sessionInfo.isStagedSessionReady()) {
+ // TODO: What do for staged session ready but not applied
+ } else {
+ StatsLog.write(StatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+ StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
+ moduleMetadataPackage.getPackageName(),
+ moduleMetadataPackage.getVersionCode());
+ }
+ }
+
private Pair<RollbackInfo, Boolean> getAvailableRollback(RollbackManager rollbackManager,
VersionedPackage failedPackage, VersionedPackage moduleMetadataPackage) {
for (RollbackInfo rollback : rollbackManager.getAvailableRollbacks()) {
@@ -174,12 +256,20 @@
return null;
}
- private VersionedPackage getModuleMetadataPackage() {
+ private String getModuleMetadataPackageName() {
String packageName = mContext.getResources().getString(
R.string.config_defaultModuleMetadataProvider);
if (TextUtils.isEmpty(packageName)) {
return null;
}
+ return packageName;
+ }
+
+ private VersionedPackage getModuleMetadataPackage() {
+ String packageName = getModuleMetadataPackageName();
+ if (packageName == null) {
+ return null;
+ }
try {
return new VersionedPackage(packageName, mContext.getPackageManager().getPackageInfo(
@@ -191,12 +281,12 @@
}
private BroadcastReceiver listenForStagedSessionReady(RollbackManager rollbackManager,
- int rollbackId) {
+ int rollbackId, VersionedPackage moduleMetadataPackage) {
BroadcastReceiver sessionUpdatedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
handleStagedSessionChange(rollbackManager,
- rollbackId, this /* BroadcastReceiver */);
+ rollbackId, this /* BroadcastReceiver */, moduleMetadataPackage);
}
};
IntentFilter sessionUpdatedFilter =
@@ -206,7 +296,7 @@
}
private void handleStagedSessionChange(RollbackManager rollbackManager, int rollbackId,
- BroadcastReceiver listener) {
+ BroadcastReceiver listener, VersionedPackage moduleMetadataPackage) {
PackageInstaller packageInstaller =
mContext.getPackageManager().getPackageInstaller();
List<RollbackInfo> recentRollbacks =
@@ -220,11 +310,52 @@
packageInstaller.getSessionInfo(sessionId);
if (sessionInfo.isStagedSessionReady()) {
mContext.unregisterReceiver(listener);
+ saveLastStagedRollbackId(rollbackId);
+ StatsLog.write(StatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+ StatsLog
+ .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
+ moduleMetadataPackage.getPackageName(),
+ moduleMetadataPackage.getVersionCode());
mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
} else if (sessionInfo.isStagedSessionFailed()) {
+ StatsLog.write(StatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+ StatsLog
+ .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
+ moduleMetadataPackage.getPackageName(),
+ moduleMetadataPackage.getVersionCode());
mContext.unregisterReceiver(listener);
}
}
}
}
+
+ private void saveLastStagedRollbackId(int stagedRollbackId) {
+ try {
+ FileOutputStream fos = new FileOutputStream(mLastStagedRollbackIdFile);
+ PrintWriter pw = new PrintWriter(fos);
+ pw.println(stagedRollbackId);
+ pw.flush();
+ FileUtils.sync(fos);
+ pw.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to save last staged rollback id", e);
+ mLastStagedRollbackIdFile.delete();
+ }
+ }
+
+ private int popLastStagedRollbackId() {
+ int rollbackId = INVALID_ROLLBACK_ID;
+ if (!mLastStagedRollbackIdFile.exists()) {
+ return rollbackId;
+ }
+
+ try {
+ rollbackId = Integer.parseInt(
+ IoUtils.readFileAsString(mLastStagedRollbackIdFile.getAbsolutePath()).trim());
+ } catch (IOException | NumberFormatException e) {
+ Slog.e(TAG, "Failed to retrieve last staged rollback id", e);
+ }
+ mLastStagedRollbackIdFile.delete();
+ return rollbackId;
+ }
}
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index ef77140..a5d291f 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -27,12 +27,8 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.service.textclassifier.IConversationActionsCallback;
-import android.service.textclassifier.ITextClassificationCallback;
+import android.service.textclassifier.ITextClassifierCallback;
import android.service.textclassifier.ITextClassifierService;
-import android.service.textclassifier.ITextLanguageCallback;
-import android.service.textclassifier.ITextLinksCallback;
-import android.service.textclassifier.ITextSelectionCallback;
import android.service.textclassifier.TextClassifierService;
import android.util.Slog;
import android.util.SparseArray;
@@ -132,7 +128,7 @@
@Override
public void onSuggestSelection(
TextClassificationSessionId sessionId,
- TextSelection.Request request, ITextSelectionCallback callback)
+ TextSelection.Request request, ITextClassifierCallback callback)
throws RemoteException {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
@@ -155,7 +151,7 @@
@Override
public void onClassifyText(
TextClassificationSessionId sessionId,
- TextClassification.Request request, ITextClassificationCallback callback)
+ TextClassification.Request request, ITextClassifierCallback callback)
throws RemoteException {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
@@ -178,7 +174,7 @@
@Override
public void onGenerateLinks(
TextClassificationSessionId sessionId,
- TextLinks.Request request, ITextLinksCallback callback)
+ TextLinks.Request request, ITextClassifierCallback callback)
throws RemoteException {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
@@ -241,7 +237,7 @@
public void onDetectLanguage(
TextClassificationSessionId sessionId,
TextLanguage.Request request,
- ITextLanguageCallback callback) throws RemoteException {
+ ITextClassifierCallback callback) throws RemoteException {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
validateInput(mContext, request.getCallingPackageName());
@@ -264,7 +260,7 @@
public void onSuggestConversationActions(
TextClassificationSessionId sessionId,
ConversationActions.Request request,
- IConversationActionsCallback callback) throws RemoteException {
+ ITextClassifierCallback callback) throws RemoteException {
Preconditions.checkNotNull(request);
Preconditions.checkNotNull(callback);
validateInput(mContext, request.getCallingPackageName());
diff --git a/services/core/java/com/android/server/utils/FlagNamespaceUtils.java b/services/core/java/com/android/server/utils/FlagNamespaceUtils.java
index f26121e..f8c7447 100644
--- a/services/core/java/com/android/server/utils/FlagNamespaceUtils.java
+++ b/services/core/java/com/android/server/utils/FlagNamespaceUtils.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.provider.DeviceConfig;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.RescueParty;
import java.util.ArrayList;
@@ -41,20 +42,23 @@
/**
* Name of the special namespace in DeviceConfig table used for communicating resets.
*/
- private static final String NAMESPACE_RESCUE_PARTY = "rescue_party_namespace";
+ @VisibleForTesting
+ public static final String NAMESPACE_RESCUE_PARTY = "rescue_party_namespace";
/**
* Flag in the {@link DeviceConfig} in {@link #NAMESPACE_RESCUE_PARTY}, holding all known {@link
* DeviceConfig} namespaces, as a {@link #DELIMITER} separated String. It's updated after the
* first time flags are written to the new namespace in the {@link DeviceConfig}.
*/
- private static final String ALL_KNOWN_NAMESPACES_FLAG = "all_known_namespaces";
+ @VisibleForTesting
+ public static final String ALL_KNOWN_NAMESPACES_FLAG = "all_known_namespaces";
/**
* Flag in the {@link DeviceConfig} in {@link #NAMESPACE_RESCUE_PARTY} with integer counter
* suffix added to it, holding {@link DeviceConfig} namespace value whose flags were recently
* reset by the {@link RescueParty}. It's updated by {@link RescueParty} every time given
* namespace flags are reset.
*/
- private static final String RESET_PLATFORM_PACKAGE_FLAG = "reset_platform_package";
+ @VisibleForTesting
+ public static final String RESET_PLATFORM_PACKAGE_FLAG = "reset_platform_package";
private static final String DELIMITER = ":";
/**
* Maximum value of the counter used in combination with {@link #RESET_PLATFORM_PACKAGE_FLAG}
@@ -97,11 +101,25 @@
* Reset all namespaces in DeviceConfig with consumed resetMode.
*/
public static void resetDeviceConfig(int resetMode) {
- List<String> allKnownNamespaces = getAllKnownDeviceConfigNamespacesList();
- for (String namespace : allKnownNamespaces) {
+ resetDeviceConfig(resetMode, getAllKnownDeviceConfigNamespacesList());
+ }
+
+ /**
+ * Reset all consumed namespaces in DeviceConfig with consumed resetMode.
+ */
+ public static void resetDeviceConfig(int resetMode, List<String> namespacesList) {
+ for (String namespace : namespacesList) {
DeviceConfig.resetToDefaults(resetMode, namespace);
}
- addToKnownResetNamespaces(allKnownNamespaces);
+ addToKnownResetNamespaces(namespacesList);
+ }
+
+ /**
+ * Resets known reset namespaces flag counter for tests only.
+ */
+ @VisibleForTesting
+ public static void resetKnownResetNamespacesFlagCounterForTest() {
+ sKnownResetNamespacesFlagCounter = -1;
}
/**
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index bb1e001..e53fde9 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -474,7 +474,13 @@
int wallpaperId;
if (wallpaper.equals(mFallbackWallpaper)) {
- extractDefaultImageWallpaperColors();
+ synchronized (mLock) {
+ if (mFallbackWallpaper.primaryColors != null) return;
+ }
+ final WallpaperColors colors = extractDefaultImageWallpaperColors();
+ synchronized (mLock) {
+ mFallbackWallpaper.primaryColors = colors;
+ }
return;
}
@@ -499,23 +505,7 @@
}
} else if (defaultImageWallpaper) {
// There is no crop and source file because this is default image wallpaper.
- try (final InputStream is =
- WallpaperManager.openDefaultWallpaper(mContext, FLAG_SYSTEM)) {
- if (is != null) {
- try {
- final BitmapFactory.Options options = new BitmapFactory.Options();
- final Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
- if (bitmap != null) {
- colors = WallpaperColors.fromBitmap(bitmap);
- bitmap.recycle();
- }
- } catch (OutOfMemoryError e) {
- Slog.w(TAG, "Can't decode default wallpaper stream", e);
- }
- }
- } catch (IOException e) {
- Slog.w(TAG, "Can't close default wallpaper stream", e);
- }
+ colors = extractDefaultImageWallpaperColors();
}
if (colors == null) {
@@ -535,37 +525,41 @@
}
}
- private void extractDefaultImageWallpaperColors() {
+ private WallpaperColors extractDefaultImageWallpaperColors() {
+ if (DEBUG) Slog.d(TAG, "Extract default image wallpaper colors");
+
synchronized (mLock) {
- if (mFallbackWallpaper.primaryColors != null) return;
+ if (mCacheDefaultImageWallpaperColors != null) return mCacheDefaultImageWallpaperColors;
}
- if (DEBUG) Slog.d(TAG, "Extract default image wallpaper colors");
WallpaperColors colors = null;
- final InputStream is = WallpaperManager.openDefaultWallpaper(mContext, FLAG_SYSTEM);
- if (is != null) {
- try {
- final BitmapFactory.Options options = new BitmapFactory.Options();
- final Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
- if (bitmap != null) {
- colors = WallpaperColors.fromBitmap(bitmap);
- bitmap.recycle();
- }
- } catch (OutOfMemoryError e) {
- Slog.w(TAG, "Can't decode default wallpaper stream", e);
- } finally {
- IoUtils.closeQuietly(is);
+ try (InputStream is = WallpaperManager.openDefaultWallpaper(mContext, FLAG_SYSTEM)) {
+ if (is == null) {
+ Slog.w(TAG, "Can't open default wallpaper stream");
+ return null;
}
+
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ final Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
+ if (bitmap != null) {
+ colors = WallpaperColors.fromBitmap(bitmap);
+ bitmap.recycle();
+ }
+ } catch (OutOfMemoryError e) {
+ Slog.w(TAG, "Can't decode default wallpaper stream", e);
+ } catch (IOException e) {
+ Slog.w(TAG, "Can't close default wallpaper stream", e);
}
if (colors == null) {
Slog.e(TAG, "Extract default image wallpaper colors failed");
- return;
+ } else {
+ synchronized (mLock) {
+ mCacheDefaultImageWallpaperColors = colors;
+ }
}
- synchronized (mLock) {
- mFallbackWallpaper.primaryColors = colors;
- }
+ return colors;
}
/**
@@ -815,6 +809,12 @@
private final ComponentName mImageWallpaper;
/**
+ * Default image wallpaper shall never changed after system service started, caching it when we
+ * first read the image file.
+ */
+ private WallpaperColors mCacheDefaultImageWallpaperColors;
+
+ /**
* Name of the default wallpaper component; might be different from mImageWallpaper
*/
private final ComponentName mDefaultWallpaperComponent;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 9f04166..23bed7b 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1009,6 +1009,12 @@
if (mService.isDeviceOwner(callingPackage)) {
return false;
}
+ // don't abort if the callingPackage is temporarily whitelisted
+ if (mService.isPackageNameWhitelistedForBgActivityStarts(callingPackage)) {
+ Slog.w(TAG, "Background activity start for " + callingPackage
+ + " temporarily whitelisted. This will not be supported in future Q builds.");
+ return false;
+ }
// anything that has fallen through would currently be aborted
Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage
+ "; callingUid: " + callingUid
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index d747198..3255bc6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5210,6 +5210,10 @@
return mAmInternal.isBackgroundActivityStartsEnabled();
}
+ boolean isPackageNameWhitelistedForBgActivityStarts(String packageName) {
+ return mAmInternal.isPackageNameWhitelistedForBgActivityStarts(packageName);
+ }
+
void enableScreenAfterBoot(boolean booted) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_ENABLE_SCREEN,
SystemClock.uptimeMillis());
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a19d5d5..eba00812 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1881,19 +1881,6 @@
mSystemServiceManager.startService(IncidentCompanionService.class);
traceEnd();
- if (safeMode) {
- traceBeginAndSlog("EnterSafeModeAndDisableJitCompilation");
- mActivityManagerService.enterSafeMode();
- // Disable the JIT for the system_server process
- VMRuntime.getRuntime().disableJitCompilation();
- traceEnd();
- } else {
- // Enable the JIT for the system_server process
- traceBeginAndSlog("StartJitCompilation");
- VMRuntime.getRuntime().startJitCompilation();
- traceEnd();
- }
-
// MMS service broker
traceBeginAndSlog("StartMmsService");
mmsService = mSystemServiceManager.startService(MmsServiceBroker.class);
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index b13735c..36825af 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -35,9 +35,12 @@
import android.os.RecoverySystem;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.am.SettingsToPropertiesMapper;
+import com.android.server.utils.FlagNamespaceUtils;
import org.junit.After;
import org.junit.Before;
@@ -56,6 +59,10 @@
public class RescuePartyTest {
private static final int PERSISTENT_APP_UID = 12;
private static final long CURRENT_NETWORK_TIME_MILLIS = 0L;
+ private static final String FAKE_NATIVE_NAMESPACE1 = "native1";
+ private static final String FAKE_NATIVE_NAMESPACE2 = "native2";
+ private static final String[] FAKE_RESET_NATIVE_NAMESPACES =
+ {FAKE_NATIVE_NAMESPACE1, FAKE_NATIVE_NAMESPACE2};
private MockitoSession mSession;
@@ -73,9 +80,11 @@
ExtendedMockito.mockitoSession().initMocks(
this)
.strictness(Strictness.LENIENT)
+ .spyStatic(DeviceConfig.class)
.spyStatic(SystemProperties.class)
.spyStatic(Settings.Global.class)
.spyStatic(Settings.Secure.class)
+ .spyStatic(SettingsToPropertiesMapper.class)
.spyStatic(RecoverySystem.class)
.spyStatic(RescueParty.class)
.startMocking();
@@ -121,8 +130,17 @@
}
).when(() -> SystemProperties.getLong(anyString(), anyLong()));
+ // Mock DeviceConfig
+ doAnswer((Answer<Boolean>) invocationOnMock -> true)
+ .when(() -> DeviceConfig.setProperty(anyString(), anyString(), anyString(),
+ anyBoolean()));
+ doAnswer((Answer<Void>) invocationOnMock -> null)
+ .when(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()));
+
+
doReturn(CURRENT_NETWORK_TIME_MILLIS).when(() -> RescueParty.getElapsedRealtime());
RescueParty.resetAllThresholds();
+ FlagNamespaceUtils.resetKnownResetNamespacesFlagCounterForTest();
SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL,
Integer.toString(RescueParty.LEVEL_NONE));
@@ -278,10 +296,32 @@
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
}
+ @Test
+ public void testNativeRescuePartyResets() {
+ doReturn(true).when(() -> SettingsToPropertiesMapper.isNativeFlagsResetPerformed());
+ doReturn(FAKE_RESET_NATIVE_NAMESPACES).when(
+ () -> SettingsToPropertiesMapper.getResetNativeCategories());
+
+ RescueParty.onSettingsProviderPublished(mMockContext);
+
+ verify(() -> DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS,
+ FAKE_NATIVE_NAMESPACE1));
+ verify(() -> DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS,
+ FAKE_NATIVE_NAMESPACE2));
+
+ ExtendedMockito.verify(
+ () -> DeviceConfig.setProperty(FlagNamespaceUtils.NAMESPACE_RESCUE_PARTY,
+ FlagNamespaceUtils.RESET_PLATFORM_PACKAGE_FLAG + 0,
+ FAKE_NATIVE_NAMESPACE1, /*makeDefault=*/true));
+ ExtendedMockito.verify(
+ () -> DeviceConfig.setProperty(FlagNamespaceUtils.NAMESPACE_RESCUE_PARTY,
+ FlagNamespaceUtils.RESET_PLATFORM_PACKAGE_FLAG + 1,
+ FAKE_NATIVE_NAMESPACE2, /*makeDefault=*/true));
+ }
+
private void verifySettingsResets(int resetMode) {
verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null,
- resetMode,
- UserHandle.USER_SYSTEM));
+ resetMode, UserHandle.USER_SYSTEM));
verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(),
eq(resetMode), anyInt()));
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index c78b96d..5bab65c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -59,6 +59,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
+import com.android.server.locksettings.recoverablekeystore.storage.CleanupManager;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
@@ -154,6 +155,7 @@
@Mock private KeyguardManager mKeyguardManager;
@Mock private PlatformKeyManager mPlatformKeyManager;
@Mock private ApplicationKeyStorage mApplicationKeyStorage;
+ @Mock private CleanupManager mCleanupManager;
@Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
@@ -191,7 +193,8 @@
mMockListenersStorage,
mPlatformKeyManager,
mApplicationKeyStorage,
- mTestOnlyInsecureCertificateHelper);
+ mTestOnlyInsecureCertificateHelper,
+ mCleanupManager);
}
@After
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/CleanupManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/CleanupManagerTest.java
new file mode 100644
index 0000000..0b15a12
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/CleanupManagerTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.locksettings.recoverablekeystore.storage;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CleanupManagerTest {
+ private static final int USER_ID = 10;
+ private static final int USER_ID_2 = 20;
+ private static final int UID = 1234;
+ private static final long USER_SERIAL_NUMBER = 101L;
+ private static final long USER_SERIAL_NUMBER_2 = 202L;
+
+ private Context mContext;
+ private CleanupManager mManager;
+
+ @Mock private RecoverableKeyStoreDb mDatabase;
+ @Mock private RecoverySnapshotStorage mRecoverySnapshotStorage;
+ @Mock private UserManager mUserManager;
+ @Mock private ApplicationKeyStorage mApplicationKeyStorage;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = InstrumentationRegistry.getTargetContext();
+ mManager = new CleanupManager(mContext, mRecoverySnapshotStorage, mDatabase, mUserManager,
+ mApplicationKeyStorage);
+ }
+
+ @Test
+ public void registerRecoveryAgent_unknownUser_storesInDb() throws Exception {
+ when(mDatabase.getUserSerialNumbers()).thenReturn(new HashMap<>());
+ when(mUserManager.getSerialNumberForUser(eq(UserHandle.of(USER_ID))))
+ .thenReturn(USER_SERIAL_NUMBER);
+ when(mUserManager.getSerialNumberForUser(eq(UserHandle.of(USER_ID_2))))
+ .thenReturn(USER_SERIAL_NUMBER_2);
+
+ mManager.registerRecoveryAgent(USER_ID, UID);
+ mManager.registerRecoveryAgent(USER_ID_2, UID);
+
+ verify(mDatabase).setUserSerialNumber(USER_ID, USER_SERIAL_NUMBER);
+ verify(mDatabase).setUserSerialNumber(USER_ID_2, USER_SERIAL_NUMBER_2);
+
+ }
+
+ @Test
+ public void registerRecoveryAgent_registersSameUser_doesntChangeDb() throws Exception {
+ when(mDatabase.getUserSerialNumbers()).thenReturn(new HashMap<>());
+ when(mUserManager.getSerialNumberForUser(eq(UserHandle.of(USER_ID))))
+ .thenReturn(USER_SERIAL_NUMBER);
+
+ mManager.registerRecoveryAgent(USER_ID, UID);
+ mManager.registerRecoveryAgent(USER_ID, UID); // ignored.
+
+ verify(mDatabase, times(1)).setUserSerialNumber(USER_ID, USER_SERIAL_NUMBER);
+ }
+
+ @Test
+ public void verifyKnownUsers_newSerialNumber_deletesData() throws Exception {
+ Map knownSerialNumbers = new HashMap<>();
+ knownSerialNumbers.put(USER_ID, USER_SERIAL_NUMBER);
+ when(mDatabase.getUserSerialNumbers()).thenReturn(knownSerialNumbers);
+ List<Integer> recoveryAgents = new ArrayList<>();
+ recoveryAgents.add(UID);
+ when(mDatabase.getRecoveryAgents(USER_ID)).thenReturn(recoveryAgents);
+
+ when(mUserManager.getSerialNumberForUser(eq(UserHandle.of(USER_ID))))
+ .thenReturn(USER_SERIAL_NUMBER_2); // new value
+
+
+ mManager.verifyKnownUsers();
+
+ verify(mDatabase).removeUserFromAllTables(USER_ID);
+ verify(mDatabase).setUserSerialNumber(USER_ID, USER_SERIAL_NUMBER_2);
+ verify(mRecoverySnapshotStorage).remove(UID);
+ }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
index 35215c3..2658af6 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
@@ -51,6 +51,7 @@
private static final long TEST_LAST_SYNCED_AT = 1517990732000L;
private static final int TEST_RECOVERY_STATUS = 3;
private static final int TEST_PLATFORM_KEY_GENERATION_ID = 11;
+ private static final int TEST_USER_SERIAL_NUMBER = 15;
private static final int TEST_SNAPSHOT_VERSION = 31;
private static final int TEST_SHOULD_CREATE_SNAPSHOT = 1;
private static final byte[] TEST_PUBLIC_KEY = "test-public-key".getBytes(UTF_8);
@@ -234,5 +235,14 @@
assertThat(mDatabase.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values))
.isGreaterThan(-1L);
+
+ // User serial number column was added when upgrading from v5 to v6
+ values = new ContentValues();
+ values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, TEST_USER_ID);
+ values.put(UserMetadataEntry.COLUMN_NAME_USER_SERIAL_NUMBER, TEST_USER_SERIAL_NUMBER);
+ assertThat(
+ mDatabase.replace(UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values))
+ .isGreaterThan(-1L);
}
+
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index 7de9ffc..932a769 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -279,6 +279,55 @@
}
@Test
+ public void getUserSerialNumbers_returnsSerialNumbers() {
+ int userId = 42;
+ int userId2 = 44;
+ Long serialNumber = 24L;
+ Long serialNumber2 = 25L;
+ mRecoverableKeyStoreDb.setUserSerialNumber(userId, serialNumber);
+ mRecoverableKeyStoreDb.setUserSerialNumber(userId2, serialNumber2);
+
+ assertEquals(2, mRecoverableKeyStoreDb.getUserSerialNumbers().size());
+ assertEquals(serialNumber, mRecoverableKeyStoreDb.getUserSerialNumbers().get(userId));
+ assertEquals(serialNumber2, mRecoverableKeyStoreDb.getUserSerialNumbers().get(userId2));
+ }
+
+ @Test
+ public void getUserSerialNumbers_returnsMinusOneIfNoEntry() {
+ int userId = 42;
+ int generationId = 24;
+ Long serialNumber = -1L;
+ // Don't set serial number
+ mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, generationId);
+
+ assertEquals(1, mRecoverableKeyStoreDb.getUserSerialNumbers().size());
+ assertEquals(serialNumber, mRecoverableKeyStoreDb.getUserSerialNumbers().get(userId));
+ }
+
+ @Test
+ public void removeUserFromAllTables_removesData() throws Exception {
+ int userId = 12;
+ int generationId = 24;
+ int[] types = new int[]{1};
+ int uid = 10009;
+ mRecoverableKeyStoreDb.setRecoveryServiceCertSerial(userId, uid,
+ TEST_ROOT_CERT_ALIAS, 1234L);
+ mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, generationId);
+ mRecoverableKeyStoreDb.setActiveRootOfTrust(userId, uid, "root");
+ mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types);
+
+ mRecoverableKeyStoreDb.removeUserFromAllTables(userId);
+
+ // RootOfTrust
+ assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid,
+ TEST_ROOT_CERT_ALIAS)).isNull();
+ // UserMetadata
+ assertThat(mRecoverableKeyStoreDb.getPlatformKeyGenerationId(userId)).isEqualTo(-1);
+ // RecoveryServiceMetadata
+ assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEmpty();
+ }
+
+ @Test
public void setRecoveryStatus_withSingleKey() {
int userId = 12;
int uid = 1009;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 606ab31..d02db7b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -561,7 +561,7 @@
runAndVerifyBackgroundActivityStartsSubtest("allowed_noStartsAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
- false, false, false, false, false);
+ false, false, false, false, false, false);
}
/**
@@ -576,7 +576,7 @@
"disallowed_unsupportedUsecase_aborted", true,
UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
- false, false, false, false, false);
+ false, false, false, false, false, false);
}
/**
@@ -591,61 +591,66 @@
runAndVerifyBackgroundActivityStartsSubtest("disallowed_rootUid_notAborted", false,
Process.ROOT_UID, false, PROCESS_STATE_TOP + 1,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
- false, false, false, false, false);
+ false, false, false, false, false, false);
runAndVerifyBackgroundActivityStartsSubtest("disallowed_systemUid_notAborted", false,
Process.SYSTEM_UID, false, PROCESS_STATE_TOP + 1,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
- false, false, false, false, false);
+ false, false, false, false, false, false);
runAndVerifyBackgroundActivityStartsSubtest("disallowed_nfcUid_notAborted", false,
Process.NFC_UID, false, PROCESS_STATE_TOP + 1,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
- false, false, false, false, false);
+ false, false, false, false, false, false);
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_callingUidHasVisibleWindow_notAborted", false,
UNIMPORTANT_UID, true, PROCESS_STATE_TOP + 1,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
- false, false, false, false, false);
+ false, false, false, false, false, false);
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_callingUidProcessStateTop_notAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
- false, false, false, false, false);
+ false, false, false, false, false, false);
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_realCallingUidHasVisibleWindow_notAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
UNIMPORTANT_UID2, true, PROCESS_STATE_TOP + 1,
- false, false, false, false, false);
+ false, false, false, false, false, false);
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_realCallingUidProcessStateTop_notAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP,
- false, false, false, false, false);
+ false, false, false, false, false, false);
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_hasForegroundActivities_notAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
- true, false, false, false, false);
+ true, false, false, false, false, false);
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_callerIsRecents_notAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
- false, true, false, false, false);
+ false, true, false, false, false, false);
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_callerIsWhitelisted_notAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
- false, false, true, false, false);
+ false, false, true, false, false, false);
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_callerIsInstrumentingWithBackgroundActivityStartPrivileges_notAborted",
false,
UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
- false, false, false, true, false);
+ false, false, false, true, false, false);
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_callingPackageNameIsDeviceOwner_notAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
- false, false, false, false, true);
+ false, false, false, false, true, false);
+ runAndVerifyBackgroundActivityStartsSubtest(
+ "disallowed_callingPackageNameIsTempWhitelisted_notAborted", false,
+ UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ false, false, false, false, false, true);
}
private void runAndVerifyBackgroundActivityStartsSubtest(String name, boolean shouldHaveAborted,
@@ -654,7 +659,7 @@
boolean hasForegroundActivities, boolean callerIsRecents,
boolean callerIsTempWhitelisted,
boolean callerIsInstrumentingWithBackgroundActivityStartPrivileges,
- boolean isCallingPackageNameDeviceOwner) {
+ boolean isCallingPackageNameDeviceOwner, boolean isCallingPackageTempWhitelisted) {
// window visibility
doReturn(callingUidHasVisibleWindow).when(mService.mWindowManager.mRoot)
.isAnyNonToastWindowVisibleForUid(callingUid);
@@ -680,11 +685,15 @@
// caller is instrumenting with background activity starts privileges
callerApp.setInstrumenting(callerIsInstrumentingWithBackgroundActivityStartPrivileges,
callerIsInstrumentingWithBackgroundActivityStartPrivileges);
- // calling package name is whitelisted
+ // calling package name is the device owner
doReturn(isCallingPackageNameDeviceOwner).when(mService).isDeviceOwner(any());
+ // calling package name is temporarily whitelisted
+ doReturn(isCallingPackageTempWhitelisted).when(mService)
+ .isPackageNameWhitelistedForBgActivityStarts("com.whatever.dude");
final ActivityOptions options = spy(ActivityOptions.makeBasic());
ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK)
+ .setCallingPackage("com.whatever.dude")
.setCaller(caller)
.setCallingUid(callingUid)
.setRealCallingUid(realCallingUid)
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index 9d46300..67477cf 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -3829,6 +3829,42 @@
*/
public static final String CARRIER_ID = "carrier_id";
+ /**
+ * The skip 464xlat flag. Flag works as follows.
+ * {@link #SKIP_464XLAT_DEFAULT}: the APN will skip only APN is IMS and no internet.
+ * {@link #SKIP_464XLAT_DISABLE}: the APN will NOT skip 464xlat
+ * {@link #SKIP_464XLAT_ENABLE}: the APN will skip 464xlat
+ * <p>Type: INTEGER</p>
+ *
+ * @hide
+ */
+ public static final String SKIP_464XLAT = "skip_464xlat";
+
+ /**
+ * Possible value for the {@link #SKIP_464XLAT} field.
+ * <p>Type: INTEGER</p>
+ *
+ * @hide
+ */
+ public static final int SKIP_464XLAT_DEFAULT = -1;
+
+ /**
+ * Possible value for the {@link #SKIP_464XLAT} field.
+ * <p>Type: INTEGER</p>
+ *
+ * @hide
+ */
+ public static final int SKIP_464XLAT_DISABLE = 0;
+
+ /**
+ * Possible value for the {@link #SKIP_464XLAT} field.
+ * <p>Type: INTEGER</p>
+ *
+ * @hide
+ */
+ public static final int SKIP_464XLAT_ENABLE = 1;
+
+
/** @hide */
@IntDef({
UNEDITED,
@@ -3839,6 +3875,16 @@
})
@Retention(RetentionPolicy.SOURCE)
public @interface EditStatus {}
+
+ /** @hide */
+ @IntDef({
+ SKIP_464XLAT_DEFAULT,
+ SKIP_464XLAT_DISABLE,
+ SKIP_464XLAT_ENABLE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Skip464XlatStatus {}
+
}
/**
diff --git a/telephony/java/android/telephony/NetworkRegistrationState.java b/telephony/java/android/telephony/NetworkRegistrationState.java
index 9614dc5..a9d3079 100644
--- a/telephony/java/android/telephony/NetworkRegistrationState.java
+++ b/telephony/java/android/telephony/NetworkRegistrationState.java
@@ -285,6 +285,14 @@
}
/**
+ * @hide
+ * @return {@code true} if in service.
+ */
+ public boolean isInService() {
+ return mRegState == REG_STATE_HOME || mRegState == REG_STATE_ROAMING;
+ }
+
+ /**
* Set {@link ServiceState.RoamingType roaming type}. This could override
* roaming type based on resource overlay or carrier config.
* @hide
diff --git a/telephony/java/android/telephony/RadioAccessFamily.java b/telephony/java/android/telephony/RadioAccessFamily.java
index 4fdfcbe0..48c07e8 100644
--- a/telephony/java/android/telephony/RadioAccessFamily.java
+++ b/telephony/java/android/telephony/RadioAccessFamily.java
@@ -22,6 +22,7 @@
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import android.telephony.TelephonyManager.PrefNetworkMode;
import com.android.internal.telephony.RILConstants;
@@ -170,7 +171,8 @@
};
@UnsupportedAppUsage
- public static int getRafFromNetworkType(int type) {
+ @TelephonyManager.NetworkTypeBitMask
+ public static int getRafFromNetworkType(@PrefNetworkMode int type) {
switch (type) {
case RILConstants.NETWORK_MODE_WCDMA_PREF:
return GSM | WCDMA;
@@ -279,6 +281,7 @@
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ @PrefNetworkMode
public static int getNetworkTypeFromRaf(int raf) {
raf = getAdjustedRaf(raf);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index feff01c..0b003dc 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1566,6 +1566,7 @@
* Returns the Type Allocation Code from the IMEI. Return null if Type Allocation Code is not
* available.
*/
+ @Nullable
public String getTypeAllocationCode() {
return getTypeAllocationCode(getSlotIndex());
}
@@ -1576,6 +1577,7 @@
*
* @param slotIndex of which Type Allocation Code is returned
*/
+ @Nullable
public String getTypeAllocationCode(int slotIndex) {
ITelephony telephony = getITelephony();
if (telephony == null) return null;
@@ -1636,6 +1638,7 @@
* Returns the Manufacturer Code from the MEID. Return null if Manufacturer Code is not
* available.
*/
+ @Nullable
public String getManufacturerCode() {
return getManufacturerCode(getSlotIndex());
}
@@ -1646,6 +1649,7 @@
*
* @param slotIndex of which Type Allocation Code is returned
*/
+ @Nullable
public String getManufacturerCode(int slotIndex) {
ITelephony telephony = getITelephony();
if (telephony == null) return null;
@@ -6005,6 +6009,7 @@
* @return IMS Service Table or null if not present or not loaded
* @hide
*/
+ @Nullable
@SystemApi
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getIsimIst() {
@@ -6856,12 +6861,12 @@
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
* @param subId the id of the subscription to set the preferred network type for.
- * @param networkType the preferred network type, defined in RILConstants.java.
+ * @param networkType the preferred network type
* @return true on success; false on any failure.
* @hide
*/
@UnsupportedAppUsage
- public boolean setPreferredNetworkType(int subId, int networkType) {
+ public boolean setPreferredNetworkType(int subId, @PrefNetworkMode int networkType) {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
@@ -10361,6 +10366,10 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the
* calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ *
+ * Note: with only carrier privileges, it is not allowed to switch from multi-sim
+ * to single-sim
+ *
* @param numOfSims number of live SIMs we want to switch to
* @throws android.os.RemoteException
*/
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 9d072f0..be6f383 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -61,6 +61,7 @@
private static final String V4_FORMAT_REGEX = "^\\[ApnSettingV4\\]\\s*";
private static final String V5_FORMAT_REGEX = "^\\[ApnSettingV5\\]\\s*";
private static final String V6_FORMAT_REGEX = "^\\[ApnSettingV6\\]\\s*";
+ private static final String V7_FORMAT_REGEX = "^\\[ApnSettingV7\\]\\s*";
/**
* Default value for mtu if it's not set. Moved from PhoneConstants.
@@ -286,6 +287,8 @@
private boolean mPermanentFailed = false;
private final int mCarrierId;
+ private final int mSkip464Xlat;
+
/**
* Returns the MTU size of the mobile interface to which the APN connected.
*
@@ -623,6 +626,17 @@
return mCarrierId;
}
+ /**
+ * Returns the skip464xlat flag for this APN.
+ *
+ * @return SKIP_464XLAT_DEFAULT, SKIP_464XLAT_DISABLE or SKIP_464XLAT_ENABLE
+ * @hide
+ */
+ @Carriers.Skip464XlatStatus
+ public int getSkip464Xlat() {
+ return mSkip464Xlat;
+ }
+
private ApnSetting(Builder builder) {
this.mEntryName = builder.mEntryName;
this.mApnName = builder.mApnName;
@@ -651,6 +665,7 @@
this.mMvnoMatchData = builder.mMvnoMatchData;
this.mApnSetId = builder.mApnSetId;
this.mCarrierId = builder.mCarrierId;
+ this.mSkip464Xlat = builder.mSkip464Xlat;
}
/**
@@ -662,7 +677,7 @@
int authType, int mApnTypeBitmask, int protocol, int roamingProtocol,
boolean carrierEnabled, int networkTypeBitmask, int profileId,
boolean modemCognitive, int maxConns, int waitTime, int maxConnsTime, int mtu,
- int mvnoType, String mvnoMatchData, int apnSetId, int carrierId) {
+ int mvnoType, String mvnoMatchData, int apnSetId, int carrierId, int skip464xlat) {
return new Builder()
.setId(id)
.setOperatorNumeric(operatorNumeric)
@@ -691,6 +706,7 @@
.setMvnoMatchData(mvnoMatchData)
.setApnSetId(apnSetId)
.setCarrierId(carrierId)
+ .setSkip464Xlat(skip464xlat)
.buildWithoutCheck();
}
@@ -708,7 +724,8 @@
mmsc, mmsProxyAddress, mmsProxyPort, user, password, authType, mApnTypeBitmask,
protocol, roamingProtocol, carrierEnabled, networkTypeBitmask, profileId,
modemCognitive, maxConns, waitTime, maxConnsTime, mtu, mvnoType, mvnoMatchData,
- Carriers.NO_APN_SET_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+ Carriers.NO_APN_SET_ID, TelephonyManager.UNKNOWN_CARRIER_ID,
+ Carriers.SKIP_464XLAT_DEFAULT);
}
/**
@@ -767,7 +784,8 @@
cursor.getString(cursor.getColumnIndexOrThrow(
Telephony.Carriers.MVNO_MATCH_DATA)),
cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN_SET_ID)),
- cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.CARRIER_ID)));
+ cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.CARRIER_ID)),
+ cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.SKIP_464XLAT)));
}
/**
@@ -780,7 +798,7 @@
apn.mProtocol, apn.mRoamingProtocol, apn.mCarrierEnabled, apn.mNetworkTypeBitmask,
apn.mProfileId, apn.mPersistent, apn.mMaxConns, apn.mWaitTime,
apn.mMaxConnsTime, apn.mMtu, apn.mMvnoType, apn.mMvnoMatchData, apn.mApnSetId,
- apn.mCarrierId);
+ apn.mCarrierId, apn.mSkip464Xlat);
}
/**
@@ -829,6 +847,13 @@
* <profileId>, <modemCognitive>, <maxConns>, <waitTime>, <maxConnsTime>, <mtu>,
* <mvnoType>, <mvnoMatchData>, <networkTypeBitmask>, <apnSetId>, <carrierId>
*
+ * v7 format:
+ * [ApnSettingV7] <carrier>, <apn>, <proxy>, <port>, <user>, <password>, <server>,
+ * <mmsc>, <mmsproxy>, <mmsport>, <mcc>, <mnc>, <authtype>,
+ * <type>[| <type>...], <protocol>, <roaming_protocol>, <carrierEnabled>, <bearerBitmask>,
+ * <profileId>, <modemCognitive>, <maxConns>, <waitTime>, <maxConnsTime>, <mtu>,
+ * <mvnoType>, <mvnoMatchData>, <networkTypeBitmask>, <apnSetId>, <carrierId>, <skip464xlat>
+ *
* Note that the strings generated by {@link #toString()} do not contain the username
* and password and thus cannot be read by this method.
*
@@ -841,7 +866,10 @@
int version;
// matches() operates on the whole string, so append .* to the regex.
- if (data.matches(V6_FORMAT_REGEX + ".*")) {
+ if (data.matches(V7_FORMAT_REGEX + ".*")) {
+ version = 7;
+ data = data.replaceFirst(V7_FORMAT_REGEX, "");
+ } else if (data.matches(V6_FORMAT_REGEX + ".*")) {
version = 6;
data = data.replaceFirst(V6_FORMAT_REGEX, "");
} else if (data.matches(V5_FORMAT_REGEX + ".*")) {
@@ -887,6 +915,7 @@
String mvnoMatchData = "";
int apnSetId = Carriers.NO_APN_SET_ID;
int carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+ int skip464xlat = Carriers.SKIP_464XLAT_DEFAULT;
if (version == 1) {
typeArray = new String[a.length - 13];
System.arraycopy(a, 13, typeArray, 0, a.length - 13);
@@ -933,6 +962,12 @@
if (a.length > 28) {
carrierId = Integer.parseInt(a[28]);
}
+ if (a.length > 29) {
+ try {
+ skip464xlat = Integer.parseInt(a[29]);
+ } catch (NumberFormatException e) {
+ }
+ }
}
// If both bearerBitmask and networkTypeBitmask were specified, bearerBitmask would be
@@ -948,7 +983,7 @@
getProtocolIntFromString(protocol), getProtocolIntFromString(roamingProtocol),
carrierEnabled, networkTypeBitmask, profileId, modemCognitive, maxConns, waitTime,
maxConnsTime, mtu, getMvnoTypeIntFromString(mvnoType), mvnoMatchData, apnSetId,
- carrierId);
+ carrierId, skip464xlat);
}
/**
@@ -984,7 +1019,7 @@
*/
public String toString() {
StringBuilder sb = new StringBuilder();
- sb.append("[ApnSettingV6] ")
+ sb.append("[ApnSettingV7] ")
.append(mEntryName)
.append(", ").append(mId)
.append(", ").append(mOperatorNumeric)
@@ -1012,6 +1047,7 @@
sb.append(", ").append(mNetworkTypeBitmask);
sb.append(", ").append(mApnSetId);
sb.append(", ").append(mCarrierId);
+ sb.append(", ").append(mSkip464Xlat);
return sb.toString();
}
@@ -1105,7 +1141,8 @@
&& Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
&& Objects.equals(mNetworkTypeBitmask, other.mNetworkTypeBitmask)
&& Objects.equals(mApnSetId, other.mApnSetId)
- && Objects.equals(mCarrierId, other.mCarrierId);
+ && Objects.equals(mCarrierId, other.mCarrierId)
+ && Objects.equals(mSkip464Xlat, other.mSkip464Xlat);
}
/**
@@ -1151,7 +1188,8 @@
&& Objects.equals(mMvnoType, other.mMvnoType)
&& Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
&& Objects.equals(mApnSetId, other.mApnSetId)
- && Objects.equals(mCarrierId, other.mCarrierId);
+ && Objects.equals(mCarrierId, other.mCarrierId)
+ && Objects.equals(mSkip464Xlat, other.mSkip464Xlat);
}
/**
@@ -1179,7 +1217,8 @@
&& xorEqualsInt(this.mMmsProxyPort, other.mMmsProxyPort))
&& Objects.equals(this.mNetworkTypeBitmask, other.mNetworkTypeBitmask)
&& Objects.equals(mApnSetId, other.mApnSetId)
- && Objects.equals(mCarrierId, other.mCarrierId);
+ && Objects.equals(mCarrierId, other.mCarrierId)
+ && Objects.equals(mSkip464Xlat, other.mSkip464Xlat);
}
// Equal or one is null.
@@ -1226,6 +1265,7 @@
apnValue.put(Telephony.Carriers.MVNO_TYPE, getMvnoTypeStringFromInt(mMvnoType));
apnValue.put(Telephony.Carriers.NETWORK_TYPE_BITMASK, mNetworkTypeBitmask);
apnValue.put(Telephony.Carriers.CARRIER_ID, mCarrierId);
+ apnValue.put(Telephony.Carriers.SKIP_464XLAT, mSkip464Xlat);
return apnValue;
}
@@ -1385,6 +1425,7 @@
dest.writeInt(mNetworkTypeBitmask);
dest.writeInt(mApnSetId);
dest.writeInt(mCarrierId);
+ dest.writeInt(mSkip464Xlat);
}
private static ApnSetting readFromParcel(Parcel in) {
@@ -1408,11 +1449,12 @@
final int networkTypeBitmask = in.readInt();
final int apnSetId = in.readInt();
final int carrierId = in.readInt();
+ final int skip464xlat = in.readInt();
return makeApnSetting(id, operatorNumeric, entryName, apnName,
- proxy, port, mmsc, mmsProxy, mmsPort, user, password, authType, apnTypesBitmask,
- protocol, roamingProtocol, carrierEnabled, networkTypeBitmask, 0, false,
- 0, 0, 0, 0, mvnoType, null, apnSetId, carrierId);
+ proxy, port, mmsc, mmsProxy, mmsPort, user, password, authType, apnTypesBitmask,
+ protocol, roamingProtocol, carrierEnabled, networkTypeBitmask, 0, false,
+ 0, 0, 0, 0, mvnoType, null, apnSetId, carrierId, skip464xlat);
}
public static final @android.annotation.NonNull Parcelable.Creator<ApnSetting> CREATOR =
@@ -1489,6 +1531,7 @@
private String mMvnoMatchData;
private int mApnSetId;
private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+ private int mSkip464Xlat = Carriers.SKIP_464XLAT_DEFAULT;
/**
* Default constructor for Builder.
@@ -1831,6 +1874,17 @@
}
/**
+ * Sets skip464xlat flag for this APN.
+ *
+ * @param skip464xlat skip464xlat for this APN
+ * @hide
+ */
+ public Builder setSkip464Xlat(@Carriers.Skip464XlatStatus int skip464xlat) {
+ this.mSkip464Xlat = skip464xlat;
+ return this;
+ }
+
+ /**
* Builds {@link ApnSetting} from this builder.
*
* @return {@code null} if {@link #setApnName(String)} or {@link #setEntryName(String)}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 4780f30..7e183db 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -379,6 +379,83 @@
// Check that the data has expired after the expiration time (with a buffer of 1 second)
Thread.sleep(expirationTime / 2);
assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A));
+
+ } finally {
+ DeviceConfig.setProperty(DeviceConfig.Rollback.BOOT_NAMESPACE,
+ DeviceConfig.Rollback.ROLLBACK_LIFETIME_IN_MILLIS,
+ Long.toString(defaultExpirationTime), false /* makeDefault*/);
+ RollbackTestUtils.dropShellPermissionIdentity();
+ }
+ }
+
+ /**
+ * Test that changing time on device does not affect the duration of time that we keep
+ * rollback available
+ */
+ @Test
+ public void testTimeChangeDoesNotAffectLifetime() throws Exception {
+ long expirationTime = TimeUnit.SECONDS.toMillis(30);
+ long defaultExpirationTime = TimeUnit.HOURS.toMillis(48);
+ RollbackManager rm = RollbackTestUtils.getRollbackManager();
+
+ try {
+ RollbackTestUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.MANAGE_ROLLBACKS,
+ Manifest.permission.WRITE_DEVICE_CONFIG,
+ Manifest.permission.SET_TIME);
+
+ DeviceConfig.setProperty(DeviceConfig.Rollback.BOOT_NAMESPACE,
+ DeviceConfig.Rollback.ROLLBACK_LIFETIME_IN_MILLIS,
+ Long.toString(expirationTime), false /* makeDefault*/);
+
+ // Pull the new expiration time from DeviceConfig
+ rm.reloadPersistedData();
+
+ // Install app A with rollback enabled
+ RollbackTestUtils.uninstall(TEST_APP_A);
+ RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
+ RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
+ assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+
+ Thread.sleep(expirationTime / 2);
+
+ // Install app B with rollback enabled
+ RollbackTestUtils.uninstall(TEST_APP_B);
+ RollbackTestUtils.install("RollbackTestAppBv1.apk", false);
+ RollbackTestUtils.install("RollbackTestAppBv2.apk", true);
+ assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+ // 1 second buffer
+ Thread.sleep(1000);
+
+ try {
+ // Change the time
+ RollbackTestUtils.forwardTimeBy(expirationTime);
+
+ // 1 second buffer to allow Rollback Manager to handle time change before loading
+ // persisted data
+ Thread.sleep(1000);
+
+ // Load timestamps from storage
+ rm.reloadPersistedData();
+
+ // Wait until rollback for app A has expired
+ // This will trigger an expiration run that should expire app A but not B
+ Thread.sleep(expirationTime / 2);
+ assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A));
+
+ // Rollback for app B should not be expired
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_B);
+ assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollback);
+
+ // Wait until rollback for app B has expired
+ Thread.sleep(expirationTime / 2);
+ assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_B));
+ } finally {
+ RollbackTestUtils.forwardTimeBy(-expirationTime);
+ }
} finally {
DeviceConfig.setProperty(DeviceConfig.Rollback.BOOT_NAMESPACE,
DeviceConfig.Rollback.ROLLBACK_LIFETIME_IN_MILLIS,
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
index e8cbd60..ed8a533 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
+import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -63,6 +64,17 @@
return rm;
}
+ private static void setTime(long millis) {
+ Context context = InstrumentationRegistry.getContext();
+ AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ am.setTime(millis);
+ }
+
+ static void forwardTimeBy(long offsetMillis) {
+ setTime(System.currentTimeMillis() + offsetMillis);
+ Log.i(TAG, "Forwarded time on device by " + offsetMillis + " millis");
+ }
+
/**
* Returns the version of the given package installed on device.
* Returns -1 if the package is not currently installed.
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 1a0e8fa..fbc1a65 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -939,11 +939,19 @@
return mConnected; // Similar trickery
}
- public void connect() {
+ private void connect(boolean isAlwaysMetered) {
mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
mConnected = true;
mConfig = new VpnConfig();
- mConfig.isMetered = false;
+ mConfig.isMetered = isAlwaysMetered;
+ }
+
+ public void connectAsAlwaysMetered() {
+ connect(true /* isAlwaysMetered */);
+ }
+
+ public void connect() {
+ connect(false /* isAlwaysMetered */);
}
@Override
@@ -5104,6 +5112,202 @@
}
@Test
+ public void testIsActiveNetworkMeteredOverWifi() {
+ // Returns true by default when no network is available.
+ assertTrue(mCm.isActiveNetworkMetered());
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+
+ assertFalse(mCm.isActiveNetworkMetered());
+ }
+
+ @Test
+ public void testIsActiveNetworkMeteredOverCell() {
+ // Returns true by default when no network is available.
+ assertTrue(mCm.isActiveNetworkMetered());
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+ mCellNetworkAgent.connect(true);
+ waitForIdle();
+
+ assertTrue(mCm.isActiveNetworkMetered());
+ }
+
+ @Test
+ public void testIsActiveNetworkMeteredOverVpnTrackingPlatformDefault() {
+ // Returns true by default when no network is available.
+ assertTrue(mCm.isActiveNetworkMetered());
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+ mCellNetworkAgent.connect(true);
+ waitForIdle();
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ // Connect VPN network. By default it is using current default network (Cell).
+ MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ final int uid = Process.myUid();
+ ranges.add(new UidRange(uid, uid));
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.setUids(ranges);
+ vpnNetworkAgent.connect(true);
+ mMockVpn.connect();
+ waitForIdle();
+ // Ensure VPN is now the active network.
+ assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // Expect VPN to be metered.
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ // Connect WiFi.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+ // VPN should still be the active network.
+ assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // Expect VPN to be unmetered as it should now be using WiFi (new default).
+ assertFalse(mCm.isActiveNetworkMetered());
+
+ // Disconnecting Cell should not affect VPN's meteredness.
+ mCellNetworkAgent.disconnect();
+ waitForIdle();
+
+ assertFalse(mCm.isActiveNetworkMetered());
+
+ // Disconnect WiFi; Now there is no platform default network.
+ mWiFiNetworkAgent.disconnect();
+ waitForIdle();
+
+ // VPN without any underlying networks is treated as metered.
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ vpnNetworkAgent.disconnect();
+ mMockVpn.disconnect();
+ }
+
+ @Test
+ public void testIsActiveNetworkMeteredOverVpnSpecifyingUnderlyingNetworks() {
+ // Returns true by default when no network is available.
+ assertTrue(mCm.isActiveNetworkMetered());
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+ mCellNetworkAgent.connect(true);
+ waitForIdle();
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+ assertFalse(mCm.isActiveNetworkMetered());
+
+ // Connect VPN network.
+ MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ final int uid = Process.myUid();
+ ranges.add(new UidRange(uid, uid));
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.setUids(ranges);
+ vpnNetworkAgent.connect(true);
+ mMockVpn.connect();
+ waitForIdle();
+ // Ensure VPN is now the active network.
+ assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ // VPN is using Cell
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mCellNetworkAgent.getNetwork() });
+ waitForIdle();
+
+ // Expect VPN to be metered.
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ // VPN is now using WiFi
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mWiFiNetworkAgent.getNetwork() });
+ waitForIdle();
+
+ // Expect VPN to be unmetered
+ assertFalse(mCm.isActiveNetworkMetered());
+
+ // VPN is using Cell | WiFi.
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
+ waitForIdle();
+
+ // Expect VPN to be metered.
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ // VPN is using WiFi | Cell.
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mWiFiNetworkAgent.getNetwork(), mCellNetworkAgent.getNetwork() });
+ waitForIdle();
+
+ // Order should not matter and VPN should still be metered.
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ // VPN is not using any underlying networks.
+ mService.setUnderlyingNetworksForVpn(new Network[0]);
+ waitForIdle();
+
+ // VPN without underlying networks is treated as metered.
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ vpnNetworkAgent.disconnect();
+ mMockVpn.disconnect();
+ }
+
+ @Test
+ public void testIsActiveNetworkMeteredOverAlwaysMeteredVpn() {
+ // Returns true by default when no network is available.
+ assertTrue(mCm.isActiveNetworkMetered());
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+ assertFalse(mCm.isActiveNetworkMetered());
+
+ // Connect VPN network.
+ MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ final int uid = Process.myUid();
+ ranges.add(new UidRange(uid, uid));
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.setUids(ranges);
+ vpnNetworkAgent.connect(true);
+ mMockVpn.connectAsAlwaysMetered();
+ waitForIdle();
+ assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // VPN is tracking current platform default (WiFi).
+ mService.setUnderlyingNetworksForVpn(null);
+ waitForIdle();
+
+ // Despite VPN using WiFi (which is unmetered), VPN itself is marked as always metered.
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ // VPN explicitly declares WiFi as its underlying network.
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mWiFiNetworkAgent.getNetwork() });
+ waitForIdle();
+
+ // Doesn't really matter whether VPN declares its underlying networks explicitly.
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ // With WiFi lost, VPN is basically without any underlying networks. And in that case it is
+ // anyways suppose to be metered.
+ mWiFiNetworkAgent.disconnect();
+ waitForIdle();
+
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ vpnNetworkAgent.disconnect();
+ }
+
+ @Test
public void testNetworkBlockedStatus() {
final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
final NetworkRequest cellRequest = new NetworkRequest.Builder()
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index 295e3de..52707c2 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -196,8 +196,10 @@
if "implements" in raw:
self.implements = raw[raw.index("implements")+1]
+ self.implements_all = [self.implements]
else:
self.implements = None
+ self.implements_all = []
else:
raise ValueError("Unknown signature format: " + sig_format)
@@ -224,7 +226,7 @@
class Package():
- NAME = re.compile("package(?: .*)? ([A-Za-z.]+)")
+ NAME = re.compile("package(?: .*)? ([A-Za-z0-9.]+)")
def __init__(self, line, raw, blame):
self.line = line
@@ -364,12 +366,12 @@
self.parse_matching_paren("<", ">")
extends = self.parse_extends()
clazz.extends = extends[0] if extends else None
- implements = self.parse_implements()
- clazz.implements = implements[0] if implements else None
+ clazz.implements_all = self.parse_implements()
# The checks assume that interfaces are always found in implements, which isn't true for
# subinterfaces.
- if not implements and "interface" in clazz.split:
- clazz.implements = clazz.extends
+ if not clazz.implements_all and "interface" in clazz.split:
+ clazz.implements_all = [clazz.extends]
+ clazz.implements = clazz.implements_all[0] if clazz.implements_all else None
self.parse_token("{")
self.parse_eof()
@@ -1249,6 +1251,7 @@
def verify_collections(clazz):
"""Verifies that collection types are interfaces."""
if clazz.fullname == "android.os.Bundle": return
+ if clazz.fullname == "android.os.Parcel": return
bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
"java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
@@ -1284,9 +1287,9 @@
error(clazz, m, "S1", "Methods must not throw generic exceptions")
if t in ["android.os.RemoteException"]:
- if clazz.name == "android.content.ContentProviderClient": continue
- if clazz.name == "android.os.Binder": continue
- if clazz.name == "android.os.IBinder": continue
+ if clazz.fullname == "android.content.ContentProviderClient": continue
+ if clazz.fullname == "android.os.Binder": continue
+ if clazz.fullname == "android.os.IBinder": continue
error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
@@ -1653,8 +1656,8 @@
def verify_closable(clazz):
"""Verifies that classes are AutoClosable."""
- if clazz.implements == "java.lang.AutoCloseable": return
- if clazz.implements == "java.io.Closeable": return
+ if "java.lang.AutoCloseable" in clazz.implements_all: return
+ if "java.io.Closeable" in clazz.implements_all: return
for m in clazz.methods:
if len(m.args) > 0: continue
@@ -1853,6 +1856,9 @@
def verify_pfd(clazz):
"""Verify that android APIs use PFD over FD."""
+ if clazz.fullname == "android.os.FileUtils" or clazz.fullname == "android.system.Os":
+ return
+
examine = clazz.ctors + clazz.methods
for m in examine:
if m.typ == "java.io.FileDescriptor":
diff --git a/tools/apilint/apilint_test.py b/tools/apilint/apilint_test.py
index f34492d..5cb43db 100644
--- a/tools/apilint/apilint_test.py
+++ b/tools/apilint/apilint_test.py
@@ -242,6 +242,10 @@
cls = self._cls("class Class {")
return apilint.Field(cls, 1, raw, '', sig_format=2)
+ def test_parse_package(self):
+ pkg = apilint.Package(999, "package wifi.p2p {", None)
+ self.assertEquals("wifi.p2p", pkg.name)
+
def test_class(self):
cls = self._cls("@Deprecated @IntRange(from=1, to=2) public static abstract class Some.Name extends Super<Class> implements Interface<Class> {")
self.assertTrue('deprecated' in cls.split)