diff options
198 files changed, 7262 insertions, 4263 deletions
diff --git a/Android.bp b/Android.bp index e5d4b8b87c88..2685ac393846 100644 --- a/Android.bp +++ b/Android.bp @@ -471,8 +471,6 @@ java_library { "telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl", @@ -492,6 +490,8 @@ java_library { "telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl", "telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl", "telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl", + "telephony/java/com/android/ims/internal/IImsRegistration.aidl", + "telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl", "telephony/java/com/android/ims/internal/IImsRcsFeature.aidl", "telephony/java/com/android/ims/internal/IImsService.aidl", "telephony/java/com/android/ims/internal/IImsServiceController.aidl", diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java index 5653a039a9ed..93a0fc314b7f 100644 --- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java @@ -190,7 +190,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( + final MeasuredText text = MeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); @@ -206,7 +206,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( + final MeasuredText text = MeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); @@ -222,7 +222,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( + final MeasuredText text = MeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); @@ -238,7 +238,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( + final MeasuredText text = MeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); @@ -254,7 +254,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( + final MeasuredText text = MeasuredText.build( generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT, LTR); state.resumeTiming(); diff --git a/api/current.txt b/api/current.txt index ccb2b1f33b3d..77241da21ed1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6544,6 +6544,7 @@ package android.app.admin { method public void setTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle); method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean); method public void setUserIcon(android.content.ComponentName, android.graphics.Bitmap); + method public boolean startUserInBackground(android.content.ComponentName, android.os.UserHandle); method public boolean stopUser(android.content.ComponentName, android.os.UserHandle); method public boolean switchUser(android.content.ComponentName, android.os.UserHandle); method public void transferOwnership(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle); @@ -6656,7 +6657,6 @@ package android.app.admin { field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2 field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1 field public static final int SKIP_SETUP_WIZARD = 1; // 0x1 - field public static final int START_USER_IN_BACKGROUND = 8; // 0x8 field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1 field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2 } @@ -20882,7 +20882,6 @@ package android.inputmethodservice { method public int getMaxWidth(); method public java.lang.CharSequence getTextForImeAction(int); method public android.app.Dialog getWindow(); - method public void hideSoftInputFromInputMethod(int); method public void hideStatusIcon(); method public void hideWindow(); method public boolean isExtractViewShown(); @@ -20930,6 +20929,7 @@ package android.inputmethodservice { method public void onWindowHidden(); method public void onWindowShown(); method public void requestHideSelf(int); + method public void requestShowSelf(int); method public boolean sendDefaultEditorAction(boolean); method public void sendDownUpKeyEvents(int); method public void sendKeyChar(char); @@ -20942,7 +20942,6 @@ package android.inputmethodservice { method public void setInputMethodAndSubtype(java.lang.String, android.view.inputmethod.InputMethodSubtype); method public void setInputView(android.view.View); method public boolean shouldOfferSwitchingToNextInputMethod(); - method public void showSoftInputFromInputMethod(int); method public void showStatusIcon(int); method public void showWindow(boolean); method public void switchInputMethod(java.lang.String); @@ -26340,10 +26339,10 @@ package android.net { } public final class MacAddress implements android.os.Parcelable { - method public int addressType(); method public int describeContents(); method public static android.net.MacAddress fromBytes(byte[]); method public static android.net.MacAddress fromString(java.lang.String); + method public int getAddressType(); method public boolean isLocallyAssigned(); method public byte[] toByteArray(); method public java.lang.String toOuiString(); @@ -26353,7 +26352,6 @@ package android.net { field public static final int TYPE_BROADCAST = 3; // 0x3 field public static final int TYPE_MULTICAST = 2; // 0x2 field public static final int TYPE_UNICAST = 1; // 0x1 - field public static final int TYPE_UNKNOWN = 0; // 0x0 } public class MailTo { @@ -32449,6 +32447,7 @@ package android.os { field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user"; field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume"; field public static final java.lang.String DISALLOW_AIRPLANE_MODE = "no_airplane_mode"; + field public static final java.lang.String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display"; field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps"; field public static final java.lang.String DISALLOW_AUTOFILL = "no_autofill"; field public static final java.lang.String DISALLOW_BLUETOOTH = "no_bluetooth"; @@ -32482,6 +32481,7 @@ package android.os { field public static final java.lang.String DISALLOW_SAFE_BOOT = "no_safe_boot"; field public static final java.lang.String DISALLOW_SET_USER_ICON = "no_set_user_icon"; field public static final java.lang.String DISALLOW_SET_WALLPAPER = "no_set_wallpaper"; + field public static final java.lang.String DISALLOW_SHARE_INTO_MANAGED_PROFILE = "no_sharing_into_profile"; field public static final java.lang.String DISALLOW_SHARE_LOCATION = "no_share_location"; field public static final java.lang.String DISALLOW_SMS = "no_sms"; field public static final java.lang.String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs"; @@ -41145,6 +41145,7 @@ package android.telephony { method public void onServiceStateChanged(android.telephony.ServiceState); method public deprecated void onSignalStrengthChanged(int); method public void onSignalStrengthsChanged(android.telephony.SignalStrength); + method public void onUserMobileDataStateChanged(boolean); field public static final int LISTEN_CALL_FORWARDING_INDICATOR = 8; // 0x8 field public static final int LISTEN_CALL_STATE = 32; // 0x20 field public static final int LISTEN_CELL_INFO = 1024; // 0x400 @@ -41156,6 +41157,7 @@ package android.telephony { field public static final int LISTEN_SERVICE_STATE = 1; // 0x1 field public static final deprecated int LISTEN_SIGNAL_STRENGTH = 2; // 0x2 field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100 + field public static final int LISTEN_USER_MOBILE_DATA_STATE = 524288; // 0x80000 } public final class RadioAccessSpecifier implements android.os.Parcelable { @@ -42292,20 +42294,9 @@ package android.text { method public boolean isAllowed(char); } - public abstract interface NoCopySpan { - } - - public static class NoCopySpan.Concrete implements android.text.NoCopySpan { - ctor public NoCopySpan.Concrete(); - } - - public abstract interface ParcelableSpan implements android.os.Parcelable { - method public abstract int getSpanTypeId(); - } - - public class PremeasuredText implements android.text.Spanned { - method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic); - method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int); + public class MeasuredText implements android.text.Spanned { + method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic); + method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int); method public char charAt(int); method public int getEnd(); method public android.text.TextPaint getPaint(); @@ -42324,6 +42315,17 @@ package android.text { method public java.lang.CharSequence subSequence(int, int); } + public abstract interface NoCopySpan { + } + + public static class NoCopySpan.Concrete implements android.text.NoCopySpan { + ctor public NoCopySpan.Concrete(); + } + + public abstract interface ParcelableSpan implements android.os.Parcelable { + method public abstract int getSpanTypeId(); + } + public class Selection { method public static boolean extendDown(android.text.Spannable, android.text.Layout); method public static boolean extendLeft(android.text.Spannable, android.text.Layout); @@ -49258,14 +49260,13 @@ package android.view.textclassifier { method public android.graphics.drawable.Drawable getSecondaryIcon(int); method public android.content.Intent getSecondaryIntent(int); method public java.lang.CharSequence getSecondaryLabel(int); - method public android.view.View.OnClickListener getSecondaryOnClickListener(int); method public java.lang.String getSignature(); method public java.lang.String getText(); } public static final class TextClassification.Builder { ctor public TextClassification.Builder(); - method public android.view.textclassifier.TextClassification.Builder addSecondaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable, android.view.View.OnClickListener); + method public android.view.textclassifier.TextClassification.Builder addSecondaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable); method public android.view.textclassifier.TextClassification build(); method public android.view.textclassifier.TextClassification.Builder clearSecondaryActions(); method public android.view.textclassifier.TextClassification.Builder setEntityType(java.lang.String, float); @@ -49273,15 +49274,18 @@ package android.view.textclassifier { method public android.view.textclassifier.TextClassification.Builder setIntent(android.content.Intent); method public android.view.textclassifier.TextClassification.Builder setLabel(java.lang.String); method public android.view.textclassifier.TextClassification.Builder setOnClickListener(android.view.View.OnClickListener); - method public android.view.textclassifier.TextClassification.Builder setPrimaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable, android.view.View.OnClickListener); + method public android.view.textclassifier.TextClassification.Builder setPrimaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable); method public android.view.textclassifier.TextClassification.Builder setSignature(java.lang.String); method public android.view.textclassifier.TextClassification.Builder setText(java.lang.String); } - public static final class TextClassification.Options { + public static final class TextClassification.Options implements android.os.Parcelable { ctor public TextClassification.Options(); + method public int describeContents(); method public android.os.LocaleList getDefaultLocales(); method public android.view.textclassifier.TextClassification.Options setDefaultLocales(android.os.LocaleList); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassification.Options> CREATOR; } public final class TextClassificationManager { @@ -49311,16 +49315,22 @@ package android.view.textclassifier { field public static final java.lang.String TYPE_URL = "url"; } - public static final class TextClassifier.EntityConfig { + public static final class TextClassifier.EntityConfig implements android.os.Parcelable { ctor public TextClassifier.EntityConfig(int); + method public int describeContents(); method public android.view.textclassifier.TextClassifier.EntityConfig excludeEntities(java.lang.String...); method public java.util.List<java.lang.String> getEntities(android.view.textclassifier.TextClassifier); method public android.view.textclassifier.TextClassifier.EntityConfig includeEntities(java.lang.String...); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassifier.EntityConfig> CREATOR; } - public final class TextLinks { + public final class TextLinks implements android.os.Parcelable { method public boolean apply(android.text.SpannableString, java.util.function.Function<android.view.textclassifier.TextLinks.TextLink, android.text.style.ClickableSpan>); + method public int describeContents(); method public java.util.Collection<android.view.textclassifier.TextLinks.TextLink> getLinks(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLinks> CREATOR; } public static final class TextLinks.Builder { @@ -49329,21 +49339,27 @@ package android.view.textclassifier { method public android.view.textclassifier.TextLinks build(); } - public static final class TextLinks.Options { + public static final class TextLinks.Options implements android.os.Parcelable { ctor public TextLinks.Options(); + method public int describeContents(); method public android.os.LocaleList getDefaultLocales(); method public android.view.textclassifier.TextClassifier.EntityConfig getEntityConfig(); method public android.view.textclassifier.TextLinks.Options setDefaultLocales(android.os.LocaleList); method public android.view.textclassifier.TextLinks.Options setEntityConfig(android.view.textclassifier.TextClassifier.EntityConfig); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLinks.Options> CREATOR; } - public static final class TextLinks.TextLink { + public static final class TextLinks.TextLink implements android.os.Parcelable { ctor public TextLinks.TextLink(java.lang.String, int, int, java.util.Map<java.lang.String, java.lang.Float>); + method public int describeContents(); method public float getConfidenceScore(java.lang.String); method public int getEnd(); method public java.lang.String getEntity(int); method public int getEntityCount(); method public int getStart(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLinks.TextLink> CREATOR; } public final class TextSelection { @@ -49362,10 +49378,13 @@ package android.view.textclassifier { method public android.view.textclassifier.TextSelection.Builder setSignature(java.lang.String); } - public static final class TextSelection.Options { + public static final class TextSelection.Options implements android.os.Parcelable { ctor public TextSelection.Options(); + method public int describeContents(); method public android.os.LocaleList getDefaultLocales(); method public android.view.textclassifier.TextSelection.Options setDefaultLocales(android.os.LocaleList); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextSelection.Options> CREATOR; } } diff --git a/api/system-current.txt b/api/system-current.txt index b47304a41bc2..002c2bd9d08c 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -33,6 +33,7 @@ package android { field public static final java.lang.String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE"; field public static final java.lang.String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED"; field public static final java.lang.String BRICK = "android.permission.BRICK"; + field public static final java.lang.String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE"; field public static final deprecated java.lang.String BROADCAST_NETWORK_PRIVILEGED = "android.permission.BROADCAST_NETWORK_PRIVILEGED"; field public static final java.lang.String CALL_PRIVILEGED = "android.permission.CALL_PRIVILEGED"; field public static final java.lang.String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED"; @@ -46,6 +47,7 @@ package android { field public static final java.lang.String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION"; field public static final java.lang.String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"; field public static final java.lang.String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; + field public static final java.lang.String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; field public static final java.lang.String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL"; field public static final java.lang.String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"; field public static final java.lang.String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE"; @@ -1092,8 +1094,38 @@ package android.hardware.camera2.params { package android.hardware.display { + public final class BrightnessChangeEvent implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessChangeEvent> CREATOR; + field public final float batteryLevel; + field public final float brightness; + field public final int colorTemperature; + field public final float lastBrightness; + field public final long[] luxTimestamps; + field public final float[] luxValues; + field public final boolean nightMode; + field public final java.lang.String packageName; + field public final long timeStamp; + } + + public final class BrightnessConfiguration implements android.os.Parcelable { + method public int describeContents(); + method public android.util.Pair<float[], float[]> getCurve(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessConfiguration> CREATOR; + } + + public static class BrightnessConfiguration.Builder { + ctor public BrightnessConfiguration.Builder(); + method public android.hardware.display.BrightnessConfiguration build(); + method public android.hardware.display.BrightnessConfiguration.Builder setCurve(float[], float[]); + } + public final class DisplayManager { + method public java.util.List<android.hardware.display.BrightnessChangeEvent> getBrightnessEvents(); method public android.graphics.Point getStableDisplaySize(); + method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); } } @@ -1701,6 +1733,39 @@ package android.hardware.location { package android.hardware.radio { + public final class ProgramList implements java.lang.AutoCloseable { + method public void addOnCompleteListener(java.util.concurrent.Executor, android.hardware.radio.ProgramList.OnCompleteListener); + method public void addOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener); + method public void close(); + method public android.hardware.radio.RadioManager.ProgramInfo get(android.hardware.radio.ProgramSelector.Identifier); + method public void registerListCallback(java.util.concurrent.Executor, android.hardware.radio.ProgramList.ListCallback); + method public void registerListCallback(android.hardware.radio.ProgramList.ListCallback); + method public void removeOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener); + method public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> toList(); + method public void unregisterListCallback(android.hardware.radio.ProgramList.ListCallback); + } + + public static final class ProgramList.Filter implements android.os.Parcelable { + ctor public ProgramList.Filter(java.util.Set<java.lang.Integer>, java.util.Set<android.hardware.radio.ProgramSelector.Identifier>, boolean, boolean); + method public boolean areCategoriesIncluded(); + method public boolean areModificationsExcluded(); + method public int describeContents(); + method public java.util.Set<java.lang.Integer> getIdentifierTypes(); + method public java.util.Set<android.hardware.radio.ProgramSelector.Identifier> getIdentifiers(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramList.Filter> CREATOR; + } + + public static abstract class ProgramList.ListCallback { + ctor public ProgramList.ListCallback(); + method public void onItemChanged(android.hardware.radio.ProgramSelector.Identifier); + method public void onItemRemoved(android.hardware.radio.ProgramSelector.Identifier); + } + + public static abstract interface ProgramList.OnCompleteListener { + method public abstract void onComplete(); + } + public final class ProgramSelector implements android.os.Parcelable { ctor public ProgramSelector(int, android.hardware.radio.ProgramSelector.Identifier, android.hardware.radio.ProgramSelector.Identifier[], long[]); method public static android.hardware.radio.ProgramSelector createAmFmSelector(int, int); @@ -1724,6 +1789,7 @@ package android.hardware.radio { field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9 field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3 field public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4 + field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0 field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2 field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc @@ -1735,6 +1801,7 @@ package android.hardware.radio { field public static final int PROGRAM_TYPE_DRMO = 6; // 0x6 field public static final int PROGRAM_TYPE_FM = 2; // 0x2 field public static final int PROGRAM_TYPE_FM_HD = 4; // 0x4 + field public static final int PROGRAM_TYPE_INVALID = 0; // 0x0 field public static final int PROGRAM_TYPE_SXM = 7; // 0x7 field public static final int PROGRAM_TYPE_VENDOR_END = 1999; // 0x7cf field public static final int PROGRAM_TYPE_VENDOR_START = 1000; // 0x3e8 @@ -1953,10 +2020,11 @@ package android.hardware.radio { method public abstract void cancelAnnouncement(); method public abstract void close(); method public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]); + method public android.hardware.radio.ProgramList getDynamicProgramList(android.hardware.radio.ProgramList.Filter); method public abstract boolean getMute(); method public java.util.Map<java.lang.String, java.lang.String> getParameters(java.util.List<java.lang.String>); method public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]); - method public abstract java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(java.util.Map<java.lang.String, java.lang.String>); + method public abstract deprecated java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(java.util.Map<java.lang.String, java.lang.String>); method public abstract boolean hasControl(); method public abstract deprecated boolean isAnalogForced(); method public abstract boolean isAntennaConnected(); diff --git a/api/test-current.txt b/api/test-current.txt index b15ec4a07603..6941731c29cd 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -156,6 +156,7 @@ package android.app.admin { method public boolean isDeviceManaged(); field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED"; field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED"; + field public static final java.lang.String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED"; field public static final java.lang.String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION"; } @@ -294,6 +295,43 @@ package android.hardware.camera2 { } +package android.hardware.display { + + public final class BrightnessChangeEvent implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessChangeEvent> CREATOR; + field public final float batteryLevel; + field public final float brightness; + field public final int colorTemperature; + field public final float lastBrightness; + field public final long[] luxTimestamps; + field public final float[] luxValues; + field public final boolean nightMode; + field public final java.lang.String packageName; + field public final long timeStamp; + } + + public final class BrightnessConfiguration implements android.os.Parcelable { + method public int describeContents(); + method public android.util.Pair<float[], float[]> getCurve(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessConfiguration> CREATOR; + } + + public static class BrightnessConfiguration.Builder { + ctor public BrightnessConfiguration.Builder(); + method public android.hardware.display.BrightnessConfiguration build(); + method public android.hardware.display.BrightnessConfiguration.Builder setCurve(float[], float[]); + } + + public final class DisplayManager { + method public java.util.List<android.hardware.display.BrightnessChangeEvent> getBrightnessEvents(); + method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); + } + +} + package android.location { public final class GnssClock implements android.os.Parcelable { diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index ffe652f72256..2ebbba612317 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -186,7 +186,8 @@ LOCAL_SRC_FILES := \ tests/statsd_test_util.cpp \ tests/e2e/WakelockDuration_e2e_test.cpp \ tests/e2e/MetricConditionLink_e2e_test.cpp \ - tests/e2e/Attribution_e2e_test.cpp + tests/e2e/Attribution_e2e_test.cpp \ + tests/e2e/GaugeMetric_e2e_test.cpp LOCAL_STATIC_LIBRARIES := \ $(statsd_common_static_libraries) \ diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 09e10a13615f..301e3a535e6d 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -99,6 +99,7 @@ private: FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions); FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice); + FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); }; diff --git a/cmds/statsd/src/dimension.h b/cmds/statsd/src/dimension.h index 845c13867e5a..d0f96a2abe6b 100644 --- a/cmds/statsd/src/dimension.h +++ b/cmds/statsd/src/dimension.h @@ -32,6 +32,10 @@ namespace statsd { const DimensionsValue* getSingleLeafValue(const DimensionsValue* value); DimensionsValue getSingleLeafValue(const DimensionsValue& value); +// Appends the leaf node to the parent tree. +void appendLeafNodeToParent(const Field& field, const DimensionsValue& value, + DimensionsValue* parentValue); + // Constructs the DimensionsValue protos from the FieldMatcher. Each DimensionsValue proto // represents a tree. When the input proto has repeated fields and the input "dimensions" wants // "ANY" locations, it will return multiple trees. diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 1a4888c31fff..ae47bd898cf9 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -114,6 +114,9 @@ GaugeMetricProducer::~GaugeMetricProducer() { void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) { flushIfNeededLocked(dumpTimeNs); + ProtoOutputStream pbOutput; + onDumpReportLocked(dumpTimeNs, &pbOutput); + parseProtoOutputStream(pbOutput, report); } void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 08b0981bb14f..a0239fcd1127 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -141,6 +141,7 @@ private: FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions); FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice); + FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); }; } // namespace statsd diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h index 09a43f5c881f..cee920038eef 100644 --- a/cmds/statsd/src/stats_log_util.h +++ b/cmds/statsd/src/stats_log_util.h @@ -43,6 +43,19 @@ int64_t TimeUnitToBucketSizeInMillis(TimeUnit unit); // Helper function to write PulledAtomStats to ProtoOutputStream void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats>& pair, util::ProtoOutputStream* protoOutput); + +template<class T> +bool parseProtoOutputStream(util::ProtoOutputStream& protoOutput, T* message) { + std::string pbBytes; + auto iter = protoOutput.data(); + while (iter.readBuffer() != NULL) { + size_t toRead = iter.currentToRead(); + pbBytes.append(reinterpret_cast<const char*>(iter.readBuffer()), toRead); + iter.rp()->move(toRead); + } + return message->ParseFromArray(pbBytes.c_str(), pbBytes.size()); +} + } // namespace statsd } // namespace os } // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp new file mode 100644 index 000000000000..10a6c363eab3 --- /dev/null +++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp @@ -0,0 +1,193 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <gtest/gtest.h> + +#include "src/StatsLogProcessor.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + +#include <vector> + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +namespace { + +StatsdConfig CreateStatsdConfigForPushedEvent() { + StatsdConfig config; + *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); + + auto atomMatcher = CreateSimpleAtomMatcher("", android::util::APP_START_CHANGED); + *config.add_atom_matcher() = atomMatcher; + + auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); + *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ }); + *config.add_predicate() = isInBackgroundPredicate; + + auto gaugeMetric = config.add_gauge_metric(); + gaugeMetric->set_id(123456); + gaugeMetric->set_what(atomMatcher.id()); + gaugeMetric->set_condition(isInBackgroundPredicate.id()); + gaugeMetric->mutable_gauge_fields_filter()->set_include_all(false); + auto fieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields(); + fieldMatcher->set_field(android::util::APP_START_CHANGED); + fieldMatcher->add_child()->set_field(3); // type (enum) + fieldMatcher->add_child()->set_field(4); // activity_name(str) + fieldMatcher->add_child()->set_field(7); // activity_start_msec(int64) + *gaugeMetric->mutable_dimensions() = + CreateDimensions(android::util::APP_START_CHANGED, {1 /* uid field */ }); + gaugeMetric->set_bucket(ONE_MINUTE); + + auto links = gaugeMetric->add_links(); + links->set_condition(isInBackgroundPredicate.id()); + auto dimensionWhat = links->mutable_dimensions_in_what(); + dimensionWhat->set_field(android::util::APP_START_CHANGED); + dimensionWhat->add_child()->set_field(1); // uid field. + auto dimensionCondition = links->mutable_dimensions_in_condition(); + dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); + dimensionCondition->add_child()->set_field(1); // uid field. + return config; +} + +std::unique_ptr<LogEvent> CreateAppStartChangedEvent( + const int uid, const string& pkg_name, AppStartChanged::TransitionType type, + const string& activity_name, const string& calling_pkg_name, const bool is_instant_app, + int64_t activity_start_msec, uint64_t timestampNs) { + auto logEvent = std::make_unique<LogEvent>( + android::util::APP_START_CHANGED, timestampNs); + logEvent->write(uid); + logEvent->write(pkg_name); + logEvent->write(type); + logEvent->write(activity_name); + logEvent->write(calling_pkg_name); + logEvent->write(is_instant_app); + logEvent->write(activity_start_msec); + logEvent->init(); + return logEvent; +} + +} // namespace + +TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { + auto config = CreateStatsdConfigForPushedEvent(); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + int appUid1 = 123; + int appUid2 = 456; + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateMoveToBackgroundEvent(appUid1, bucketStartTimeNs + 15)); + events.push_back(CreateMoveToForegroundEvent(appUid1, bucketStartTimeNs + bucketSizeNs + 250)); + events.push_back(CreateMoveToBackgroundEvent(appUid1, bucketStartTimeNs + bucketSizeNs + 350)); + events.push_back(CreateMoveToForegroundEvent( + appUid1, bucketStartTimeNs + 2 * bucketSizeNs + 100)); + + + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::WARM, "activity_name1", "calling_pkg_name1", + true /*is_instant_app*/, 101 /*activity_start_msec*/, bucketStartTimeNs + 10)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::HOT, "activity_name2", "calling_pkg_name2", + true /*is_instant_app*/, 102 /*activity_start_msec*/, bucketStartTimeNs + 20)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::COLD, "activity_name3", "calling_pkg_name3", + true /*is_instant_app*/, 103 /*activity_start_msec*/, bucketStartTimeNs + 30)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::WARM, "activity_name4", "calling_pkg_name4", + true /*is_instant_app*/, 104 /*activity_start_msec*/, + bucketStartTimeNs + bucketSizeNs + 30)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::COLD, "activity_name5", "calling_pkg_name5", + true /*is_instant_app*/, 105 /*activity_start_msec*/, + bucketStartTimeNs + 2 * bucketSizeNs)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::HOT, "activity_name6", "calling_pkg_name6", + false /*is_instant_app*/, 106 /*activity_start_msec*/, + bucketStartTimeNs + 2 * bucketSizeNs + 10)); + + events.push_back(CreateMoveToBackgroundEvent(appUid2, bucketStartTimeNs + bucketSizeNs + 10)); + events.push_back(CreateAppStartChangedEvent( + appUid2, "app2", AppStartChanged::COLD, "activity_name7", "calling_pkg_name7", + true /*is_instant_app*/, 201 /*activity_start_msec*/, + bucketStartTimeNs + 2 * bucketSizeNs + 10)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 3 * bucketSizeNs, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); + EXPECT_EQ(gaugeMetrics.data_size(), 2); + + auto data = gaugeMetrics.data(0); + EXPECT_EQ(data.dimension().field(), android::util::APP_START_CHANGED); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1 /* uid field */); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid1); + EXPECT_EQ(data.bucket_info_size(), 3); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().type(), AppStartChanged::HOT); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_name(), "activity_name2"); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_start_msec(), 102L); + + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().type(), AppStartChanged::WARM); + EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().activity_name(), "activity_name4"); + EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().activity_start_msec(), 104L); + + EXPECT_EQ(data.bucket_info(2).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(2).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().type(), AppStartChanged::COLD); + EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().activity_name(), "activity_name5"); + EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().activity_start_msec(), 105L); + + data = gaugeMetrics.data(1); + EXPECT_EQ(data.dimension().field(), android::util::APP_START_CHANGED); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1 /* uid field */); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid2); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().type(), AppStartChanged::COLD); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_name(), "activity_name7"); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_start_msec(), 201L); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp index 278335674130..fcdaafce6627 100644 --- a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp @@ -26,6 +26,8 @@ namespace statsd { #ifdef __ANDROID__ +namespace { + StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) { StatsdConfig config; *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); @@ -56,6 +58,8 @@ StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) return config; } +} // namespace + TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { ConfigKey cfgKey; for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE }) { @@ -94,6 +98,7 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { auto releaseEvent2 = CreateReleaseWakelockEvent( attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 15); + std::vector<std::unique_ptr<LogEvent>> events; events.push_back(std::move(screenTurnedOnEvent)); @@ -119,18 +124,8 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { auto data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate dimension value. - EXPECT_EQ(data.dimension().field(), - android::util::WAKELOCK_STATE_CHANGED); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); - // Attribution field. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); - // Uid only. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).value_int(), 111); + ValidateAttributionUidDimension( + data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111); // Validate bucket info. EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); data = reports.reports(0).metrics(0).duration_metrics().data(0); @@ -147,18 +142,8 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate dimension value. - EXPECT_EQ(data.dimension().field(), - android::util::WAKELOCK_STATE_CHANGED); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); - // Attribution field. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); - // Uid only. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).value_int(), 111); + ValidateAttributionUidDimension( + data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111); // Two output buckets. // The wakelock holding interval in the 1st bucket starts from the screen off event and to // the end of the 1st bucket. @@ -167,6 +152,32 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and // ends at the second screen on event. EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL); + + events.clear(); + events.push_back(CreateScreenStateChangedEvent( + ScreenStateChanged::STATE_OFF, bucketStartTimeNs + 2 * bucketSizeNs + 90)); + events.push_back(CreateAcquireWakelockEvent( + attributions1, "wl3", bucketStartTimeNs + 2 * bucketSizeNs + 100)); + events.push_back(CreateReleaseWakelockEvent( + attributions1, "wl3", bucketStartTimeNs + 5 * bucketSizeNs + 100)); + sortLogEventsByTimestamp(&events); + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + reports.Clear(); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6); + data = reports.reports(0).metrics(0).duration_metrics().data(0); + ValidateAttributionUidDimension( + data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111); + // The last wakelock holding spans 4 buckets. + EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100); + EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs); + EXPECT_EQ((unsigned long long)data.bucket_info(4).duration_nanos(), bucketSizeNs); + EXPECT_EQ((unsigned long long)data.bucket_info(5).duration_nanos(), 100UL); } } diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index e7882353bec0..718b2e177d52 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -19,6 +19,14 @@ namespace android { namespace os { namespace statsd { +AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(atomId); + return atom_matcher; +} + AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name, WakelockStateChanged::State state) { AtomMatcher atom_matcher; diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h index 1bbbd9a30540..7eb93b9d5fcd 100644 --- a/cmds/statsd/tests/statsd_test_util.h +++ b/cmds/statsd/tests/statsd_test_util.h @@ -23,6 +23,9 @@ namespace android { namespace os { namespace statsd { +// Create AtomMatcher proto to simply match a specific atom type. +AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId); + // Create AtomMatcher proto for acquiring wakelock. AtomMatcher CreateAcquireWakelockAtomMatcher(); diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 60a5a110193a..972ffcbf527f 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -319,4 +319,9 @@ public abstract class ActivityManagerInternal { } public abstract void registerScreenObserver(ScreenObserver observer); + + /** + * Returns if more users can be started without stopping currently running users. + */ + public abstract boolean canStartMoreUsers(); } diff --git a/core/java/android/app/admin/ConnectEvent.java b/core/java/android/app/admin/ConnectEvent.java index f06a9257b7f8..d511c57b1f51 100644 --- a/core/java/android/app/admin/ConnectEvent.java +++ b/core/java/android/app/admin/ConnectEvent.java @@ -68,7 +68,7 @@ public final class ConnectEvent extends NetworkEvent implements Parcelable { @Override public String toString() { - return String.format("ConnectEvent(%s, %d, %d, %s)", mIpAddress, mPort, mTimestamp, + return String.format("ConnectEvent(%d, %s, %d, %d, %s)", mId, mIpAddress, mPort, mTimestamp, mPackageName); } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index ab85fdce6ce8..e334aab7005e 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1254,6 +1254,26 @@ public class DevicePolicyManager { = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED"; /** + * Broadcast action to notify ManagedProvisioning that + * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction has changed. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DATA_SHARING_RESTRICTION_CHANGED = + "android.app.action.DATA_SHARING_RESTRICTION_CHANGED"; + + /** + * Broadcast action from ManagedProvisioning to notify that the latest change to + * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction has been successfully + * applied (cross profile intent filters updated). Only usesd for CTS tests. + * @hide + */ + @TestApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = + "android.app.action.DATA_SHARING_RESTRICTION_APPLIED"; + + /** * Permission policy to prompt user for new permission requests for runtime permissions. * Already granted or denied permissions are not affected by this. */ @@ -6057,6 +6077,13 @@ public class DevicePolicyManager { * Called by a profile owner of a managed profile to remove the cross-profile intent filters * that go from the managed profile to the parent, or from the parent to the managed profile. * Only removes those that have been set by the profile owner. + * <p> + * <em>Note</em>: A list of default cross profile intent filters are set up by the system when + * the profile is created, some of them ensure the proper functioning of the profile, while + * others enable sharing of data from the parent to the managed profile for user convenience. + * These default intent filters are not cleared when this API is called. If the default cross + * profile data sharing is not desired, they can be disabled with + * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE}. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @throws SecurityException if {@code admin} is not a device or profile owner. @@ -6479,12 +6506,6 @@ public class DevicePolicyManager { public static final int MAKE_USER_DEMO = 0x0004; /** - * Flag used by {@link #createAndManageUser} to specify that the newly created user should be - * started in the background as part of the user creation. - */ - public static final int START_USER_IN_BACKGROUND = 0x0008; - - /** * Flag used by {@link #createAndManageUser} to specify that the newly created user should skip * the disabling of system apps during provisioning. */ @@ -6497,7 +6518,6 @@ public class DevicePolicyManager { SKIP_SETUP_WIZARD, MAKE_USER_EPHEMERAL, MAKE_USER_DEMO, - START_USER_IN_BACKGROUND, LEAVE_ALL_SYSTEM_APPS_ENABLED }) @Retention(RetentionPolicy.SOURCE) @@ -6526,7 +6546,8 @@ public class DevicePolicyManager { * IllegalArgumentException is thrown. * @param adminExtras Extras that will be passed to onEnable of the admin receiver on the new * user. - * @param flags {@link #SKIP_SETUP_WIZARD} is supported. + * @param flags {@link #SKIP_SETUP_WIZARD}, {@link #MAKE_USER_EPHEMERAL} and + * {@link #LEAVE_ALL_SYSTEM_APPS_ENABLED} are supported. * @see UserHandle * @return the {@link android.os.UserHandle} object for the created user, or {@code null} if the * user could not be created. @@ -6545,8 +6566,8 @@ public class DevicePolicyManager { } /** - * Called by a device owner to remove a user and all associated data. The primary user can not - * be removed. + * Called by a device owner to remove a user/profile and all associated data. The primary user + * can not be removed. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param userHandle the user to remove. @@ -6563,14 +6584,14 @@ public class DevicePolicyManager { } /** - * Called by a device owner to switch the specified user to the foreground. - * <p> This cannot be used to switch to a managed profile. + * Called by a device owner to switch the specified secondary user to the foreground. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param userHandle the user to switch to; null will switch to primary. * @return {@code true} if the switch was successful, {@code false} otherwise. * @throws SecurityException if {@code admin} is not a device owner. * @see Intent#ACTION_USER_FOREGROUND + * @see #getSecondaryUsers(ComponentName) */ public boolean switchUser(@NonNull ComponentName admin, @Nullable UserHandle userHandle) { throwIfParentInstance("switchUser"); @@ -6582,13 +6603,32 @@ public class DevicePolicyManager { } /** + * Called by a device owner to start the specified secondary user in background. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param userHandle the user to be stopped. + * @return {@code true} if the user can be started, {@code false} otherwise. + * @throws SecurityException if {@code admin} is not a device owner. + * @see #getSecondaryUsers(ComponentName) + */ + public boolean startUserInBackground( + @NonNull ComponentName admin, @NonNull UserHandle userHandle) { + throwIfParentInstance("startUserInBackground"); + try { + return mService.startUserInBackground(admin, userHandle); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Called by a device owner to stop the specified secondary user. - * <p> This cannot be used to stop the primary user or a managed profile. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param userHandle the user to be stopped. * @return {@code true} if the user can be stopped, {@code false} otherwise. * @throws SecurityException if {@code admin} is not a device owner. + * @see #getSecondaryUsers(ComponentName) */ public boolean stopUser(@NonNull ComponentName admin, @NonNull UserHandle userHandle) { throwIfParentInstance("stopUser"); @@ -6600,14 +6640,13 @@ public class DevicePolicyManager { } /** - * Called by a profile owner that is affiliated with the device to stop the calling user - * and switch back to primary. - * <p> This has no effect when called on a managed profile. + * Called by a profile owner of secondary user that is affiliated with the device to stop the + * calling user and switch back to primary. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @return {@code true} if the exit was successful, {@code false} otherwise. * @throws SecurityException if {@code admin} is not a profile owner affiliated with the device. - * @see #isAffiliatedUser + * @see #getSecondaryUsers(ComponentName) */ public boolean logoutUser(@NonNull ComponentName admin) { throwIfParentInstance("logoutUser"); @@ -6619,17 +6658,18 @@ public class DevicePolicyManager { } /** - * Called by a device owner to list all secondary users on the device, excluding managed - * profiles. + * Called by a device owner to list all secondary users on the device. Managed profiles are not + * considered as secondary users. * <p> Used for various user management APIs, including {@link #switchUser}, {@link #removeUser} * and {@link #stopUser}. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @return list of other {@link UserHandle}s on the device. * @throws SecurityException if {@code admin} is not a device owner. - * @see #switchUser - * @see #removeUser - * @see #stopUser + * @see #removeUser(ComponentName, UserHandle) + * @see #switchUser(ComponentName, UserHandle) + * @see #startUserInBackground(ComponentName, UserHandle) + * @see #stopUser(ComponentName, UserHandle) */ public List<UserHandle> getSecondaryUsers(@NonNull ComponentName admin) { throwIfParentInstance("getSecondaryUsers"); diff --git a/core/java/android/app/admin/DnsEvent.java b/core/java/android/app/admin/DnsEvent.java index 4ddf13e07344..a2d704b86649 100644 --- a/core/java/android/app/admin/DnsEvent.java +++ b/core/java/android/app/admin/DnsEvent.java @@ -96,7 +96,7 @@ public final class DnsEvent extends NetworkEvent implements Parcelable { @Override public String toString() { - return String.format("DnsEvent(%s, %s, %d, %d, %s)", mHostname, + return String.format("DnsEvent(%d, %s, %s, %d, %d, %s)", mId, mHostname, (mIpAddresses == null) ? "NONE" : String.join(" ", mIpAddresses), mIpAddressesCount, mTimestamp, mPackageName); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 1d8ddeebcc26..7154053f593c 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -226,6 +226,7 @@ interface IDevicePolicyManager { UserHandle createAndManageUser(in ComponentName who, in String name, in ComponentName profileOwner, in PersistableBundle adminExtras, in int flags); boolean removeUser(in ComponentName who, in UserHandle userHandle); boolean switchUser(in ComponentName who, in UserHandle userHandle); + boolean startUserInBackground(in ComponentName who, in UserHandle userHandle); boolean stopUser(in ComponentName who, in UserHandle userHandle); boolean logoutUser(in ComponentName who); List<UserHandle> getSecondaryUsers(in ComponentName who); diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java index da81d19ca3cb..3558e3430470 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -55,6 +55,22 @@ public class BackupTransport { // Transport should ignore its own moratoriums for call with this flag set. public static final int FLAG_USER_INITIATED = 1; + /** + * For key value backup, indicates that the backup data is a diff from a previous backup. The + * transport must apply this diff to an existing backup to build the new backup set. + * + * @hide + */ + public static final int FLAG_INCREMENTAL = 1 << 1; + + /** + * For key value backup, indicates that the backup data is a complete set, not a diff from a + * previous backup. The transport should clear any previous backup when storing this backup. + * + * @hide + */ + public static final int FLAG_NON_INCREMENTAL = 1 << 2; + IBackupTransport mBinderImpl = new TransportImpl(); public IBinder getBinder() { @@ -231,12 +247,18 @@ public class BackupTransport { * {@link #TRANSPORT_OK}, {@link #finishBackup} will then be called to ensure the data * is sent and recorded successfully. * + * If the backup data is a diff against the previous backup then the flag {@link + * BackupTransport#FLAG_INCREMENTAL} will be set. Otherwise, if the data is a complete backup + * set then {@link BackupTransport#FLAG_NON_INCREMENTAL} will be set. Before P neither flag will + * be set regardless of whether the backup is incremental or not. + * * @param packageInfo The identity of the application whose data is being backed up. * This specifically includes the signature list for the package. * @param inFd Descriptor of file with data that resulted from invoking the application's * BackupService.doBackup() method. This may be a pipe rather than a file on * persistent media, so it may not be seekable. - * @param flags {@link BackupTransport#FLAG_USER_INITIATED} or 0. + * @param flags a combination of {@link BackupTransport#FLAG_USER_INITIATED}, {@link + * BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL}, or 0. * @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far), * {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this * specific package, but allow others to proceed), diff --git a/core/java/android/hardware/display/BrightnessChangeEvent.java b/core/java/android/hardware/display/BrightnessChangeEvent.java index 0a08353cbe4c..2301824cb9dc 100644 --- a/core/java/android/hardware/display/BrightnessChangeEvent.java +++ b/core/java/android/hardware/display/BrightnessChangeEvent.java @@ -16,6 +16,8 @@ package android.hardware.display; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -23,51 +25,65 @@ import android.os.Parcelable; * Data about a brightness settings change. * * {@see DisplayManager.getBrightnessEvents()} - * TODO make this SystemAPI * @hide */ +@SystemApi +@TestApi public final class BrightnessChangeEvent implements Parcelable { /** Brightness in nits */ - public float brightness; + public final float brightness; /** Timestamp of the change {@see System.currentTimeMillis()} */ - public long timeStamp; + public final long timeStamp; /** Package name of focused activity when brightness was changed. * This will be null if the caller of {@see DisplayManager.getBrightnessEvents()} * does not have access to usage stats {@see UsageStatsManager} */ - public String packageName; + public final String packageName; /** User id of of the user running when brightness was changed. * @hide */ - public int userId; + public final int userId; /** Lux values of recent sensor data */ - public float[] luxValues; + public final float[] luxValues; /** Timestamps of the lux sensor readings {@see System.currentTimeMillis()} */ - public long[] luxTimestamps; + public final long[] luxTimestamps; /** Most recent battery level when brightness was changed or Float.NaN */ - public float batteryLevel; + public final float batteryLevel; /** Color filter active to provide night mode */ - public boolean nightMode; + public final boolean nightMode; /** If night mode color filter is active this will be the temperature in kelvin */ - public int colorTemperature; + public final int colorTemperature; - /** Brightness level before slider adjustment */ - public float lastBrightness; + /** Brightness le vel before slider adjustment */ + public final float lastBrightness; - public BrightnessChangeEvent() { + /** @hide */ + private BrightnessChangeEvent(float brightness, long timeStamp, String packageName, + int userId, float[] luxValues, long[] luxTimestamps, float batteryLevel, + boolean nightMode, int colorTemperature, float lastBrightness) { + this.brightness = brightness; + this.timeStamp = timeStamp; + this.packageName = packageName; + this.userId = userId; + this.luxValues = luxValues; + this.luxTimestamps = luxTimestamps; + this.batteryLevel = batteryLevel; + this.nightMode = nightMode; + this.colorTemperature = colorTemperature; + this.lastBrightness = lastBrightness; } /** @hide */ - public BrightnessChangeEvent(BrightnessChangeEvent other) { + public BrightnessChangeEvent(BrightnessChangeEvent other, boolean redactPackage) { this.brightness = other.brightness; this.timeStamp = other.timeStamp; - this.packageName = other.packageName; + this.packageName = redactPackage ? null : other.packageName; this.userId = other.userId; this.luxValues = other.luxValues; this.luxTimestamps = other.luxTimestamps; @@ -118,4 +134,85 @@ public final class BrightnessChangeEvent implements Parcelable { dest.writeInt(colorTemperature); dest.writeFloat(lastBrightness); } + + /** @hide */ + public static class Builder { + private float mBrightness; + private long mTimeStamp; + private String mPackageName; + private int mUserId; + private float[] mLuxValues; + private long[] mLuxTimestamps; + private float mBatteryLevel; + private boolean mNightMode; + private int mColorTemperature; + private float mLastBrightness; + + /** {@see BrightnessChangeEvent#brightness} */ + public Builder setBrightness(float brightness) { + mBrightness = brightness; + return this; + } + + /** {@see BrightnessChangeEvent#timeStamp} */ + public Builder setTimeStamp(long timeStamp) { + mTimeStamp = timeStamp; + return this; + } + + /** {@see BrightnessChangeEvent#packageName} */ + public Builder setPackageName(String packageName) { + mPackageName = packageName; + return this; + } + + /** {@see BrightnessChangeEvent#userId} */ + public Builder setUserId(int userId) { + mUserId = userId; + return this; + } + + /** {@see BrightnessChangeEvent#luxValues} */ + public Builder setLuxValues(float[] luxValues) { + mLuxValues = luxValues; + return this; + } + + /** {@see BrightnessChangeEvent#luxTimestamps} */ + public Builder setLuxTimestamps(long[] luxTimestamps) { + mLuxTimestamps = luxTimestamps; + return this; + } + + /** {@see BrightnessChangeEvent#batteryLevel} */ + public Builder setBatteryLevel(float batteryLevel) { + mBatteryLevel = batteryLevel; + return this; + } + + /** {@see BrightnessChangeEvent#nightMode} */ + public Builder setNightMode(boolean nightMode) { + mNightMode = nightMode; + return this; + } + + /** {@see BrightnessChangeEvent#colorTemperature} */ + public Builder setColorTemperature(int colorTemperature) { + mColorTemperature = colorTemperature; + return this; + } + + /** {@see BrightnessChangeEvent#lastBrightness} */ + public Builder setLastBrightness(float lastBrightness) { + mLastBrightness = lastBrightness; + return this; + } + + /** Builds a BrightnessChangeEvent */ + public BrightnessChangeEvent build() { + return new BrightnessChangeEvent(mBrightness, mTimeStamp, + mPackageName, mUserId, mLuxValues, mLuxTimestamps, mBatteryLevel, + mNightMode, mColorTemperature, mLastBrightness); + } + } } diff --git a/core/java/android/hardware/display/BrightnessConfiguration.java b/core/java/android/hardware/display/BrightnessConfiguration.java index 6c3be816e87b..2156491a3e24 100644 --- a/core/java/android/hardware/display/BrightnessConfiguration.java +++ b/core/java/android/hardware/display/BrightnessConfiguration.java @@ -16,6 +16,8 @@ package android.hardware.display; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; import android.util.Pair; @@ -25,6 +27,8 @@ import com.android.internal.util.Preconditions; import java.util.Arrays; /** @hide */ +@SystemApi +@TestApi public final class BrightnessConfiguration implements Parcelable { private final float[] mLux; private final float[] mNits; diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 97e9b9c2e2f4..76ab35d02c95 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.app.KeyguardManager; import android.content.Context; import android.graphics.Point; @@ -622,6 +623,8 @@ public final class DisplayManager { * Fetch {@link BrightnessChangeEvent}s. * @hide until we make it a system api. */ + @SystemApi + @TestApi @RequiresPermission(Manifest.permission.BRIGHTNESS_SLIDER_USAGE) public List<BrightnessChangeEvent> getBrightnessEvents() { return mGlobal.getBrightnessEvents(mContext.getOpPackageName()); @@ -632,6 +635,9 @@ public final class DisplayManager { * * @hide */ + @SystemApi + @TestApi + @RequiresPermission(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(BrightnessConfiguration c) { setBrightnessConfigurationForUser(c, UserHandle.myUserId()); } diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl index ca380769954b..bf5e391794f5 100644 --- a/core/java/android/hardware/radio/ITuner.aidl +++ b/core/java/android/hardware/radio/ITuner.aidl @@ -17,6 +17,7 @@ package android.hardware.radio; import android.graphics.Bitmap; +import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; @@ -73,14 +74,8 @@ interface ITuner { */ boolean startBackgroundScan(); - /** - * @param vendorFilter Vendor-specific filter, must be Map<String, String> - * @return the list, or null if scan is in progress - * @throws IllegalArgumentException if invalid arguments are passed - * @throws IllegalStateException if the scan has not been started, client may - * call startBackgroundScan to fix this. - */ - List<RadioManager.ProgramInfo> getProgramList(in Map vendorFilter); + void startProgramListUpdates(in ProgramList.Filter filter); + void stopProgramListUpdates(); boolean isConfigFlagSupported(int flag); boolean isConfigFlagSet(int flag); diff --git a/core/java/android/hardware/radio/ITunerCallback.aidl b/core/java/android/hardware/radio/ITunerCallback.aidl index 775e25c7e7cf..54af30fcc35e 100644 --- a/core/java/android/hardware/radio/ITunerCallback.aidl +++ b/core/java/android/hardware/radio/ITunerCallback.aidl @@ -16,6 +16,7 @@ package android.hardware.radio; +import android.hardware.radio.ProgramList; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; @@ -30,6 +31,7 @@ oneway interface ITunerCallback { void onBackgroundScanAvailabilityChange(boolean isAvailable); void onBackgroundScanComplete(); void onProgramListChanged(); + void onProgramListUpdated(in ProgramList.Chunk chunk); /** * @param parameters Vendor-specific key-value pairs, must be Map<String, String> diff --git a/core/java/android/hardware/radio/ProgramList.aidl b/core/java/android/hardware/radio/ProgramList.aidl new file mode 100644 index 000000000000..34b7f97558c7 --- /dev/null +++ b/core/java/android/hardware/radio/ProgramList.aidl @@ -0,0 +1,23 @@ +/** + * 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.hardware.radio; + +/** @hide */ +parcelable ProgramList.Filter; + +/** @hide */ +parcelable ProgramList.Chunk; diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java new file mode 100644 index 000000000000..b2aa9ba532a9 --- /dev/null +++ b/core/java/android/hardware/radio/ProgramList.java @@ -0,0 +1,427 @@ +/** + * 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.hardware.radio; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.stream.Collectors; + +/** + * @hide + */ +@SystemApi +public final class ProgramList implements AutoCloseable { + + private final Object mLock = new Object(); + private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mPrograms = + new HashMap<>(); + + private final List<ListCallback> mListCallbacks = new ArrayList<>(); + private final List<OnCompleteListener> mOnCompleteListeners = new ArrayList<>(); + private OnCloseListener mOnCloseListener; + private boolean mIsClosed = false; + private boolean mIsComplete = false; + + ProgramList() {} + + /** + * Callback for list change operations. + */ + public abstract static class ListCallback { + /** + * Called when item was modified or added to the list. + */ + public void onItemChanged(@NonNull ProgramSelector.Identifier id) { } + + /** + * Called when item was removed from the list. + */ + public void onItemRemoved(@NonNull ProgramSelector.Identifier id) { } + } + + /** + * Listener of list complete event. + */ + public interface OnCompleteListener { + /** + * Called when the list turned complete (i.e. when the scan process + * came to an end). + */ + void onComplete(); + } + + interface OnCloseListener { + void onClose(); + } + + /** + * Registers list change callback with executor. + */ + public void registerListCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull ListCallback callback) { + registerListCallback(new ListCallback() { + public void onItemChanged(@NonNull ProgramSelector.Identifier id) { + executor.execute(() -> callback.onItemChanged(id)); + } + + public void onItemRemoved(@NonNull ProgramSelector.Identifier id) { + executor.execute(() -> callback.onItemRemoved(id)); + } + }); + } + + /** + * Registers list change callback. + */ + public void registerListCallback(@NonNull ListCallback callback) { + synchronized (mLock) { + if (mIsClosed) return; + mListCallbacks.add(Objects.requireNonNull(callback)); + } + } + + /** + * Unregisters list change callback. + */ + public void unregisterListCallback(@NonNull ListCallback callback) { + synchronized (mLock) { + if (mIsClosed) return; + mListCallbacks.remove(Objects.requireNonNull(callback)); + } + } + + /** + * Adds list complete event listener with executor. + */ + public void addOnCompleteListener(@NonNull @CallbackExecutor Executor executor, + @NonNull OnCompleteListener listener) { + addOnCompleteListener(() -> executor.execute(listener::onComplete)); + } + + /** + * Adds list complete event listener. + */ + public void addOnCompleteListener(@NonNull OnCompleteListener listener) { + synchronized (mLock) { + if (mIsClosed) return; + mOnCompleteListeners.add(Objects.requireNonNull(listener)); + if (mIsComplete) listener.onComplete(); + } + } + + /** + * Removes list complete event listener. + */ + public void removeOnCompleteListener(@NonNull OnCompleteListener listener) { + synchronized (mLock) { + if (mIsClosed) return; + mOnCompleteListeners.remove(Objects.requireNonNull(listener)); + } + } + + void setOnCloseListener(@Nullable OnCloseListener listener) { + synchronized (mLock) { + if (mOnCloseListener != null) { + throw new IllegalStateException("Close callback is already set"); + } + mOnCloseListener = listener; + } + } + + /** + * Disables list updates and releases all resources. + */ + public void close() { + synchronized (mLock) { + if (mIsClosed) return; + mIsClosed = true; + mPrograms.clear(); + mListCallbacks.clear(); + mOnCompleteListeners.clear(); + if (mOnCloseListener != null) { + mOnCloseListener.onClose(); + mOnCloseListener = null; + } + } + } + + void apply(@NonNull Chunk chunk) { + synchronized (mLock) { + if (mIsClosed) return; + + mIsComplete = false; + + if (chunk.isPurge()) { + new HashSet<>(mPrograms.keySet()).stream().forEach(id -> removeLocked(id)); + } + + chunk.getRemoved().stream().forEach(id -> removeLocked(id)); + chunk.getModified().stream().forEach(info -> putLocked(info)); + + if (chunk.isComplete()) { + mIsComplete = true; + mOnCompleteListeners.forEach(cb -> cb.onComplete()); + } + } + } + + private void putLocked(@NonNull RadioManager.ProgramInfo value) { + ProgramSelector.Identifier key = value.getSelector().getPrimaryId(); + mPrograms.put(Objects.requireNonNull(key), value); + ProgramSelector.Identifier sel = value.getSelector().getPrimaryId(); + mListCallbacks.forEach(cb -> cb.onItemChanged(sel)); + } + + private void removeLocked(@NonNull ProgramSelector.Identifier key) { + RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key)); + if (removed == null) return; + ProgramSelector.Identifier sel = removed.getSelector().getPrimaryId(); + mListCallbacks.forEach(cb -> cb.onItemRemoved(sel)); + } + + /** + * Converts the program list in its current shape to the static List<>. + * + * @return the new List<> object; it won't receive any further updates + */ + public @NonNull List<RadioManager.ProgramInfo> toList() { + synchronized (mLock) { + return mPrograms.values().stream().collect(Collectors.toList()); + } + } + + /** + * Returns the program with a specified primary identifier. + * + * @param id primary identifier of a program to fetch + * @return the program info, or null if there is no such program on the list + */ + public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) { + synchronized (mLock) { + return mPrograms.get(Objects.requireNonNull(id)); + } + } + + /** + * Filter for the program list. + */ + public static final class Filter implements Parcelable { + private final @NonNull Set<Integer> mIdentifierTypes; + private final @NonNull Set<ProgramSelector.Identifier> mIdentifiers; + private final boolean mIncludeCategories; + private final boolean mExcludeModifications; + private final @Nullable Map<String, String> mVendorFilter; + + /** + * Constructor of program list filter. + * + * Arrays passed to this constructor become owned by this object, do not modify them later. + * + * @param identifierTypes see getIdentifierTypes() + * @param identifiers see getIdentifiers() + * @param includeCategories see areCategoriesIncluded() + * @param excludeModifications see areModificationsExcluded() + */ + public Filter(@NonNull Set<Integer> identifierTypes, + @NonNull Set<ProgramSelector.Identifier> identifiers, + boolean includeCategories, boolean excludeModifications) { + mIdentifierTypes = Objects.requireNonNull(identifierTypes); + mIdentifiers = Objects.requireNonNull(identifiers); + mIncludeCategories = includeCategories; + mExcludeModifications = excludeModifications; + mVendorFilter = null; + } + + /** + * @hide for framework use only + */ + public Filter(@Nullable Map<String, String> vendorFilter) { + mIdentifierTypes = Collections.emptySet(); + mIdentifiers = Collections.emptySet(); + mIncludeCategories = false; + mExcludeModifications = false; + mVendorFilter = vendorFilter; + } + + private Filter(@NonNull Parcel in) { + mIdentifierTypes = Utils.createIntSet(in); + mIdentifiers = Utils.createSet(in, ProgramSelector.Identifier.CREATOR); + mIncludeCategories = in.readByte() != 0; + mExcludeModifications = in.readByte() != 0; + mVendorFilter = Utils.readStringMap(in); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + Utils.writeIntSet(dest, mIdentifierTypes); + Utils.writeSet(dest, mIdentifiers); + dest.writeByte((byte) (mIncludeCategories ? 1 : 0)); + dest.writeByte((byte) (mExcludeModifications ? 1 : 0)); + Utils.writeStringMap(dest, mVendorFilter); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<Filter> CREATOR = new Parcelable.Creator<Filter>() { + public Filter createFromParcel(Parcel in) { + return new Filter(in); + } + + public Filter[] newArray(int size) { + return new Filter[size]; + } + }; + + /** + * @hide for framework use only + */ + public Map<String, String> getVendorFilter() { + return mVendorFilter; + } + + /** + * Returns the list of identifier types that satisfy the filter. + * + * If the program list entry contains at least one identifier of the type + * listed, it satisfies this condition. + * + * Empty list means no filtering on identifier type. + * + * @return the list of accepted identifier types, must not be modified + */ + public @NonNull Set<Integer> getIdentifierTypes() { + return mIdentifierTypes; + } + + /** + * Returns the list of identifiers that satisfy the filter. + * + * If the program list entry contains at least one listed identifier, + * it satisfies this condition. + * + * Empty list means no filtering on identifier. + * + * @return the list of accepted identifiers, must not be modified + */ + public @NonNull Set<ProgramSelector.Identifier> getIdentifiers() { + return mIdentifiers; + } + + /** + * Checks, if non-tunable entries that define tree structure on the + * program list (i.e. DAB ensembles) should be included. + */ + public boolean areCategoriesIncluded() { + return mIncludeCategories; + } + + /** + * Checks, if updates on entry modifications should be disabled. + * + * If true, 'modified' vector of ProgramListChunk must contain list + * additions only. Once the program is added to the list, it's not + * updated anymore. + */ + public boolean areModificationsExcluded() { + return mExcludeModifications; + } + } + + /** + * @hide This is a transport class used for internal communication between + * Broadcast Radio Service and RadioManager. + * Do not use it directly. + */ + public static final class Chunk implements Parcelable { + private final boolean mPurge; + private final boolean mComplete; + private final @NonNull Set<RadioManager.ProgramInfo> mModified; + private final @NonNull Set<ProgramSelector.Identifier> mRemoved; + + public Chunk(boolean purge, boolean complete, + @Nullable Set<RadioManager.ProgramInfo> modified, + @Nullable Set<ProgramSelector.Identifier> removed) { + mPurge = purge; + mComplete = complete; + mModified = (modified != null) ? modified : Collections.emptySet(); + mRemoved = (removed != null) ? removed : Collections.emptySet(); + } + + private Chunk(@NonNull Parcel in) { + mPurge = in.readByte() != 0; + mComplete = in.readByte() != 0; + mModified = Utils.createSet(in, RadioManager.ProgramInfo.CREATOR); + mRemoved = Utils.createSet(in, ProgramSelector.Identifier.CREATOR); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte((byte) (mPurge ? 1 : 0)); + dest.writeByte((byte) (mComplete ? 1 : 0)); + Utils.writeSet(dest, mModified); + Utils.writeSet(dest, mRemoved); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<Chunk> CREATOR = new Parcelable.Creator<Chunk>() { + public Chunk createFromParcel(Parcel in) { + return new Chunk(in); + } + + public Chunk[] newArray(int size) { + return new Chunk[size]; + } + }; + + public boolean isPurge() { + return mPurge; + } + + public boolean isComplete() { + return mComplete; + } + + public @NonNull Set<RadioManager.ProgramInfo> getModified() { + return mModified; + } + + public @NonNull Set<ProgramSelector.Identifier> getRemoved() { + return mRemoved; + } + } +} diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java index 2211cee9b315..3556751f4af4 100644 --- a/core/java/android/hardware/radio/ProgramSelector.java +++ b/core/java/android/hardware/radio/ProgramSelector.java @@ -59,6 +59,7 @@ import java.util.stream.Stream; */ @SystemApi public final class ProgramSelector implements Parcelable { + public static final int PROGRAM_TYPE_INVALID = 0; /** Analogue AM radio (with or without RDS). */ public static final int PROGRAM_TYPE_AM = 1; /** analogue FM radio (with or without RDS). */ @@ -77,6 +78,7 @@ public final class ProgramSelector implements Parcelable { public static final int PROGRAM_TYPE_VENDOR_START = 1000; public static final int PROGRAM_TYPE_VENDOR_END = 1999; @IntDef(prefix = { "PROGRAM_TYPE_" }, value = { + PROGRAM_TYPE_INVALID, PROGRAM_TYPE_AM, PROGRAM_TYPE_FM, PROGRAM_TYPE_AM_HD, @@ -89,6 +91,7 @@ public final class ProgramSelector implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface ProgramType {} + public static final int IDENTIFIER_TYPE_INVALID = 0; /** kHz */ public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1; /** 16bit */ @@ -148,6 +151,7 @@ public final class ProgramSelector implements Parcelable { public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = PROGRAM_TYPE_VENDOR_START; public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = PROGRAM_TYPE_VENDOR_END; @IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = { + IDENTIFIER_TYPE_INVALID, IDENTIFIER_TYPE_AMFM_FREQUENCY, IDENTIFIER_TYPE_RDS_PI, IDENTIFIER_TYPE_HD_STATION_ID_EXT, @@ -268,7 +272,7 @@ public final class ProgramSelector implements Parcelable { * Vendor identifiers are passed as-is to the HAL implementation, * preserving elements order. * - * @return a array of vendor identifiers, must not be modified. + * @return an array of vendor identifiers, must not be modified. */ public @NonNull long[] getVendorIds() { return mVendorIds; diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index b740f1430157..56668ac00527 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -185,25 +185,6 @@ public class RadioManager { @Retention(RetentionPolicy.SOURCE) public @interface ConfigFlag {} - private static void writeStringMap(@NonNull Parcel dest, @NonNull Map<String, String> map) { - dest.writeInt(map.size()); - for (Map.Entry<String, String> entry : map.entrySet()) { - dest.writeString(entry.getKey()); - dest.writeString(entry.getValue()); - } - } - - private static @NonNull Map<String, String> readStringMap(@NonNull Parcel in) { - int size = in.readInt(); - Map<String, String> map = new HashMap<>(); - while (size-- > 0) { - String key = in.readString(); - String value = in.readString(); - map.put(key, value); - } - return map; - } - /***************************************************************************** * Lists properties, options and radio bands supported by a given broadcast radio module. * Each module has a unique ID used to address it when calling RadioManager APIs. @@ -415,7 +396,7 @@ public class RadioManager { mIsBgScanSupported = in.readInt() == 1; mSupportedProgramTypes = arrayToSet(in.createIntArray()); mSupportedIdentifierTypes = arrayToSet(in.createIntArray()); - mVendorInfo = readStringMap(in); + mVendorInfo = Utils.readStringMap(in); } public static final Parcelable.Creator<ModuleProperties> CREATOR @@ -445,7 +426,7 @@ public class RadioManager { dest.writeInt(mIsBgScanSupported ? 1 : 0); dest.writeIntArray(setToArray(mSupportedProgramTypes)); dest.writeIntArray(setToArray(mSupportedIdentifierTypes)); - writeStringMap(dest, mVendorInfo); + Utils.writeStringMap(dest, mVendorInfo); } @Override @@ -1410,7 +1391,7 @@ public class RadioManager { private static final int FLAG_TRAFFIC_ANNOUNCEMENT = 1 << 3; @NonNull private final ProgramSelector mSelector; - private final boolean mTuned; + private final boolean mTuned; // TODO(b/69958777): replace with mFlags private final boolean mStereo; private final boolean mDigital; private final int mFlags; @@ -1418,7 +1399,8 @@ public class RadioManager { private final RadioMetadata mMetadata; @NonNull private final Map<String, String> mVendorInfo; - ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo, + /** @hide */ + public ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo, boolean digital, int signalStrength, RadioMetadata metadata, int flags, Map<String, String> vendorInfo) { mSelector = selector; @@ -1564,7 +1546,7 @@ public class RadioManager { mMetadata = null; } mFlags = in.readInt(); - mVendorInfo = readStringMap(in); + mVendorInfo = Utils.readStringMap(in); } public static final Parcelable.Creator<ProgramInfo> CREATOR @@ -1592,7 +1574,7 @@ public class RadioManager { mMetadata.writeToParcel(dest, flags); } dest.writeInt(mFlags); - writeStringMap(dest, mVendorInfo); + Utils.writeStringMap(dest, mVendorInfo); } @Override @@ -1727,7 +1709,8 @@ public class RadioManager { Log.e(TAG, "Failed to open tuner"); return null; } - return new TunerAdapter(tuner, config != null ? config.getType() : BAND_INVALID); + return new TunerAdapter(tuner, halCallback, + config != null ? config.getType() : BAND_INVALID); } @NonNull private final Context mContext; diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java index 0d367e787122..ed20c4aad761 100644 --- a/core/java/android/hardware/radio/RadioTuner.java +++ b/core/java/android/hardware/radio/RadioTuner.java @@ -280,11 +280,29 @@ public abstract class RadioTuner { * @throws IllegalStateException if the scan is in progress or has not been started, * startBackgroundScan() call may fix it. * @throws IllegalArgumentException if the vendorFilter argument is not valid. + * @deprecated Use {@link getDynamicProgramList} instead. */ + @Deprecated public abstract @NonNull List<RadioManager.ProgramInfo> getProgramList(@Nullable Map<String, String> vendorFilter); /** + * Get the dynamic list of discovered radio stations. + * + * The list object is updated asynchronously; to get the updates register + * with {@link ProgramList#addListCallback}. + * + * When the returned object is no longer used, it must be closed. + * + * @param filter filter for the list, or null to get the full list. + * @return the dynamic program list object, close it after use + * or {@code null} if program list is not supported by the tuner + */ + public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) { + return null; + } + + /** * Checks, if the analog playback is forced, see setAnalogForced. * * @throws IllegalStateException if the switch is not supported at current diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java index 8ad609d00816..91944bfd04f0 100644 --- a/core/java/android/hardware/radio/TunerAdapter.java +++ b/core/java/android/hardware/radio/TunerAdapter.java @@ -33,15 +33,18 @@ class TunerAdapter extends RadioTuner { private static final String TAG = "BroadcastRadio.TunerAdapter"; @NonNull private final ITuner mTuner; + @NonNull private final TunerCallbackAdapter mCallback; private boolean mIsClosed = false; private @RadioManager.Band int mBand; - TunerAdapter(ITuner tuner, @RadioManager.Band int band) { - if (tuner == null) { - throw new NullPointerException(); - } - mTuner = tuner; + private ProgramList mLegacyListProxy; + private Map<String, String> mLegacyListFilter; + + TunerAdapter(@NonNull ITuner tuner, @NonNull TunerCallbackAdapter callback, + @RadioManager.Band int band) { + mTuner = Objects.requireNonNull(tuner); + mCallback = Objects.requireNonNull(callback); mBand = band; } @@ -53,6 +56,10 @@ class TunerAdapter extends RadioTuner { return; } mIsClosed = true; + if (mLegacyListProxy != null) { + mLegacyListProxy.close(); + mLegacyListProxy = null; + } } try { mTuner.close(); @@ -227,10 +234,55 @@ class TunerAdapter extends RadioTuner { @Override public @NonNull List<RadioManager.ProgramInfo> getProgramList(@Nullable Map<String, String> vendorFilter) { - try { - return mTuner.getProgramList(vendorFilter); - } catch (RemoteException e) { - throw new RuntimeException("service died", e); + synchronized (mTuner) { + if (mLegacyListProxy == null || !Objects.equals(mLegacyListFilter, vendorFilter)) { + Log.i(TAG, "Program list filter has changed, requesting new list"); + mLegacyListProxy = new ProgramList(); + mLegacyListFilter = vendorFilter; + + mCallback.clearLastCompleteList(); + mCallback.setProgramListObserver(mLegacyListProxy, () -> { }); + try { + mTuner.startProgramListUpdates(new ProgramList.Filter(vendorFilter)); + } catch (RemoteException ex) { + throw new RuntimeException("service died", ex); + } + } + + List<RadioManager.ProgramInfo> list = mCallback.getLastCompleteList(); + if (list == null) throw new IllegalStateException("Program list is not ready yet"); + return list; + } + } + + @Override + public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) { + synchronized (mTuner) { + if (mLegacyListProxy != null) { + mLegacyListProxy.close(); + mLegacyListProxy = null; + } + mLegacyListFilter = null; + + ProgramList list = new ProgramList(); + mCallback.setProgramListObserver(list, () -> { + try { + mTuner.stopProgramListUpdates(); + } catch (RemoteException ex) { + Log.e(TAG, "Couldn't stop program list updates", ex); + } + }); + + try { + mTuner.startProgramListUpdates(filter); + } catch (UnsupportedOperationException ex) { + return null; + } catch (RemoteException ex) { + mCallback.setProgramListObserver(null, () -> { }); + throw new RuntimeException("service died", ex); + } + + return list; } } diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java index a01f658e80f6..b299ffe042b2 100644 --- a/core/java/android/hardware/radio/TunerCallbackAdapter.java +++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java @@ -22,7 +22,9 @@ import android.os.Handler; import android.os.Looper; import android.util.Log; +import java.util.List; import java.util.Map; +import java.util.Objects; /** * Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback. @@ -30,9 +32,14 @@ import java.util.Map; class TunerCallbackAdapter extends ITunerCallback.Stub { private static final String TAG = "BroadcastRadio.TunerCallbackAdapter"; + private final Object mLock = new Object(); @NonNull private final RadioTuner.Callback mCallback; @NonNull private final Handler mHandler; + @Nullable ProgramList mProgramList; + @Nullable List<RadioManager.ProgramInfo> mLastCompleteList; // for legacy getProgramList call + private boolean mDelayedCompleteCallback = false; + TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) { mCallback = callback; if (handler == null) { @@ -42,6 +49,49 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { } } + void setProgramListObserver(@Nullable ProgramList programList, + @NonNull ProgramList.OnCloseListener closeListener) { + Objects.requireNonNull(closeListener); + synchronized (mLock) { + if (mProgramList != null) { + Log.w(TAG, "Previous program list observer wasn't properly closed, closing it..."); + mProgramList.close(); + } + mProgramList = programList; + if (programList == null) return; + programList.setOnCloseListener(() -> { + synchronized (mLock) { + if (mProgramList != programList) return; + mProgramList = null; + mLastCompleteList = null; + closeListener.onClose(); + } + }); + programList.addOnCompleteListener(() -> { + synchronized (mLock) { + if (mProgramList != programList) return; + mLastCompleteList = programList.toList(); + if (mDelayedCompleteCallback) { + Log.d(TAG, "Sending delayed onBackgroundScanComplete callback"); + sendBackgroundScanCompleteLocked(); + } + } + }); + } + } + + @Nullable List<RadioManager.ProgramInfo> getLastCompleteList() { + synchronized (mLock) { + return mLastCompleteList; + } + } + + void clearLastCompleteList() { + synchronized (mLock) { + mLastCompleteList = null; + } + } + @Override public void onError(int status) { mHandler.post(() -> mCallback.onError(status)); @@ -87,9 +137,22 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { mHandler.post(() -> mCallback.onBackgroundScanAvailabilityChange(isAvailable)); } + private void sendBackgroundScanCompleteLocked() { + mDelayedCompleteCallback = false; + mHandler.post(() -> mCallback.onBackgroundScanComplete()); + } + @Override public void onBackgroundScanComplete() { - mHandler.post(() -> mCallback.onBackgroundScanComplete()); + synchronized (mLock) { + if (mLastCompleteList == null) { + Log.i(TAG, "Got onBackgroundScanComplete callback, but the " + + "program list didn't get through yet. Delaying it..."); + mDelayedCompleteCallback = true; + return; + } + sendBackgroundScanCompleteLocked(); + } } @Override @@ -98,6 +161,14 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { } @Override + public void onProgramListUpdated(ProgramList.Chunk chunk) { + synchronized (mLock) { + if (mProgramList == null) return; + mProgramList.apply(Objects.requireNonNull(chunk)); + } + } + + @Override public void onParametersUpdated(Map parameters) { mHandler.post(() -> mCallback.onParametersUpdated(parameters)); } diff --git a/core/java/android/hardware/radio/Utils.java b/core/java/android/hardware/radio/Utils.java new file mode 100644 index 000000000000..09bf8feb30c2 --- /dev/null +++ b/core/java/android/hardware/radio/Utils.java @@ -0,0 +1,92 @@ +/** + * 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.hardware.radio; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +final class Utils { + static void writeStringMap(@NonNull Parcel dest, @Nullable Map<String, String> map) { + if (map == null) { + dest.writeInt(0); + return; + } + dest.writeInt(map.size()); + for (Map.Entry<String, String> entry : map.entrySet()) { + dest.writeString(entry.getKey()); + dest.writeString(entry.getValue()); + } + } + + static @NonNull Map<String, String> readStringMap(@NonNull Parcel in) { + int size = in.readInt(); + Map<String, String> map = new HashMap<>(); + while (size-- > 0) { + String key = in.readString(); + String value = in.readString(); + map.put(key, value); + } + return map; + } + + static <T extends Parcelable> void writeSet(@NonNull Parcel dest, @Nullable Set<T> set) { + if (set == null) { + dest.writeInt(0); + return; + } + dest.writeInt(set.size()); + set.stream().forEach(elem -> dest.writeTypedObject(elem, 0)); + } + + static <T> Set<T> createSet(@NonNull Parcel in, Parcelable.Creator<T> c) { + int size = in.readInt(); + Set<T> set = new HashSet<>(); + while (size-- > 0) { + set.add(in.readTypedObject(c)); + } + return set; + } + + static void writeIntSet(@NonNull Parcel dest, @Nullable Set<Integer> set) { + if (set == null) { + dest.writeInt(0); + return; + } + dest.writeInt(set.size()); + set.stream().forEach(elem -> dest.writeInt(Objects.requireNonNull(elem))); + } + + static Set<Integer> createIntSet(@NonNull Parcel in) { + return createSet(in, new Parcelable.Creator<Integer>() { + public Integer createFromParcel(Parcel in) { + return in.readInt(); + } + + public Integer[] newArray(int size) { + return new Integer[size]; + } + }); + } +} diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 8937490091d0..7528bc3989c2 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -340,42 +340,35 @@ public class InputMethodService extends AbstractInputMethodService { final Insets mTmpInsets = new Insets(); final int[] mTmpLocation = new int[2]; - final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = - new ViewTreeObserver.OnComputeInternalInsetsListener() { - public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { - if (isExtractViewShown()) { - // In true fullscreen mode, we just say the window isn't covering - // any content so we don't impact whatever is behind. - View decor = getWindow().getWindow().getDecorView(); - info.contentInsets.top = info.visibleInsets.top - = decor.getHeight(); - info.touchableRegion.setEmpty(); - info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); - } else { - onComputeInsets(mTmpInsets); - info.contentInsets.top = mTmpInsets.contentTopInsets; - info.visibleInsets.top = mTmpInsets.visibleTopInsets; - info.touchableRegion.set(mTmpInsets.touchableRegion); - info.setTouchableInsets(mTmpInsets.touchableInsets); - } + final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> { + if (isExtractViewShown()) { + // In true fullscreen mode, we just say the window isn't covering + // any content so we don't impact whatever is behind. + View decor = getWindow().getWindow().getDecorView(); + info.contentInsets.top = info.visibleInsets.top = decor.getHeight(); + info.touchableRegion.setEmpty(); + info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); + } else { + onComputeInsets(mTmpInsets); + info.contentInsets.top = mTmpInsets.contentTopInsets; + info.visibleInsets.top = mTmpInsets.visibleTopInsets; + info.touchableRegion.set(mTmpInsets.touchableRegion); + info.setTouchableInsets(mTmpInsets.touchableInsets); } }; - final View.OnClickListener mActionClickListener = new View.OnClickListener() { - public void onClick(View v) { - final EditorInfo ei = getCurrentInputEditorInfo(); - final InputConnection ic = getCurrentInputConnection(); - if (ei != null && ic != null) { - if (ei.actionId != 0) { - ic.performEditorAction(ei.actionId); - } else if ((ei.imeOptions&EditorInfo.IME_MASK_ACTION) - != EditorInfo.IME_ACTION_NONE) { - ic.performEditorAction(ei.imeOptions&EditorInfo.IME_MASK_ACTION); - } + final View.OnClickListener mActionClickListener = v -> { + final EditorInfo ei = getCurrentInputEditorInfo(); + final InputConnection ic = getCurrentInputConnection(); + if (ei != null && ic != null) { + if (ei.actionId != 0) { + ic.performEditorAction(ei.actionId); + } else if ((ei.imeOptions & EditorInfo.IME_MASK_ACTION) != EditorInfo.IME_ACTION_NONE) { + ic.performEditorAction(ei.imeOptions & EditorInfo.IME_MASK_ACTION); } } }; - + /** * Concrete implementation of * {@link AbstractInputMethodService.AbstractInputMethodImpl} that provides @@ -896,20 +889,20 @@ public class InputMethodService extends AbstractInputMethodService { mWindow.getWindow().setWindowAnimations( com.android.internal.R.style.Animation_InputMethodFancy); } - mFullscreenArea = (ViewGroup)mRootView.findViewById(com.android.internal.R.id.fullscreenArea); + mFullscreenArea = mRootView.findViewById(com.android.internal.R.id.fullscreenArea); mExtractViewHidden = false; - mExtractFrame = (FrameLayout)mRootView.findViewById(android.R.id.extractArea); + mExtractFrame = mRootView.findViewById(android.R.id.extractArea); mExtractView = null; mExtractEditText = null; mExtractAccessories = null; mExtractAction = null; mFullscreenApplied = false; - - mCandidatesFrame = (FrameLayout)mRootView.findViewById(android.R.id.candidatesArea); - mInputFrame = (FrameLayout)mRootView.findViewById(android.R.id.inputArea); + + mCandidatesFrame = mRootView.findViewById(android.R.id.candidatesArea); + mInputFrame = mRootView.findViewById(android.R.id.inputArea); mInputView = null; mIsInputViewShown = false; - + mExtractFrame.setVisibility(View.GONE); mCandidatesVisibility = getCandidatesHiddenVisibility(); mCandidatesFrame.setVisibility(mCandidatesVisibility); @@ -1089,33 +1082,6 @@ public class InputMethodService extends AbstractInputMethodService { } /** - * Close/hide the input method's soft input area, so the user no longer - * sees it or can interact with it. This can only be called - * from the currently active input method, as validated by the given token. - * - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY}, - * {@link InputMethodManager#HIDE_NOT_ALWAYS} bit set. - */ - public void hideSoftInputFromInputMethod(int flags) { - mImm.hideSoftInputFromInputMethodInternal(mToken, flags); - } - - /** - * Show the input method's soft input area, so the user - * sees the input method window and can interact with it. - * This can only be called from the currently active input method, - * as validated by the given token. - * - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT} or - * {@link InputMethodManager#SHOW_FORCED} bit set. - */ - public void showSoftInputFromInputMethod(int flags) { - mImm.showSoftInputFromInputMethodInternal(mToken, flags); - } - - /** * Force switch to the last used input method and subtype. If the last input method didn't have * any subtypes, the framework will simply switch to the last input method with no subtype * specified. @@ -1461,17 +1427,17 @@ public class InputMethodService extends AbstractInputMethodService { public int getCandidatesHiddenVisibility() { return isExtractViewShown() ? View.GONE : View.INVISIBLE; } - + public void showStatusIcon(@DrawableRes int iconResId) { mStatusIcon = iconResId; - mImm.showStatusIcon(mToken, getPackageName(), iconResId); + mImm.showStatusIconInternal(mToken, getPackageName(), iconResId); } - + public void hideStatusIcon() { mStatusIcon = 0; - mImm.hideStatusIcon(mToken); + mImm.hideStatusIconInternal(mToken); } - + /** * Force switch to a new input method, as identified by <var>id</var>. This * input method will be destroyed, and the requested one started on the @@ -1480,9 +1446,9 @@ public class InputMethodService extends AbstractInputMethodService { * @param id Unique identifier of the new input method ot start. */ public void switchInputMethod(String id) { - mImm.setInputMethod(mToken, id); + mImm.setInputMethodInternal(mToken, id); } - + public void setExtractView(View view) { mExtractFrame.removeAllViews(); mExtractFrame.addView(view, new FrameLayout.LayoutParams( @@ -1490,13 +1456,13 @@ public class InputMethodService extends AbstractInputMethodService { ViewGroup.LayoutParams.MATCH_PARENT)); mExtractView = view; if (view != null) { - mExtractEditText = (ExtractEditText)view.findViewById( + mExtractEditText = view.findViewById( com.android.internal.R.id.inputExtractEditText); mExtractEditText.setIME(this); mExtractAction = view.findViewById( com.android.internal.R.id.inputExtractAction); if (mExtractAction != null) { - mExtractAccessories = (ViewGroup)view.findViewById( + mExtractAccessories = view.findViewById( com.android.internal.R.id.inputExtractAccessories); } startExtractingText(false); @@ -1745,7 +1711,7 @@ public class InputMethodService extends AbstractInputMethodService { // Rethrow the exception to preserve the existing behavior. Some IMEs may have directly // called this method and relied on this exception for some clean-up tasks. // TODO: Give developers a clear guideline of whether it's OK to call this method or - // InputMethodManager#showSoftInputFromInputMethod() should always be used instead. + // InputMethodService#requestShowSelf(int) should always be used instead. throw e; } finally { // TODO: Is it OK to set true when we get BadTokenException? @@ -2067,27 +2033,30 @@ public class InputMethodService extends AbstractInputMethodService { /** * Close this input method's soft input area, removing it from the display. - * The input method will continue running, but the user can no longer use - * it to generate input by touching the screen. - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY - * InputMethodManager.HIDE_IMPLICIT_ONLY} bit set. + * + * The input method will continue running, but the user can no longer use it to generate input + * by touching the screen. + * + * @see InputMethodManager#HIDE_IMPLICIT_ONLY + * @see InputMethodManager#HIDE_NOT_ALWAYS + * @param flags Provides additional operating flags. */ public void requestHideSelf(int flags) { - mImm.hideSoftInputFromInputMethod(mToken, flags); + mImm.hideSoftInputFromInputMethodInternal(mToken, flags); } - + /** - * Show the input method. This is a call back to the - * IMF to handle showing the input method. - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link InputMethodManager#SHOW_FORCED - * InputMethodManager.} bit set. + * Show the input method's soft input area, so the user sees the input method window and can + * interact with it. + * + * @see InputMethodManager#SHOW_IMPLICIT + * @see InputMethodManager#SHOW_FORCED + * @param flags Provides additional operating flags. */ - private void requestShowSelf(int flags) { - mImm.showSoftInputFromInputMethod(mToken, flags); + public void requestShowSelf(int flags) { + mImm.showSoftInputFromInputMethodInternal(mToken, flags); } - + private boolean handleBack(boolean doIt) { if (mShowInputRequested) { // If the soft input area is shown, back closes it and we @@ -2754,7 +2723,7 @@ public class InputMethodService extends AbstractInputMethodService { * application. * This cannot be {@code null}. * @param inputConnection {@link InputConnection} with which - * {@link InputConnection#commitContent(InputContentInfo, Bundle)} will be called. + * {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} will be called. * @hide */ @Override diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java index d6992aaede5f..287bdc88dd3e 100644 --- a/core/java/android/net/MacAddress.java +++ b/core/java/android/net/MacAddress.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.IntDef; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; @@ -60,7 +61,7 @@ public final class MacAddress implements Parcelable { }) public @interface MacAddressType { } - /** Indicates a MAC address of unknown type. */ + /** @hide Indicates a MAC address of unknown type. */ public static final int TYPE_UNKNOWN = 0; /** Indicates a MAC address is a unicast address. */ public static final int TYPE_UNICAST = 1; @@ -92,7 +93,7 @@ public final class MacAddress implements Parcelable { * * @return the int constant representing the MAC address type of this MacAddress. */ - public @MacAddressType int addressType() { + public @MacAddressType int getAddressType() { if (equals(BROADCAST_ADDRESS)) { return TYPE_BROADCAST; } @@ -120,12 +121,12 @@ public final class MacAddress implements Parcelable { /** * @return a byte array representation of this MacAddress. */ - public byte[] toByteArray() { + public @NonNull byte[] toByteArray() { return byteAddrFromLongAddr(mAddr); } @Override - public String toString() { + public @NonNull String toString() { return stringAddrFromLongAddr(mAddr); } @@ -133,7 +134,7 @@ public final class MacAddress implements Parcelable { * @return a String representation of the OUI part of this MacAddress made of 3 hexadecimal * numbers in [0,ff] joined by ':' characters. */ - public String toOuiString() { + public @NonNull String toOuiString() { return String.format( "%02x:%02x:%02x", (mAddr >> 40) & 0xff, (mAddr >> 32) & 0xff, (mAddr >> 24) & 0xff); } @@ -197,7 +198,7 @@ public final class MacAddress implements Parcelable { if (!isMacAddress(addr)) { return TYPE_UNKNOWN; } - return MacAddress.fromBytes(addr).addressType(); + return MacAddress.fromBytes(addr).getAddressType(); } /** @@ -211,7 +212,7 @@ public final class MacAddress implements Parcelable { * * @hide */ - public static byte[] byteAddrFromStringAddr(String addr) { + public static @NonNull byte[] byteAddrFromStringAddr(String addr) { Preconditions.checkNotNull(addr); String[] parts = addr.split(":"); if (parts.length != ETHER_ADDR_LEN) { @@ -239,7 +240,7 @@ public final class MacAddress implements Parcelable { * * @hide */ - public static String stringAddrFromByteAddr(byte[] addr) { + public static @NonNull String stringAddrFromByteAddr(byte[] addr) { if (!isMacAddress(addr)) { return null; } @@ -291,7 +292,7 @@ public final class MacAddress implements Parcelable { // Internal conversion function equivalent to stringAddrFromByteAddr(byteAddrFromLongAddr(addr)) // that avoids the allocation of an intermediary byte[]. - private static String stringAddrFromLongAddr(long addr) { + private static @NonNull String stringAddrFromLongAddr(long addr) { return String.format("%02x:%02x:%02x:%02x:%02x:%02x", (addr >> 40) & 0xff, (addr >> 32) & 0xff, @@ -310,7 +311,7 @@ public final class MacAddress implements Parcelable { * @return the MacAddress corresponding to the given String representation. * @throws IllegalArgumentException if the given String is not a valid representation. */ - public static MacAddress fromString(String addr) { + public static @NonNull MacAddress fromString(@NonNull String addr) { return new MacAddress(longAddrFromStringAddr(addr)); } @@ -322,7 +323,7 @@ public final class MacAddress implements Parcelable { * @return the MacAddress corresponding to the given byte array representation. * @throws IllegalArgumentException if the given byte array is not a valid representation. */ - public static MacAddress fromBytes(byte[] addr) { + public static @NonNull MacAddress fromBytes(@NonNull byte[] addr) { return new MacAddress(longAddrFromByteAddr(addr)); } @@ -336,7 +337,7 @@ public final class MacAddress implements Parcelable { * * @hide */ - public static MacAddress createRandomUnicastAddress() { + public static @NonNull MacAddress createRandomUnicastAddress() { return createRandomUnicastAddress(BASE_GOOGLE_MAC, new Random()); } @@ -352,7 +353,7 @@ public final class MacAddress implements Parcelable { * * @hide */ - public static MacAddress createRandomUnicastAddress(MacAddress base, Random r) { + public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) { long addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong()); addr = addr | LOCALLY_ASSIGNED_MASK; addr = addr & ~MULTICAST_MASK; diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index 1a3ce9124b48..5df168d20586 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -357,13 +357,13 @@ public class Network implements Parcelable { // Multiple Provisioning Domains API recommendations, as made by the // IETF mif working group. // - // The HANDLE_MAGIC value MUST be kept in sync with the corresponding + // The handleMagic value MUST be kept in sync with the corresponding // value in the native/android/net.c NDK implementation. if (netId == 0) { return 0L; // make this zero condition obvious for debugging } - final long HANDLE_MAGIC = 0xfacade; - return (((long) netId) << 32) | HANDLE_MAGIC; + final long handleMagic = 0xcafed00dL; + return (((long) netId) << 32) | handleMagic; } // implement the Parcelable interface diff --git a/core/java/android/net/metrics/WakeupStats.java b/core/java/android/net/metrics/WakeupStats.java index 7277ba34534b..bb36536fe2ce 100644 --- a/core/java/android/net/metrics/WakeupStats.java +++ b/core/java/android/net/metrics/WakeupStats.java @@ -80,7 +80,7 @@ public class WakeupStats { break; } - switch (ev.dstHwAddr.addressType()) { + switch (ev.dstHwAddr.getAddressType()) { case MacAddress.TYPE_UNICAST: l2UnicastCount++; break; diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 9ececa6be2a2..21974843613f 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -224,6 +224,20 @@ public class UserManager { public static final String DISALLOW_CONFIG_BRIGHTNESS = "no_config_brightness"; /** + * Specifies if ambient display is disallowed for the user. + * + * <p>The default value is <code>false</code>. + * + * <p>This user restriction has no effect on managed profiles. + * <p>Key for user restrictions. + * <p>Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display"; + + /** * Specifies if a user is disallowed from enabling the * "Unknown Sources" setting, that allows installation of apps from unknown sources. * The default value is <code>false</code>. @@ -892,6 +906,27 @@ public class UserManager { public static final String DISALLOW_USER_SWITCH = "no_user_switch"; /** + * Specifies whether the user can share file / picture / data from the primary user into the + * managed profile, either by sending them from the primary side, or by picking up data within + * an app in the managed profile. + * <p> + * When a managed profile is created, the system allows the user to send data from the primary + * side to the profile by setting up certain default cross profile intent filters. If + * this is undesired, this restriction can be set to disallow it. Note that this restriction + * will not block any sharing allowed by explicit + * {@link DevicePolicyManager#addCrossProfileIntentFilter} calls by the profile owner. + * <p> + * This restriction is only meaningful when set by profile owner. When it is set by device + * owner, it does not have any effect. + * <p> + * The default value is <code>false</code>. + * + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_SHARE_INTO_MANAGED_PROFILE = "no_sharing_into_profile"; + /** * Application restriction key that is used to indicate the pending arrival * of real restrictions for the app. * diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index 070b8c1b0008..839a8bf42b10 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -394,4 +394,32 @@ public final class StorageVolume implements Parcelable { parcel.writeString(mFsUuid); parcel.writeString(mState); } + + /** {@hide} */ + public static final class ScopedAccessProviderContract { + + private ScopedAccessProviderContract() { + throw new UnsupportedOperationException("contains constants only"); + } + + public static final String AUTHORITY = "com.android.documentsui.scopedAccess"; + + public static final String TABLE_PACKAGES = "packages"; + public static final String TABLE_PERMISSIONS = "permissions"; + + public static final String COL_PACKAGE = "package_name"; + public static final String COL_VOLUME_UUID = "volume_uuid"; + public static final String COL_DIRECTORY = "directory"; + public static final String COL_GRANTED = "granted"; + + public static final String[] TABLE_PACKAGES_COLUMNS = new String[] { COL_PACKAGE }; + public static final String[] TABLE_PERMISSIONS_COLUMNS = + new String[] { COL_PACKAGE, COL_VOLUME_UUID, COL_DIRECTORY, COL_GRANTED }; + + public static final int TABLE_PACKAGES_COL_PACKAGE = 0; + public static final int TABLE_PERMISSIONS_COL_PACKAGE = 0; + public static final int TABLE_PERMISSIONS_COL_VOLUME_UUID = 1; + public static final int TABLE_PERMISSIONS_COL_DIRECTORY = 2; + public static final int TABLE_PERMISSIONS_COL_GRANTED = 3; + } } diff --git a/core/java/android/security/keystore/KeyDerivationParameters.aidl b/core/java/android/security/keystore/KeyDerivationParams.aidl index 92316e531f9e..f39aa047adee 100644 --- a/core/java/android/security/keystore/KeyDerivationParameters.aidl +++ b/core/java/android/security/keystore/KeyDerivationParams.aidl @@ -17,4 +17,4 @@ package android.security.keystore; /* @hide */ -parcelable KeyDerivationParameters; +parcelable KeyDerivationParams; diff --git a/core/java/android/security/keystore/KeyDerivationParameters.java b/core/java/android/security/keystore/KeyDerivationParams.java index 762f62af0f32..b702accffba1 100644 --- a/core/java/android/security/keystore/KeyDerivationParameters.java +++ b/core/java/android/security/keystore/KeyDerivationParams.java @@ -28,27 +28,22 @@ import java.lang.annotation.RetentionPolicy; /** * Collection of parameters which define a key derivation function. - * Supports + * Currently only supports salted SHA-256 * - * <ul> - * <li>SHA256 - * <li>Argon2id - * </ul> * @hide */ -public final class KeyDerivationParameters implements Parcelable { +public final class KeyDerivationParams implements Parcelable { private final int mAlgorithm; private byte[] mSalt; /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({ALGORITHM_SHA256, ALGORITHM_ARGON2ID}) + @IntDef(prefix = {"ALGORITHM_"}, value = {ALGORITHM_SHA256, ALGORITHM_ARGON2ID}) public @interface KeyDerivationAlgorithm { } /** * Salted SHA256 - * @hide */ public static final int ALGORITHM_SHA256 = 1; @@ -62,11 +57,11 @@ public final class KeyDerivationParameters implements Parcelable { /** * Creates instance of the class to to derive key using salted SHA256 hash. */ - public static KeyDerivationParameters createSha256Parameters(@NonNull byte[] salt) { - return new KeyDerivationParameters(ALGORITHM_SHA256, salt); + public static KeyDerivationParams createSha256Params(@NonNull byte[] salt) { + return new KeyDerivationParams(ALGORITHM_SHA256, salt); } - private KeyDerivationParameters(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt) { + private KeyDerivationParams(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt) { mAlgorithm = algorithm; mSalt = Preconditions.checkNotNull(salt); } @@ -85,14 +80,14 @@ public final class KeyDerivationParameters implements Parcelable { return mSalt; } - public static final Parcelable.Creator<KeyDerivationParameters> CREATOR = - new Parcelable.Creator<KeyDerivationParameters>() { - public KeyDerivationParameters createFromParcel(Parcel in) { - return new KeyDerivationParameters(in); + public static final Parcelable.Creator<KeyDerivationParams> CREATOR = + new Parcelable.Creator<KeyDerivationParams>() { + public KeyDerivationParams createFromParcel(Parcel in) { + return new KeyDerivationParams(in); } - public KeyDerivationParameters[] newArray(int length) { - return new KeyDerivationParameters[length]; + public KeyDerivationParams[] newArray(int length) { + return new KeyDerivationParams[length]; } }; @@ -108,7 +103,7 @@ public final class KeyDerivationParameters implements Parcelable { /** * @hide */ - protected KeyDerivationParameters(Parcel in) { + protected KeyDerivationParams(Parcel in) { mAlgorithm = in.readInt(); mSalt = in.createByteArray(); } diff --git a/core/java/android/security/keystore/RecoveryData.java b/core/java/android/security/keystore/RecoveryData.java index c717d9a26f77..897aa18a0e18 100644 --- a/core/java/android/security/keystore/RecoveryData.java +++ b/core/java/android/security/keystore/RecoveryData.java @@ -38,7 +38,7 @@ import java.util.List; * @hide */ public final class RecoveryData implements Parcelable { - private Integer mSnapshotVersion; + private int mSnapshotVersion; private List<RecoveryMetadata> mRecoveryMetadata; private List<EntryRecoveryData> mEntryRecoveryData; private byte[] mEncryptedRecoveryKeyBlob; @@ -163,7 +163,6 @@ public final class RecoveryData implements Parcelable { * @throws NullPointerException if some required fields were not set. */ public @NonNull RecoveryData build() { - Preconditions.checkNotNull(mInstance.mSnapshotVersion); Preconditions.checkCollectionElementsNotNull(mInstance.mRecoveryMetadata, "recoveryMetadata"); Preconditions.checkCollectionElementsNotNull(mInstance.mEntryRecoveryData, diff --git a/core/java/android/security/keystore/RecoveryManager.java b/core/java/android/security/keystore/RecoveryManager.java index d00f43f93808..99bd284e4d80 100644 --- a/core/java/android/security/keystore/RecoveryManager.java +++ b/core/java/android/security/keystore/RecoveryManager.java @@ -23,8 +23,6 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; -import android.security.KeyStore; -import android.util.AndroidException; import com.android.internal.widget.ILockSettings; @@ -39,64 +37,6 @@ import java.util.Map; */ public class RecoveryManager { - public static final int NO_ERROR = KeyStore.NO_ERROR; - public static final int SYSTEM_ERROR = KeyStore.SYSTEM_ERROR; - - /** - * Failed because the loader has not been initialized with a recovery public key yet. - */ - public static final int ERROR_UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20; - - /** - * Failed because no snapshot is yet pending to be synced for the user. - */ - public static final int ERROR_NO_SNAPSHOT_PENDING = 21; - - /** - * Failed due to an error internal to AndroidKeyStore. - */ - public static final int ERROR_KEYSTORE_INTERNAL_ERROR = 22; - - /** - * Failed because the user does not have a lock screen set. - */ - public static final int ERROR_INSECURE_USER = 24; - - /** - * Failed because of an internal database error. - */ - public static final int ERROR_DATABASE_ERROR = 25; - - /** - * Failed because the provided certificate was not a valid X509 certificate. - */ - public static final int ERROR_BAD_X509_CERTIFICATE = 26; - - /** - * Should never be thrown - some algorithm that all AOSP implementations must support is - * not available. - */ - public static final int ERROR_UNEXPECTED_MISSING_ALGORITHM = 27; - - /** - * The caller is attempting to perform an operation that is not yet fully supported in the API. - */ - public static final int ERROR_NOT_YET_SUPPORTED = 28; - - /** - * Error thrown if decryption failed. This might be because the tag is wrong, the key is wrong, - * the data has become corrupted, the data has been tampered with, etc. - */ - public static final int ERROR_DECRYPTION_FAILED = 29; - - /** - * Rate limit is enforced to prevent using too many trusted remote devices, since each device - * can have its own number of user secret guesses allowed. - * - * @hide - */ - public static final int ERROR_RATE_LIMIT_EXCEEDED = 30; - /** Key has been successfully synced. */ public static final int RECOVERY_STATUS_SYNCED = 0; /** Waiting for recovery agent to sync the key. */ @@ -122,47 +62,6 @@ public class RecoveryManager { } /** - * Exceptions returned by {@link RecoveryManager}. - */ - public static class RecoveryManagerException extends AndroidException { - private int mErrorCode; - - /** - * Creates new {@link #RecoveryManagerException} instance from the error code. - * - * @param errorCode An error code, as listed at the top of this file. - * @param message The associated error message. - * @hide - */ - public static RecoveryManagerException fromErrorCode( - int errorCode, String message) { - return new RecoveryManagerException(errorCode, message); - } - - /** - * Creates new {@link #RecoveryManagerException} from {@link - * ServiceSpecificException}. - * - * @param e exception thrown on service side. - * @hide - */ - static RecoveryManagerException fromServiceSpecificException( - ServiceSpecificException e) throws RecoveryManagerException { - throw RecoveryManagerException.fromErrorCode(e.errorCode, e.getMessage()); - } - - private RecoveryManagerException(int errorCode, String message) { - super(message); - mErrorCode = errorCode; - } - - /** Returns errorCode. */ - public int getErrorCode() { - return mErrorCode; - } - } - - /** * Initializes key recovery service for the calling application. RecoveryManager * randomly chooses one of the keys from the list and keeps it to use for future key export * operations. Collection of all keys in the list must be signed by the provided {@code @@ -260,15 +159,14 @@ public class RecoveryManager { * {@code RecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included * in vaultParams {@link #startRecoverySession} * - * @param serverParameters included in recovery key blob. + * @param serverParams included in recovery key blob. * @see #getRecoveryData * @throws RecoveryManagerException If parameters rotation is rate limited. * @hide */ - public void setServerParameters(long serverParameters) - throws RecoveryManagerException { + public void setServerParams(byte[] serverParams) throws RecoveryManagerException { try { - mBinder.setServerParameters(serverParameters); + mBinder.setServerParams(serverParams); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { @@ -309,20 +207,17 @@ public class RecoveryManager { * <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE} * </ul> * - * @param packageName Application whose recoverable keys' statuses are to be retrieved. if - * {@code null} caller's package will be used. * @return {@code Map} from KeyStore alias to recovery status. * @see #setRecoveryStatus * @hide */ - public Map<String, Integer> getRecoveryStatus(@Nullable String packageName) + public Map<String, Integer> getRecoveryStatus() throws RecoveryManagerException { try { // IPC doesn't support generic Maps. @SuppressWarnings("unchecked") Map<String, Integer> result = - (Map<String, Integer>) - mBinder.getRecoveryStatus(packageName); + (Map<String, Integer>) mBinder.getRecoveryStatus(/*packageName=*/ null); return result; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -414,8 +309,9 @@ public class RecoveryManager { * return recovery key. * * @param sessionId ID for recovery session. - * @param verifierPublicKey Certificate with Public key used to create the recovery blob on the - * source device. Keystore will verify the certificate using root of trust. + * @param verifierPublicKey Encoded {@code java.security.cert.X509Certificate} with Public key + * used to create the recovery blob on the source device. + * Keystore will verify the certificate using root of trust. * @param vaultParams Must match the parameters in the corresponding field in the recovery blob. * Used to limit number of guesses. * @param vaultChallenge Data passed from server for this recovery session and used to prevent diff --git a/core/java/android/security/keystore/RecoveryManagerException.java b/core/java/android/security/keystore/RecoveryManagerException.java new file mode 100644 index 000000000000..344718aa31d4 --- /dev/null +++ b/core/java/android/security/keystore/RecoveryManagerException.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +import android.os.ServiceSpecificException; + +/** + * Exception thrown by {@link RecoveryManager} methods. + * + * @hide + */ +public class RecoveryManagerException extends Exception { + /** + * Failed because the loader has not been initialized with a recovery public key yet. + */ + public static final int ERROR_UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20; + + /** + * Failed because no snapshot is yet pending to be synced for the user. + */ + public static final int ERROR_NO_SNAPSHOT_PENDING = 21; + + /** + * Failed due to an error internal to AndroidKeyStore. + */ + public static final int ERROR_KEYSTORE_INTERNAL_ERROR = 22; + + /** + * Failed because the user does not have a lock screen set. + */ + public static final int ERROR_INSECURE_USER = 24; + + /** + * Failed because of an internal database error. + */ + public static final int ERROR_DATABASE_ERROR = 25; + + /** + * Failed because the provided certificate was not a valid X509 certificate. + */ + public static final int ERROR_BAD_X509_CERTIFICATE = 26; + + /** + * Should never be thrown - some algorithm that all AOSP implementations must support is + * not available. + */ + public static final int ERROR_UNEXPECTED_MISSING_ALGORITHM = 27; + + /** + * Error thrown if decryption failed. This might be because the tag is wrong, the key is wrong, + * the data has become corrupted, the data has been tampered with, etc. + */ + public static final int ERROR_DECRYPTION_FAILED = 28; + + /** + * Rate limit is enforced to prevent using too many trusted remote devices, since each device + * can have its own number of user secret guesses allowed. + * + * @hide + */ + public static final int ERROR_RATE_LIMIT_EXCEEDED = 29; + + private int mErrorCode; + + /** + * Creates new {@link #RecoveryManagerException} instance from the error code. + * + * @param errorCode An error code, as listed at the top of this file. + * @param message The associated error message. + * @hide + */ + public static RecoveryManagerException fromErrorCode( + int errorCode, String message) { + return new RecoveryManagerException(errorCode, message); + } + /** + * Creates new {@link #RecoveryManagerException} from {@link + * ServiceSpecificException}. + * + * @param e exception thrown on service side. + * @hide + */ + static RecoveryManagerException fromServiceSpecificException( + ServiceSpecificException e) throws RecoveryManagerException { + throw RecoveryManagerException.fromErrorCode(e.errorCode, e.getMessage()); + } + + private RecoveryManagerException(int errorCode, String message) { + super(message); + mErrorCode = errorCode; + } + + /** Returns errorCode. */ + public int getErrorCode() { + return mErrorCode; + } +} diff --git a/core/java/android/security/keystore/RecoveryMetadata.java b/core/java/android/security/keystore/RecoveryMetadata.java index bfd558eb8d80..3f0945557a5f 100644 --- a/core/java/android/security/keystore/RecoveryMetadata.java +++ b/core/java/android/security/keystore/RecoveryMetadata.java @@ -78,9 +78,9 @@ public final class RecoveryMetadata implements Parcelable { private Integer mLockScreenUiFormat; /** - * Parameters of key derivation function, including algorithm, difficulty, salt. + * Parameters of the key derivation function, including algorithm, difficulty, salt. */ - private KeyDerivationParameters mKeyDerivationParameters; + private KeyDerivationParams mKeyDerivationParams; private byte[] mSecret; // Derived from user secret. The field must have limited visibility. /** @@ -90,11 +90,11 @@ public final class RecoveryMetadata implements Parcelable { */ public RecoveryMetadata(@UserSecretType int userSecretType, @LockScreenUiFormat int lockScreenUiFormat, - @NonNull KeyDerivationParameters keyDerivationParameters, + @NonNull KeyDerivationParams keyDerivationParams, @NonNull byte[] secret) { mUserSecretType = userSecretType; mLockScreenUiFormat = lockScreenUiFormat; - mKeyDerivationParameters = Preconditions.checkNotNull(keyDerivationParameters); + mKeyDerivationParams = Preconditions.checkNotNull(keyDerivationParams); mSecret = Preconditions.checkNotNull(secret); } @@ -126,8 +126,8 @@ public final class RecoveryMetadata implements Parcelable { * Specifies function used to derive symmetric key from user input * Format is defined in separate util class. */ - public @NonNull KeyDerivationParameters getKeyDerivationParameters() { - return mKeyDerivationParameters; + public @NonNull KeyDerivationParams getKeyDerivationParams() { + return mKeyDerivationParams; } /** @@ -176,12 +176,12 @@ public final class RecoveryMetadata implements Parcelable { /** * Sets parameters of the key derivation function. * - * @param keyDerivationParameters Key derivation parameters + * @param keyDerivationParams Key derivation Params * @return This builder. */ - public Builder setKeyDerivationParameters(@NonNull KeyDerivationParameters - keyDerivationParameters) { - mInstance.mKeyDerivationParameters = keyDerivationParameters; + public Builder setKeyDerivationParams(@NonNull KeyDerivationParams + keyDerivationParams) { + mInstance.mKeyDerivationParams = keyDerivationParams; return this; } @@ -210,7 +210,7 @@ public final class RecoveryMetadata implements Parcelable { mInstance.mUserSecretType = TYPE_LOCKSCREEN; } Preconditions.checkNotNull(mInstance.mLockScreenUiFormat); - Preconditions.checkNotNull(mInstance.mKeyDerivationParameters); + Preconditions.checkNotNull(mInstance.mKeyDerivationParams); if (mInstance.mSecret == null) { mInstance.mSecret = new byte[]{}; } @@ -253,7 +253,7 @@ public final class RecoveryMetadata implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeInt(mUserSecretType); out.writeInt(mLockScreenUiFormat); - out.writeTypedObject(mKeyDerivationParameters, flags); + out.writeTypedObject(mKeyDerivationParams, flags); out.writeByteArray(mSecret); } @@ -263,7 +263,7 @@ public final class RecoveryMetadata implements Parcelable { protected RecoveryMetadata(Parcel in) { mUserSecretType = in.readInt(); mLockScreenUiFormat = in.readInt(); - mKeyDerivationParameters = in.readTypedObject(KeyDerivationParameters.CREATOR); + mKeyDerivationParams = in.readTypedObject(KeyDerivationParams.CREATOR); mSecret = in.createByteArray(); } diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index bf4b6ac5194f..aa97b2aba749 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1917,10 +1917,10 @@ public abstract class Layout { private static float measurePara(TextPaint paint, CharSequence text, int start, int end, TextDirectionHeuristic textDir) { - MeasuredText mt = null; + MeasuredParagraph mt = null; TextLine tl = TextLine.obtain(); try { - mt = MeasuredText.buildForBidi(text, start, end, textDir, mt); + mt = MeasuredParagraph.buildForBidi(text, start, end, textDir, mt); final char[] chars = mt.getChars(); final int len = chars.length; final Directions directions = mt.getDirections(0, len); diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java new file mode 100644 index 000000000000..c93e0365d58d --- /dev/null +++ b/core/java/android/text/MeasuredParagraph.java @@ -0,0 +1,677 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.text; + +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Paint; +import android.text.AutoGrowArray.ByteArray; +import android.text.AutoGrowArray.FloatArray; +import android.text.AutoGrowArray.IntArray; +import android.text.Layout.Directions; +import android.text.style.MetricAffectingSpan; +import android.text.style.ReplacementSpan; +import android.util.Pools.SynchronizedPool; + +import dalvik.annotation.optimization.CriticalNative; + +import libcore.util.NativeAllocationRegistry; + +import java.util.Arrays; + +/** + * MeasuredParagraph provides text information for rendering purpose. + * + * The first motivation of this class is identify the text directions and retrieving individual + * character widths. However retrieving character widths is slower than identifying text directions. + * Thus, this class provides several builder methods for specific purposes. + * + * - buildForBidi: + * Compute only text directions. + * - buildForMeasurement: + * Compute text direction and all character widths. + * - buildForStaticLayout: + * This is bit special. StaticLayout also needs to know text direction and character widths for + * line breaking, but all things are done in native code. Similarly, text measurement is done + * in native code. So instead of storing result to Java array, this keeps the result in native + * code since there is no good reason to move the results to Java layer. + * + * In addition to the character widths, some additional information is computed for each purposes, + * e.g. whole text length for measurement or font metrics for static layout. + * + * MeasuredParagraph is NOT a thread safe object. + * @hide + */ +public class MeasuredParagraph { + private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC'; + + private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( + MeasuredParagraph.class.getClassLoader(), nGetReleaseFunc(), 1024); + + private MeasuredParagraph() {} // Use build static functions instead. + + private static final SynchronizedPool<MeasuredParagraph> sPool = new SynchronizedPool<>(1); + + private static @NonNull MeasuredParagraph obtain() { // Use build static functions instead. + final MeasuredParagraph mt = sPool.acquire(); + return mt != null ? mt : new MeasuredParagraph(); + } + + /** + * Recycle the MeasuredParagraph. + * + * Do not call any methods after you call this method. + */ + public void recycle() { + release(); + sPool.release(this); + } + + // The casted original text. + // + // This may be null if the passed text is not a Spanned. + private @Nullable Spanned mSpanned; + + // The start offset of the target range in the original text (mSpanned); + private @IntRange(from = 0) int mTextStart; + + // The length of the target range in the original text. + private @IntRange(from = 0) int mTextLength; + + // The copied character buffer for measuring text. + // + // The length of this array is mTextLength. + private @Nullable char[] mCopiedBuffer; + + // The whole paragraph direction. + private @Layout.Direction int mParaDir; + + // True if the text is LTR direction and doesn't contain any bidi characters. + private boolean mLtrWithoutBidi; + + // The bidi level for individual characters. + // + // This is empty if mLtrWithoutBidi is true. + private @NonNull ByteArray mLevels = new ByteArray(); + + // The whole width of the text. + // See getWholeWidth comments. + private @FloatRange(from = 0.0f) float mWholeWidth; + + // Individual characters' widths. + // See getWidths comments. + private @Nullable FloatArray mWidths = new FloatArray(); + + // The span end positions. + // See getSpanEndCache comments. + private @Nullable IntArray mSpanEndCache = new IntArray(4); + + // The font metrics. + // See getFontMetrics comments. + private @Nullable IntArray mFontMetrics = new IntArray(4 * 4); + + // The native MeasuredParagraph. + // See getNativePtr comments. + // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead. + private /* Maybe Zero */ long mNativePtr = 0; + private @Nullable Runnable mNativeObjectCleaner; + + // Associate the native object to this Java object. + private void bindNativeObject(/* Non Zero*/ long nativePtr) { + mNativePtr = nativePtr; + mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr); + } + + // Decouple the native object from this Java object and release the native object. + private void unbindNativeObject() { + if (mNativePtr != 0) { + mNativeObjectCleaner.run(); + mNativePtr = 0; + } + } + + // Following two objects are for avoiding object allocation. + private @NonNull TextPaint mCachedPaint = new TextPaint(); + private @Nullable Paint.FontMetricsInt mCachedFm; + + /** + * Releases internal buffers. + */ + public void release() { + reset(); + mLevels.clearWithReleasingLargeArray(); + mWidths.clearWithReleasingLargeArray(); + mFontMetrics.clearWithReleasingLargeArray(); + mSpanEndCache.clearWithReleasingLargeArray(); + } + + /** + * Resets the internal state for starting new text. + */ + private void reset() { + mSpanned = null; + mCopiedBuffer = null; + mWholeWidth = 0; + mLevels.clear(); + mWidths.clear(); + mFontMetrics.clear(); + mSpanEndCache.clear(); + unbindNativeObject(); + } + + /** + * Returns the characters to be measured. + * + * This is always available. + */ + public @NonNull char[] getChars() { + return mCopiedBuffer; + } + + /** + * Returns the paragraph direction. + * + * This is always available. + */ + public @Layout.Direction int getParagraphDir() { + return mParaDir; + } + + /** + * Returns the directions. + * + * This is always available. + */ + public Directions getDirections(@IntRange(from = 0) int start, // inclusive + @IntRange(from = 0) int end) { // exclusive + if (mLtrWithoutBidi) { + return Layout.DIRS_ALL_LEFT_TO_RIGHT; + } + + final int length = end - start; + return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start, + length); + } + + /** + * Returns the whole text width. + * + * This is available only if the MeasureText is computed with computeForMeasurement. + * Returns 0 in other cases. + */ + public @FloatRange(from = 0.0f) float getWholeWidth() { + return mWholeWidth; + } + + /** + * Returns the individual character's width. + * + * This is available only if the MeasureText is computed with computeForMeasurement. + * Returns empty array in other cases. + */ + public @NonNull FloatArray getWidths() { + return mWidths; + } + + /** + * Returns the MetricsAffectingSpan end indices. + * + * If the input text is not a spanned string, this has one value that is the length of the text. + * + * This is available only if the MeasureText is computed with computeForStaticLayout. + * Returns empty array in other cases. + */ + public @NonNull IntArray getSpanEndCache() { + return mSpanEndCache; + } + + /** + * Returns the int array which holds FontMetrics. + * + * This array holds the repeat of top, bottom, ascent, descent of font metrics value. + * + * This is available only if the MeasureText is computed with computeForStaticLayout. + * Returns empty array in other cases. + */ + public @NonNull IntArray getFontMetrics() { + return mFontMetrics; + } + + /** + * Returns the native ptr of the MeasuredParagraph. + * + * This is available only if the MeasureText is computed with computeForStaticLayout. + * Returns 0 in other cases. + */ + public /* Maybe Zero */ long getNativePtr() { + return mNativePtr; + } + + /** + * Generates new MeasuredParagraph for Bidi computation. + * + * If recycle is null, this returns new instance. If recycle is not null, this fills computed + * result to recycle and returns recycle. + * + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + * @param recycle pass existing MeasuredParagraph if you want to recycle it. + * + * @return measured text + */ + public static @NonNull MeasuredParagraph buildForBidi(@NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextDirectionHeuristic textDir, + @Nullable MeasuredParagraph recycle) { + final MeasuredParagraph mt = recycle == null ? obtain() : recycle; + mt.resetAndAnalyzeBidi(text, start, end, textDir); + return mt; + } + + /** + * Generates new MeasuredParagraph for measuring texts. + * + * If recycle is null, this returns new instance. If recycle is not null, this fills computed + * result to recycle and returns recycle. + * + * @param paint the paint to be used for rendering the text. + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + * @param recycle pass existing MeasuredParagraph if you want to recycle it. + * + * @return measured text + */ + public static @NonNull MeasuredParagraph buildForMeasurement(@NonNull TextPaint paint, + @NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextDirectionHeuristic textDir, + @Nullable MeasuredParagraph recycle) { + final MeasuredParagraph mt = recycle == null ? obtain() : recycle; + mt.resetAndAnalyzeBidi(text, start, end, textDir); + + mt.mWidths.resize(mt.mTextLength); + if (mt.mTextLength == 0) { + return mt; + } + + if (mt.mSpanned == null) { + // No style change by MetricsAffectingSpan. Just measure all text. + mt.applyMetricsAffectingSpan( + paint, null /* spans */, start, end, 0 /* native static layout ptr */); + } else { + // There may be a MetricsAffectingSpan. Split into span transitions and apply styles. + int spanEnd; + for (int spanStart = start; spanStart < end; spanStart = spanEnd) { + spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class); + MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, + MetricAffectingSpan.class); + spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); + mt.applyMetricsAffectingSpan( + paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */); + } + } + return mt; + } + + /** + * Generates new MeasuredParagraph for StaticLayout. + * + * If recycle is null, this returns new instance. If recycle is not null, this fills computed + * result to recycle and returns recycle. + * + * @param paint the paint to be used for rendering the text. + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + * @param recycle pass existing MeasuredParagraph if you want to recycle it. + * + * @return measured text + */ + public static @NonNull MeasuredParagraph buildForStaticLayout( + @NonNull TextPaint paint, + @NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextDirectionHeuristic textDir, + @Nullable MeasuredParagraph recycle) { + final MeasuredParagraph mt = recycle == null ? obtain() : recycle; + mt.resetAndAnalyzeBidi(text, start, end, textDir); + if (mt.mTextLength == 0) { + // Need to build empty native measured text for StaticLayout. + // TODO: Stop creating empty measured text for empty lines. + long nativeBuilderPtr = nInitBuilder(); + try { + mt.bindNativeObject( + nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer)); + } finally { + nFreeBuilder(nativeBuilderPtr); + } + return mt; + } + + long nativeBuilderPtr = nInitBuilder(); + try { + if (mt.mSpanned == null) { + // No style change by MetricsAffectingSpan. Just measure all text. + mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr); + mt.mSpanEndCache.append(end); + } else { + // There may be a MetricsAffectingSpan. Split into span transitions and apply + // styles. + int spanEnd; + for (int spanStart = start; spanStart < end; spanStart = spanEnd) { + spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, + MetricAffectingSpan.class); + MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, + MetricAffectingSpan.class); + spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, + MetricAffectingSpan.class); + mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, + nativeBuilderPtr); + mt.mSpanEndCache.append(spanEnd); + } + } + mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer)); + } finally { + nFreeBuilder(nativeBuilderPtr); + } + + return mt; + } + + /** + * Reset internal state and analyzes text for bidirectional runs. + * + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + */ + private void resetAndAnalyzeBidi(@NonNull CharSequence text, + @IntRange(from = 0) int start, // inclusive + @IntRange(from = 0) int end, // exclusive + @NonNull TextDirectionHeuristic textDir) { + reset(); + mSpanned = text instanceof Spanned ? (Spanned) text : null; + mTextStart = start; + mTextLength = end - start; + + if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) { + mCopiedBuffer = new char[mTextLength]; + } + TextUtils.getChars(text, start, end, mCopiedBuffer, 0); + + // Replace characters associated with ReplacementSpan to U+FFFC. + if (mSpanned != null) { + ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class); + + for (int i = 0; i < spans.length; i++) { + int startInPara = mSpanned.getSpanStart(spans[i]) - start; + int endInPara = mSpanned.getSpanEnd(spans[i]) - start; + // The span interval may be larger and must be restricted to [start, end) + if (startInPara < 0) startInPara = 0; + if (endInPara > mTextLength) endInPara = mTextLength; + Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER); + } + } + + if ((textDir == TextDirectionHeuristics.LTR + || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR + || textDir == TextDirectionHeuristics.ANYRTL_LTR) + && TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) { + mLevels.clear(); + mParaDir = Layout.DIR_LEFT_TO_RIGHT; + mLtrWithoutBidi = true; + } else { + final int bidiRequest; + if (textDir == TextDirectionHeuristics.LTR) { + bidiRequest = Layout.DIR_REQUEST_LTR; + } else if (textDir == TextDirectionHeuristics.RTL) { + bidiRequest = Layout.DIR_REQUEST_RTL; + } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) { + bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR; + } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) { + bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL; + } else { + final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength); + bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR; + } + mLevels.resize(mTextLength); + mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray()); + mLtrWithoutBidi = false; + } + } + + private void applyReplacementRun(@NonNull ReplacementSpan replacement, + @IntRange(from = 0) int start, // inclusive, in copied buffer + @IntRange(from = 0) int end, // exclusive, in copied buffer + /* Maybe Zero */ long nativeBuilderPtr) { + // Use original text. Shouldn't matter. + // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for + // backward compatibility? or Should we initialize them for getFontMetricsInt? + final float width = replacement.getSize( + mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm); + if (nativeBuilderPtr == 0) { + // Assigns all width to the first character. This is the same behavior as minikin. + mWidths.set(start, width); + if (end > start + 1) { + Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f); + } + mWholeWidth += width; + } else { + nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, + width); + } + } + + private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer + @IntRange(from = 0) int end, // exclusive, in copied buffer + /* Maybe Zero */ long nativeBuilderPtr) { + if (nativeBuilderPtr != 0) { + mCachedPaint.getFontMetricsInt(mCachedFm); + } + + if (mLtrWithoutBidi) { + // If the whole text is LTR direction, just apply whole region. + if (nativeBuilderPtr == 0) { + mWholeWidth += mCachedPaint.getTextRunAdvances( + mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */, + mWidths.getRawArray(), start); + } else { + nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, + false /* isRtl */); + } + } else { + // If there is multiple bidi levels, split into individual bidi level and apply style. + byte level = mLevels.get(start); + // Note that the empty text or empty range won't reach this method. + // Safe to search from start + 1. + for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) { + if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point + final boolean isRtl = (level & 0x1) != 0; + if (nativeBuilderPtr == 0) { + final int levelLength = levelEnd - levelStart; + mWholeWidth += mCachedPaint.getTextRunAdvances( + mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, + isRtl, mWidths.getRawArray(), levelStart); + } else { + nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart, + levelEnd, isRtl); + } + if (levelEnd == end) { + break; + } + levelStart = levelEnd; + level = mLevels.get(levelEnd); + } + } + } + } + + private void applyMetricsAffectingSpan( + @NonNull TextPaint paint, + @Nullable MetricAffectingSpan[] spans, + @IntRange(from = 0) int start, // inclusive, in original text buffer + @IntRange(from = 0) int end, // exclusive, in original text buffer + /* Maybe Zero */ long nativeBuilderPtr) { + mCachedPaint.set(paint); + // XXX paint should not have a baseline shift, but... + mCachedPaint.baselineShift = 0; + + final boolean needFontMetrics = nativeBuilderPtr != 0; + + if (needFontMetrics && mCachedFm == null) { + mCachedFm = new Paint.FontMetricsInt(); + } + + ReplacementSpan replacement = null; + if (spans != null) { + for (int i = 0; i < spans.length; i++) { + MetricAffectingSpan span = spans[i]; + if (span instanceof ReplacementSpan) { + // The last ReplacementSpan is effective for backward compatibility reasons. + replacement = (ReplacementSpan) span; + } else { + // TODO: No need to call updateMeasureState for ReplacementSpan as well? + span.updateMeasureState(mCachedPaint); + } + } + } + + final int startInCopiedBuffer = start - mTextStart; + final int endInCopiedBuffer = end - mTextStart; + + if (replacement != null) { + applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, + nativeBuilderPtr); + } else { + applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr); + } + + if (needFontMetrics) { + if (mCachedPaint.baselineShift < 0) { + mCachedFm.ascent += mCachedPaint.baselineShift; + mCachedFm.top += mCachedPaint.baselineShift; + } else { + mCachedFm.descent += mCachedPaint.baselineShift; + mCachedFm.bottom += mCachedPaint.baselineShift; + } + + mFontMetrics.append(mCachedFm.top); + mFontMetrics.append(mCachedFm.bottom); + mFontMetrics.append(mCachedFm.ascent); + mFontMetrics.append(mCachedFm.descent); + } + } + + /** + * Returns the maximum index that the accumulated width not exceeds the width. + * + * If forward=false is passed, returns the minimum index from the end instead. + * + * This only works if the MeasuredParagraph is computed with computeForMeasurement. + * Undefined behavior in other case. + */ + @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) { + float[] w = mWidths.getRawArray(); + if (forwards) { + int i = 0; + while (i < limit) { + width -= w[i]; + if (width < 0.0f) break; + i++; + } + while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--; + return i; + } else { + int i = limit - 1; + while (i >= 0) { + width -= w[i]; + if (width < 0.0f) break; + i--; + } + while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) { + i++; + } + return limit - i - 1; + } + } + + /** + * Returns the length of the substring. + * + * This only works if the MeasuredParagraph is computed with computeForMeasurement. + * Undefined behavior in other case. + */ + @FloatRange(from = 0.0f) float measure(int start, int limit) { + float width = 0; + float[] w = mWidths.getRawArray(); + for (int i = start; i < limit; ++i) { + width += w[i]; + } + return width; + } + + private static native /* Non Zero */ long nInitBuilder(); + + /** + * Apply style to make native measured text. + * + * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. + * @param paintPtr The native paint pointer to be applied. + * @param start The start offset in the copied buffer. + * @param end The end offset in the copied buffer. + * @param isRtl True if the text is RTL. + */ + private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, + /* Non Zero */ long paintPtr, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + boolean isRtl); + + /** + * Apply ReplacementRun to make native measured text. + * + * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. + * @param paintPtr The native paint pointer to be applied. + * @param start The start offset in the copied buffer. + * @param end The end offset in the copied buffer. + * @param width The width of the replacement. + */ + private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr, + /* Non Zero */ long paintPtr, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @FloatRange(from = 0) float width); + + private static native long nBuildNativeMeasuredParagraph(/* Non Zero */ long nativeBuilderPtr, + @NonNull char[] text); + + private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); + + @CriticalNative + private static native /* Non Zero */ long nGetReleaseFunc(); +} diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java index 14d6f9e8a9ef..2c30360b81bc 100644 --- a/core/java/android/text/MeasuredText.java +++ b/core/java/android/text/MeasuredText.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,661 +16,255 @@ package android.text; -import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.Paint; -import android.text.AutoGrowArray.ByteArray; -import android.text.AutoGrowArray.FloatArray; -import android.text.AutoGrowArray.IntArray; -import android.text.Layout.Directions; -import android.text.style.MetricAffectingSpan; -import android.text.style.ReplacementSpan; -import android.util.Pools.SynchronizedPool; +import android.util.IntArray; -import dalvik.annotation.optimization.CriticalNative; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.Preconditions; -import libcore.util.NativeAllocationRegistry; - -import java.util.Arrays; +import java.util.ArrayList; /** - * MeasuredText provides text information for rendering purpose. - * - * The first motivation of this class is identify the text directions and retrieving individual - * character widths. However retrieving character widths is slower than identifying text directions. - * Thus, this class provides several builder methods for specific purposes. - * - * - buildForBidi: - * Compute only text directions. - * - buildForMeasurement: - * Compute text direction and all character widths. - * - buildForStaticLayout: - * This is bit special. StaticLayout also needs to know text direction and character widths for - * line breaking, but all things are done in native code. Similarly, text measurement is done - * in native code. So instead of storing result to Java array, this keeps the result in native - * code since there is no good reason to move the results to Java layer. - * - * In addition to the character widths, some additional information is computed for each purposes, - * e.g. whole text length for measurement or font metrics for static layout. - * - * MeasuredText is NOT a thread safe object. - * @hide + * A text which has already been measured. */ -public class MeasuredText { - private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC'; - - private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024); - - private MeasuredText() {} // Use build static functions instead. - - private static final SynchronizedPool<MeasuredText> sPool = new SynchronizedPool<>(1); - - private static @NonNull MeasuredText obtain() { // Use build static functions instead. - final MeasuredText mt = sPool.acquire(); - return mt != null ? mt : new MeasuredText(); - } - - /** - * Recycle the MeasuredText. - * - * Do not call any methods after you call this method. - */ - public void recycle() { - release(); - sPool.release(this); - } +public class MeasuredText implements Spanned { + private static final char LINE_FEED = '\n'; - // The casted original text. - // - // This may be null if the passed text is not a Spanned. - private @Nullable Spanned mSpanned; - - // The start offset of the target range in the original text (mSpanned); - private @IntRange(from = 0) int mTextStart; + // The original text. + private final @NonNull CharSequence mText; - // The length of the target range in the original text. - private @IntRange(from = 0) int mTextLength; - - // The copied character buffer for measuring text. - // - // The length of this array is mTextLength. - private @Nullable char[] mCopiedBuffer; + // The inclusive start offset of the measuring target. + private final @IntRange(from = 0) int mStart; - // The whole paragraph direction. - private @Layout.Direction int mParaDir; + // The exclusive end offset of the measuring target. + private final @IntRange(from = 0) int mEnd; - // True if the text is LTR direction and doesn't contain any bidi characters. - private boolean mLtrWithoutBidi; + // The TextPaint used for measurement. + private final @NonNull TextPaint mPaint; - // The bidi level for individual characters. - // - // This is empty if mLtrWithoutBidi is true. - private @NonNull ByteArray mLevels = new ByteArray(); - - // The whole width of the text. - // See getWholeWidth comments. - private @FloatRange(from = 0.0f) float mWholeWidth; - - // Individual characters' widths. - // See getWidths comments. - private @Nullable FloatArray mWidths = new FloatArray(); - - // The span end positions. - // See getSpanEndCache comments. - private @Nullable IntArray mSpanEndCache = new IntArray(4); - - // The font metrics. - // See getFontMetrics comments. - private @Nullable IntArray mFontMetrics = new IntArray(4 * 4); - - // The native MeasuredText. - // See getNativePtr comments. - // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead. - private /* Maybe Zero */ long mNativePtr = 0; - private @Nullable Runnable mNativeObjectCleaner; - - // Associate the native object to this Java object. - private void bindNativeObject(/* Non Zero*/ long nativePtr) { - mNativePtr = nativePtr; - mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr); - } + // The requested text direction. + private final @NonNull TextDirectionHeuristic mTextDir; - // Decouple the native object from this Java object and release the native object. - private void unbindNativeObject() { - if (mNativePtr != 0) { - mNativeObjectCleaner.run(); - mNativePtr = 0; - } - } + // The measured paragraph texts. + private final @NonNull MeasuredParagraph[] mMeasuredParagraphs; - // Following two objects are for avoiding object allocation. - private @NonNull TextPaint mCachedPaint = new TextPaint(); - private @Nullable Paint.FontMetricsInt mCachedFm; - - /** - * Releases internal buffers. - */ - public void release() { - reset(); - mLevels.clearWithReleasingLargeArray(); - mWidths.clearWithReleasingLargeArray(); - mFontMetrics.clearWithReleasingLargeArray(); - mSpanEndCache.clearWithReleasingLargeArray(); - } - - /** - * Resets the internal state for starting new text. - */ - private void reset() { - mSpanned = null; - mCopiedBuffer = null; - mWholeWidth = 0; - mLevels.clear(); - mWidths.clear(); - mFontMetrics.clear(); - mSpanEndCache.clear(); - unbindNativeObject(); - } + // The sorted paragraph end offsets. + private final @NonNull int[] mParagraphBreakPoints; /** - * Returns the characters to be measured. + * Build MeasuredText from the text. * - * This is always available. + * @param text The text to be measured. + * @param paint The paint to be used for drawing. + * @param textDir The text direction. + * @return The measured text. */ - public @NonNull char[] getChars() { - return mCopiedBuffer; + public static @NonNull MeasuredText build(@NonNull CharSequence text, + @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir) { + return MeasuredText.build(text, paint, textDir, 0, text.length()); } /** - * Returns the paragraph direction. + * Build MeasuredText from the specific range of the text.. * - * This is always available. + * @param text The text to be measured. + * @param paint The paint to be used for drawing. + * @param textDir The text direction. + * @param start The inclusive start offset of the text. + * @param end The exclusive start offset of the text. + * @return The measured text. */ - public @Layout.Direction int getParagraphDir() { - return mParaDir; - } + public static @NonNull MeasuredText build(@NonNull CharSequence text, + @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end) { + Preconditions.checkNotNull(text); + Preconditions.checkNotNull(paint); + Preconditions.checkNotNull(textDir); + Preconditions.checkArgumentInRange(start, 0, text.length(), "start"); + Preconditions.checkArgumentInRange(end, 0, text.length(), "end"); + + final IntArray paragraphEnds = new IntArray(); + final ArrayList<MeasuredParagraph> measuredTexts = new ArrayList<>(); + + int paraEnd = 0; + for (int paraStart = start; paraStart < end; paraStart = paraEnd) { + paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end); + if (paraEnd < 0) { + // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end. + paraEnd = end; + } else { + paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph. + } - /** - * Returns the directions. - * - * This is always available. - */ - public Directions getDirections(@IntRange(from = 0) int start, // inclusive - @IntRange(from = 0) int end) { // exclusive - if (mLtrWithoutBidi) { - return Layout.DIRS_ALL_LEFT_TO_RIGHT; + paragraphEnds.add(paraEnd); + measuredTexts.add(MeasuredParagraph.buildForStaticLayout( + paint, text, paraStart, paraEnd, textDir, null /* no recycle */)); } - final int length = end - start; - return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start, - length); + return new MeasuredText(text, start, end, paint, textDir, + measuredTexts.toArray(new MeasuredParagraph[measuredTexts.size()]), + paragraphEnds.toArray()); } - /** - * Returns the whole text width. - * - * This is available only if the MeasureText is computed with computeForMeasurement. - * Returns 0 in other cases. - */ - public @FloatRange(from = 0.0f) float getWholeWidth() { - return mWholeWidth; + // Use MeasuredText.build instead. + private MeasuredText(@NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir, + @NonNull MeasuredParagraph[] measuredTexts, + @NonNull int[] paragraphBreakPoints) { + mText = text; + mStart = start; + mEnd = end; + mPaint = paint; + mMeasuredParagraphs = measuredTexts; + mParagraphBreakPoints = paragraphBreakPoints; + mTextDir = textDir; } /** - * Returns the individual character's width. - * - * This is available only if the MeasureText is computed with computeForMeasurement. - * Returns empty array in other cases. + * Return the underlying text. */ - public @NonNull FloatArray getWidths() { - return mWidths; + public @NonNull CharSequence getText() { + return mText; } /** - * Returns the MetricsAffectingSpan end indices. - * - * If the input text is not a spanned string, this has one value that is the length of the text. - * - * This is available only if the MeasureText is computed with computeForStaticLayout. - * Returns empty array in other cases. + * Returns the inclusive start offset of measured region. */ - public @NonNull IntArray getSpanEndCache() { - return mSpanEndCache; + public @IntRange(from = 0) int getStart() { + return mStart; } /** - * Returns the int array which holds FontMetrics. - * - * This array holds the repeat of top, bottom, ascent, descent of font metrics value. - * - * This is available only if the MeasureText is computed with computeForStaticLayout. - * Returns empty array in other cases. + * Returns the exclusive end offset of measured region. */ - public @NonNull IntArray getFontMetrics() { - return mFontMetrics; + public @IntRange(from = 0) int getEnd() { + return mEnd; } /** - * Returns the native ptr of the MeasuredText. - * - * This is available only if the MeasureText is computed with computeForStaticLayout. - * Returns 0 in other cases. + * Returns the text direction associated with char sequence. */ - public /* Maybe Zero */ long getNativePtr() { - return mNativePtr; + public @NonNull TextDirectionHeuristic getTextDir() { + return mTextDir; } /** - * Generates new MeasuredText for Bidi computation. - * - * If recycle is null, this returns new instance. If recycle is not null, this fills computed - * result to recycle and returns recycle. - * - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction - * @param recycle pass existing MeasuredText if you want to recycle it. - * - * @return measured text + * Returns the paint used to measure this text. */ - public static @NonNull MeasuredText buildForBidi(@NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, - @Nullable MeasuredText recycle) { - final MeasuredText mt = recycle == null ? obtain() : recycle; - mt.resetAndAnalyzeBidi(text, start, end, textDir); - return mt; + public @NonNull TextPaint getPaint() { + return mPaint; } /** - * Generates new MeasuredText for measuring texts. - * - * If recycle is null, this returns new instance. If recycle is not null, this fills computed - * result to recycle and returns recycle. - * - * @param paint the paint to be used for rendering the text. - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction - * @param recycle pass existing MeasuredText if you want to recycle it. - * - * @return measured text + * Returns the length of the paragraph of this text. */ - public static @NonNull MeasuredText buildForMeasurement(@NonNull TextPaint paint, - @NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, - @Nullable MeasuredText recycle) { - final MeasuredText mt = recycle == null ? obtain() : recycle; - mt.resetAndAnalyzeBidi(text, start, end, textDir); - - mt.mWidths.resize(mt.mTextLength); - if (mt.mTextLength == 0) { - return mt; - } - - if (mt.mSpanned == null) { - // No style change by MetricsAffectingSpan. Just measure all text. - mt.applyMetricsAffectingSpan( - paint, null /* spans */, start, end, 0 /* native static layout ptr */); - } else { - // There may be a MetricsAffectingSpan. Split into span transitions and apply styles. - int spanEnd; - for (int spanStart = start; spanStart < end; spanStart = spanEnd) { - spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class); - MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, - MetricAffectingSpan.class); - spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); - mt.applyMetricsAffectingSpan( - paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */); - } - } - return mt; + public @IntRange(from = 0) int getParagraphCount() { + return mParagraphBreakPoints.length; } /** - * Generates new MeasuredText for StaticLayout. - * - * If recycle is null, this returns new instance. If recycle is not null, this fills computed - * result to recycle and returns recycle. - * - * @param paint the paint to be used for rendering the text. - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction - * @param recycle pass existing MeasuredText if you want to recycle it. - * - * @return measured text + * Returns the paragraph start offset of the text. */ - public static @NonNull MeasuredText buildForStaticLayout( - @NonNull TextPaint paint, - @NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, - @Nullable MeasuredText recycle) { - final MeasuredText mt = recycle == null ? obtain() : recycle; - mt.resetAndAnalyzeBidi(text, start, end, textDir); - if (mt.mTextLength == 0) { - // Need to build empty native measured text for StaticLayout. - // TODO: Stop creating empty measured text for empty lines. - long nativeBuilderPtr = nInitBuilder(); - try { - mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer)); - } finally { - nFreeBuilder(nativeBuilderPtr); - } - return mt; - } - - long nativeBuilderPtr = nInitBuilder(); - try { - if (mt.mSpanned == null) { - // No style change by MetricsAffectingSpan. Just measure all text. - mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr); - mt.mSpanEndCache.append(end); - } else { - // There may be a MetricsAffectingSpan. Split into span transitions and apply - // styles. - int spanEnd; - for (int spanStart = start; spanStart < end; spanStart = spanEnd) { - spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, - MetricAffectingSpan.class); - MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, - MetricAffectingSpan.class); - spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, - MetricAffectingSpan.class); - mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, - nativeBuilderPtr); - mt.mSpanEndCache.append(spanEnd); - } - } - mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer)); - } finally { - nFreeBuilder(nativeBuilderPtr); - } - - return mt; + public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) { + Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); + return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1]; } /** - * Reset internal state and analyzes text for bidirectional runs. - * - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction + * Returns the paragraph end offset of the text. */ - private void resetAndAnalyzeBidi(@NonNull CharSequence text, - @IntRange(from = 0) int start, // inclusive - @IntRange(from = 0) int end, // exclusive - @NonNull TextDirectionHeuristic textDir) { - reset(); - mSpanned = text instanceof Spanned ? (Spanned) text : null; - mTextStart = start; - mTextLength = end - start; - - if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) { - mCopiedBuffer = new char[mTextLength]; - } - TextUtils.getChars(text, start, end, mCopiedBuffer, 0); - - // Replace characters associated with ReplacementSpan to U+FFFC. - if (mSpanned != null) { - ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class); - - for (int i = 0; i < spans.length; i++) { - int startInPara = mSpanned.getSpanStart(spans[i]) - start; - int endInPara = mSpanned.getSpanEnd(spans[i]) - start; - // The span interval may be larger and must be restricted to [start, end) - if (startInPara < 0) startInPara = 0; - if (endInPara > mTextLength) endInPara = mTextLength; - Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER); - } - } - - if ((textDir == TextDirectionHeuristics.LTR || - textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR || - textDir == TextDirectionHeuristics.ANYRTL_LTR) && - TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) { - mLevels.clear(); - mParaDir = Layout.DIR_LEFT_TO_RIGHT; - mLtrWithoutBidi = true; - } else { - final int bidiRequest; - if (textDir == TextDirectionHeuristics.LTR) { - bidiRequest = Layout.DIR_REQUEST_LTR; - } else if (textDir == TextDirectionHeuristics.RTL) { - bidiRequest = Layout.DIR_REQUEST_RTL; - } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) { - bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR; - } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) { - bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL; - } else { - final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength); - bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR; - } - mLevels.resize(mTextLength); - mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray()); - mLtrWithoutBidi = false; - } + public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) { + Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); + return mParagraphBreakPoints[paraIndex]; } - private void applyReplacementRun(@NonNull ReplacementSpan replacement, - @IntRange(from = 0) int start, // inclusive, in copied buffer - @IntRange(from = 0) int end, // exclusive, in copied buffer - /* Maybe Zero */ long nativeBuilderPtr) { - // Use original text. Shouldn't matter. - // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for - // backward compatibility? or Should we initialize them for getFontMetricsInt? - final float width = replacement.getSize( - mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm); - if (nativeBuilderPtr == 0) { - // Assigns all width to the first character. This is the same behavior as minikin. - mWidths.set(start, width); - if (end > start + 1) { - Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f); - } - mWholeWidth += width; - } else { - nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, - width); - } + /** @hide */ + public @NonNull MeasuredParagraph getMeasuredParagraph(@IntRange(from = 0) int paraIndex) { + return mMeasuredParagraphs[paraIndex]; } - private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer - @IntRange(from = 0) int end, // exclusive, in copied buffer - /* Maybe Zero */ long nativeBuilderPtr) { - if (nativeBuilderPtr != 0) { - mCachedPaint.getFontMetricsInt(mCachedFm); - } + /////////////////////////////////////////////////////////////////////////////////////////////// + // Spanned overrides + // + // Just proxy for underlying mText if appropriate. - if (mLtrWithoutBidi) { - // If the whole text is LTR direction, just apply whole region. - if (nativeBuilderPtr == 0) { - mWholeWidth += mCachedPaint.getTextRunAdvances( - mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */, - mWidths.getRawArray(), start); - } else { - nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, - false /* isRtl */); - } + @Override + public <T> T[] getSpans(int start, int end, Class<T> type) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpans(start, end, type); } else { - // If there is multiple bidi levels, split into individual bidi level and apply style. - byte level = mLevels.get(start); - // Note that the empty text or empty range won't reach this method. - // Safe to search from start + 1. - for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) { - if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point - final boolean isRtl = (level & 0x1) != 0; - if (nativeBuilderPtr == 0) { - final int levelLength = levelEnd - levelStart; - mWholeWidth += mCachedPaint.getTextRunAdvances( - mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, - isRtl, mWidths.getRawArray(), levelStart); - } else { - nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart, - levelEnd, isRtl); - } - if (levelEnd == end) { - break; - } - levelStart = levelEnd; - level = mLevels.get(levelEnd); - } - } + return ArrayUtils.emptyArray(type); } } - private void applyMetricsAffectingSpan( - @NonNull TextPaint paint, - @Nullable MetricAffectingSpan[] spans, - @IntRange(from = 0) int start, // inclusive, in original text buffer - @IntRange(from = 0) int end, // exclusive, in original text buffer - /* Maybe Zero */ long nativeBuilderPtr) { - mCachedPaint.set(paint); - // XXX paint should not have a baseline shift, but... - mCachedPaint.baselineShift = 0; - - final boolean needFontMetrics = nativeBuilderPtr != 0; - - if (needFontMetrics && mCachedFm == null) { - mCachedFm = new Paint.FontMetricsInt(); - } - - ReplacementSpan replacement = null; - if (spans != null) { - for (int i = 0; i < spans.length; i++) { - MetricAffectingSpan span = spans[i]; - if (span instanceof ReplacementSpan) { - // The last ReplacementSpan is effective for backward compatibility reasons. - replacement = (ReplacementSpan) span; - } else { - // TODO: No need to call updateMeasureState for ReplacementSpan as well? - span.updateMeasureState(mCachedPaint); - } - } - } - - final int startInCopiedBuffer = start - mTextStart; - final int endInCopiedBuffer = end - mTextStart; - - if (replacement != null) { - applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, - nativeBuilderPtr); + @Override + public int getSpanStart(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanStart(tag); } else { - applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr); + return -1; } + } - if (needFontMetrics) { - if (mCachedPaint.baselineShift < 0) { - mCachedFm.ascent += mCachedPaint.baselineShift; - mCachedFm.top += mCachedPaint.baselineShift; - } else { - mCachedFm.descent += mCachedPaint.baselineShift; - mCachedFm.bottom += mCachedPaint.baselineShift; - } - - mFontMetrics.append(mCachedFm.top); - mFontMetrics.append(mCachedFm.bottom); - mFontMetrics.append(mCachedFm.ascent); - mFontMetrics.append(mCachedFm.descent); + @Override + public int getSpanEnd(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanEnd(tag); + } else { + return -1; } } - /** - * Returns the maximum index that the accumulated width not exceeds the width. - * - * If forward=false is passed, returns the minimum index from the end instead. - * - * This only works if the MeasuredText is computed with computeForMeasurement. - * Undefined behavior in other case. - */ - @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) { - float[] w = mWidths.getRawArray(); - if (forwards) { - int i = 0; - while (i < limit) { - width -= w[i]; - if (width < 0.0f) break; - i++; - } - while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--; - return i; + @Override + public int getSpanFlags(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanFlags(tag); } else { - int i = limit - 1; - while (i >= 0) { - width -= w[i]; - if (width < 0.0f) break; - i--; - } - while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) { - i++; - } - return limit - i - 1; + return 0; } } - /** - * Returns the length of the substring. - * - * This only works if the MeasuredText is computed with computeForMeasurement. - * Undefined behavior in other case. - */ - @FloatRange(from = 0.0f) float measure(int start, int limit) { - float width = 0; - float[] w = mWidths.getRawArray(); - for (int i = start; i < limit; ++i) { - width += w[i]; + @Override + public int nextSpanTransition(int start, int limit, Class type) { + if (mText instanceof Spanned) { + return ((Spanned) mText).nextSpanTransition(start, limit, type); + } else { + return mText.length(); } - return width; } - private static native /* Non Zero */ long nInitBuilder(); - - /** - * Apply style to make native measured text. - * - * @param nativeBuilderPtr The native MeasuredText builder pointer. - * @param paintPtr The native paint pointer to be applied. - * @param start The start offset in the copied buffer. - * @param end The end offset in the copied buffer. - * @param isRtl True if the text is RTL. - */ - private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, - /* Non Zero */ long paintPtr, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - boolean isRtl); + /////////////////////////////////////////////////////////////////////////////////////////////// + // CharSequence overrides. + // + // Just proxy for underlying mText. - /** - * Apply ReplacementRun to make native measured text. - * - * @param nativeBuilderPtr The native MeasuredText builder pointer. - * @param paintPtr The native paint pointer to be applied. - * @param start The start offset in the copied buffer. - * @param end The end offset in the copied buffer. - * @param width The width of the replacement. - */ - private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr, - /* Non Zero */ long paintPtr, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @FloatRange(from = 0) float width); + @Override + public int length() { + return mText.length(); + } - private static native long nBuildNativeMeasuredText(/* Non Zero */ long nativeBuilderPtr, - @NonNull char[] text); + @Override + public char charAt(int index) { + // TODO: Should this be index + mStart ? + return mText.charAt(index); + } - private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); + @Override + public CharSequence subSequence(int start, int end) { + // TODO: return MeasuredText. + // TODO: Should this be index + mStart, end + mStart ? + return mText.subSequence(start, end); + } - @CriticalNative - private static native /* Non Zero */ long nGetReleaseFunc(); + @Override + public String toString() { + return mText.toString(); + } } diff --git a/core/java/android/text/PremeasuredText.java b/core/java/android/text/PremeasuredText.java deleted file mode 100644 index 465314dd21ac..000000000000 --- a/core/java/android/text/PremeasuredText.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.text; - -import android.annotation.IntRange; -import android.annotation.NonNull; -import android.util.IntArray; - -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.Preconditions; - -import java.util.ArrayList; - -/** - * A text which has already been measured. - * - * TODO: Rename to better name? e.g. MeasuredText, FrozenText etc. - */ -public class PremeasuredText implements Spanned { - private static final char LINE_FEED = '\n'; - - // The original text. - private final @NonNull CharSequence mText; - - // The inclusive start offset of the measuring target. - private final @IntRange(from = 0) int mStart; - - // The exclusive end offset of the measuring target. - private final @IntRange(from = 0) int mEnd; - - // The TextPaint used for measurement. - private final @NonNull TextPaint mPaint; - - // The requested text direction. - private final @NonNull TextDirectionHeuristic mTextDir; - - // The measured paragraph texts. - private final @NonNull MeasuredText[] mMeasuredTexts; - - // The sorted paragraph end offsets. - private final @NonNull int[] mParagraphBreakPoints; - - /** - * Build PremeasuredText from the text. - * - * @param text The text to be measured. - * @param paint The paint to be used for drawing. - * @param textDir The text direction. - * @return The measured text. - */ - public static @NonNull PremeasuredText build(@NonNull CharSequence text, - @NonNull TextPaint paint, - @NonNull TextDirectionHeuristic textDir) { - return PremeasuredText.build(text, paint, textDir, 0, text.length()); - } - - /** - * Build PremeasuredText from the specific range of the text.. - * - * @param text The text to be measured. - * @param paint The paint to be used for drawing. - * @param textDir The text direction. - * @param start The inclusive start offset of the text. - * @param end The exclusive start offset of the text. - * @return The measured text. - */ - public static @NonNull PremeasuredText build(@NonNull CharSequence text, - @NonNull TextPaint paint, - @NonNull TextDirectionHeuristic textDir, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end) { - Preconditions.checkNotNull(text); - Preconditions.checkNotNull(paint); - Preconditions.checkNotNull(textDir); - Preconditions.checkArgumentInRange(start, 0, text.length(), "start"); - Preconditions.checkArgumentInRange(end, 0, text.length(), "end"); - - final IntArray paragraphEnds = new IntArray(); - final ArrayList<MeasuredText> measuredTexts = new ArrayList<>(); - - int paraEnd = 0; - for (int paraStart = start; paraStart < end; paraStart = paraEnd) { - paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end); - if (paraEnd < 0) { - // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end. - paraEnd = end; - } else { - paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph. - } - - paragraphEnds.add(paraEnd); - measuredTexts.add(MeasuredText.buildForStaticLayout( - paint, text, paraStart, paraEnd, textDir, null /* no recycle */)); - } - - return new PremeasuredText(text, start, end, paint, textDir, - measuredTexts.toArray(new MeasuredText[measuredTexts.size()]), - paragraphEnds.toArray()); - } - - // Use PremeasuredText.build instead. - private PremeasuredText(@NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextPaint paint, - @NonNull TextDirectionHeuristic textDir, - @NonNull MeasuredText[] measuredTexts, - @NonNull int[] paragraphBreakPoints) { - mText = text; - mStart = start; - mEnd = end; - mPaint = paint; - mMeasuredTexts = measuredTexts; - mParagraphBreakPoints = paragraphBreakPoints; - mTextDir = textDir; - } - - /** - * Return the underlying text. - */ - public @NonNull CharSequence getText() { - return mText; - } - - /** - * Returns the inclusive start offset of measured region. - */ - public @IntRange(from = 0) int getStart() { - return mStart; - } - - /** - * Returns the exclusive end offset of measured region. - */ - public @IntRange(from = 0) int getEnd() { - return mEnd; - } - - /** - * Returns the text direction associated with char sequence. - */ - public @NonNull TextDirectionHeuristic getTextDir() { - return mTextDir; - } - - /** - * Returns the paint used to measure this text. - */ - public @NonNull TextPaint getPaint() { - return mPaint; - } - - /** - * Returns the length of the paragraph of this text. - */ - public @IntRange(from = 0) int getParagraphCount() { - return mParagraphBreakPoints.length; - } - - /** - * Returns the paragraph start offset of the text. - */ - public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) { - Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); - return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1]; - } - - /** - * Returns the paragraph end offset of the text. - */ - public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) { - Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); - return mParagraphBreakPoints[paraIndex]; - } - - /** @hide */ - public @NonNull MeasuredText getMeasuredText(@IntRange(from = 0) int paraIndex) { - return mMeasuredTexts[paraIndex]; - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Spanned overrides - // - // Just proxy for underlying mText if appropriate. - - @Override - public <T> T[] getSpans(int start, int end, Class<T> type) { - if (mText instanceof Spanned) { - return ((Spanned) mText).getSpans(start, end, type); - } else { - return ArrayUtils.emptyArray(type); - } - } - - @Override - public int getSpanStart(Object tag) { - if (mText instanceof Spanned) { - return ((Spanned) mText).getSpanStart(tag); - } else { - return -1; - } - } - - @Override - public int getSpanEnd(Object tag) { - if (mText instanceof Spanned) { - return ((Spanned) mText).getSpanEnd(tag); - } else { - return -1; - } - } - - @Override - public int getSpanFlags(Object tag) { - if (mText instanceof Spanned) { - return ((Spanned) mText).getSpanFlags(tag); - } else { - return 0; - } - } - - @Override - public int nextSpanTransition(int start, int limit, Class type) { - if (mText instanceof Spanned) { - return ((Spanned) mText).nextSpanTransition(start, limit, type); - } else { - return mText.length(); - } - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // CharSequence overrides. - // - // Just proxy for underlying mText. - - @Override - public int length() { - return mText.length(); - } - - @Override - public char charAt(int index) { - // TODO: Should this be index + mStart ? - return mText.charAt(index); - } - - @Override - public CharSequence subSequence(int start, int end) { - // TODO: return PremeasuredText. - // TODO: Should this be index + mStart, end + mStart ? - return mText.subSequence(start, end); - } - - @Override - public String toString() { - return mText.toString(); - } -} diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index d69b1190140f..36bec863e7ac 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -55,7 +55,8 @@ public class StaticLayout extends Layout { * First, call nInit to setup native line breaker object. Then, for each paragraph, do the * following: * - * - Create MeasuredText by MeasuredText.buildForStaticLayout which measures in native. + * - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in + * native. * - Run nComputeLineBreaks() to obtain line breaks for the paragraph. * * After all paragraphs, call finish() to release expensive buffers. @@ -650,34 +651,34 @@ public class StaticLayout extends Layout { b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, indents, mLeftPaddings, mRightPaddings); - PremeasuredText premeasured = null; + MeasuredText measured = null; final Spanned spanned; - if (source instanceof PremeasuredText) { - premeasured = (PremeasuredText) source; + if (source instanceof MeasuredText) { + measured = (MeasuredText) source; - final CharSequence original = premeasured.getText(); + final CharSequence original = measured.getText(); spanned = (original instanceof Spanned) ? (Spanned) original : null; - if (bufStart != premeasured.getStart() || bufEnd != premeasured.getEnd()) { + if (bufStart != measured.getStart() || bufEnd != measured.getEnd()) { // The buffer position has changed. Re-measure here. - premeasured = PremeasuredText.build(original, paint, textDir, bufStart, bufEnd); + measured = MeasuredText.build(original, paint, textDir, bufStart, bufEnd); } else { - // We can use premeasured information. + // We can use measured information. - // Overwrite with the one when premeasured. + // Overwrite with the one when emeasured. // TODO: Give an option for developer not to overwrite and measure again here? - textDir = premeasured.getTextDir(); - paint = premeasured.getPaint(); + textDir = measured.getTextDir(); + paint = measured.getPaint(); } } else { - premeasured = PremeasuredText.build(source, paint, textDir, bufStart, bufEnd); + measured = MeasuredText.build(source, paint, textDir, bufStart, bufEnd); spanned = (source instanceof Spanned) ? (Spanned) source : null; } try { - for (int paraIndex = 0; paraIndex < premeasured.getParagraphCount(); paraIndex++) { - final int paraStart = premeasured.getParagraphStart(paraIndex); - final int paraEnd = premeasured.getParagraphEnd(paraIndex); + for (int paraIndex = 0; paraIndex < measured.getParagraphCount(); paraIndex++) { + final int paraStart = measured.getParagraphStart(paraIndex); + final int paraEnd = measured.getParagraphEnd(paraIndex); int firstWidthLineCount = 1; int firstWidth = outerWidth; @@ -743,10 +744,10 @@ public class StaticLayout extends Layout { } } - final MeasuredText measured = premeasured.getMeasuredText(paraIndex); - final char[] chs = measured.getChars(); - final int[] spanEndCache = measured.getSpanEndCache().getRawArray(); - final int[] fmCache = measured.getFontMetrics().getRawArray(); + final MeasuredParagraph measuredPara = measured.getMeasuredParagraph(paraIndex); + final char[] chs = measuredPara.getChars(); + final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray(); + final int[] fmCache = measuredPara.getFontMetrics().getRawArray(); // TODO: Stop keeping duplicated width copy in native and Java. widths.resize(chs.length); @@ -759,7 +760,7 @@ public class StaticLayout extends Layout { // Inputs chs, - measured.getNativePtr(), + measuredPara.getNativePtr(), paraEnd - paraStart, firstWidth, firstWidthLineCount, @@ -863,7 +864,7 @@ public class StaticLayout extends Layout { v = out(source, here, endPos, ascent, descent, fmTop, fmBottom, v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, - flags[breakIndex], needMultiply, measured, bufEnd, + flags[breakIndex], needMultiply, measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(), paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint, moreChars); @@ -894,8 +895,8 @@ public class StaticLayout extends Layout { if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) && mLineCount < mMaximumVisibleLineCount) { - final MeasuredText measured = - MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, null); + final MeasuredParagraph measuredPara = + MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null); paint.getFontMetricsInt(fm); v = out(source, bufEnd, bufEnd, fm.ascent, fm.descent, @@ -903,7 +904,7 @@ public class StaticLayout extends Layout { v, spacingmult, spacingadd, null, null, fm, 0, - needMultiply, measured, bufEnd, + needMultiply, measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, null, null, bufStart, ellipsize, ellipsizedWidth, 0, paint, false); @@ -918,7 +919,7 @@ public class StaticLayout extends Layout { private int out(final CharSequence text, final int start, final int end, int above, int below, int top, int bottom, int v, final float spacingmult, final float spacingadd, final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm, - final int flags, final boolean needMultiply, @NonNull final MeasuredText measured, + final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured, final int bufEnd, final boolean includePad, final boolean trackPad, final boolean addLastLineLineSpacing, final char[] chs, final float[] widths, final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth, diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 9c9fbf23832f..409e51438d20 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -1250,10 +1250,10 @@ public class TextUtils { @NonNull String ellipsis) { final int len = text.length(); - MeasuredText mt = null; - MeasuredText resultMt = null; + MeasuredParagraph mt = null; + MeasuredParagraph resultMt = null; try { - mt = MeasuredText.buildForMeasurement(paint, text, 0, text.length(), textDir, mt); + mt = MeasuredParagraph.buildForMeasurement(paint, text, 0, text.length(), textDir, mt); float width = mt.getWholeWidth(); if (width <= avail) { @@ -1332,7 +1332,7 @@ public class TextUtils { if (remaining == 0) { // All text is gone. textFits = true; } else { - resultMt = MeasuredText.buildForMeasurement( + resultMt = MeasuredParagraph.buildForMeasurement( paint, result, 0, result.length(), textDir, resultMt); width = resultMt.getWholeWidth(); if (width <= avail) { @@ -1479,11 +1479,11 @@ public class TextUtils { public static CharSequence commaEllipsize(CharSequence text, TextPaint p, float avail, String oneMore, String more, TextDirectionHeuristic textDir) { - MeasuredText mt = null; - MeasuredText tempMt = null; + MeasuredParagraph mt = null; + MeasuredParagraph tempMt = null; try { int len = text.length(); - mt = MeasuredText.buildForMeasurement(p, text, 0, len, textDir, mt); + mt = MeasuredParagraph.buildForMeasurement(p, text, 0, len, textDir, mt); final float width = mt.getWholeWidth(); if (width <= avail) { return text; @@ -1523,7 +1523,7 @@ public class TextUtils { } // XXX this is probably ok, but need to look at it more - tempMt = MeasuredText.buildForMeasurement( + tempMt = MeasuredParagraph.buildForMeasurement( p, format, 0, format.length(), textDir, tempMt); float moreWid = tempMt.getWholeWidth(); diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 914017f694e5..62f971724673 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -39,7 +39,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS = new HashMap<>(); DEFAULT_FLAGS.put("device_info_v2", "true"); DEFAULT_FLAGS.put("settings_search_v2", "true"); - DEFAULT_FLAGS.put("settings_app_info_v2", "false"); + DEFAULT_FLAGS.put("settings_app_info_v2", "true"); DEFAULT_FLAGS.put("settings_connected_device_v2", "true"); DEFAULT_FLAGS.put("settings_battery_v2", "false"); DEFAULT_FLAGS.put("settings_battery_display_app_list", "false"); diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index cc4a0b60dd0a..84ae20b92f3c 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -18,30 +18,18 @@ package android.util; import android.os.SystemClock; +import libcore.util.TimeZoneFinder; +import libcore.util.ZoneInfoDB; + import java.io.PrintWriter; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Calendar; -import java.util.Collection; -import java.util.Collections; import java.util.Date; -import java.util.List; -import libcore.util.TimeZoneFinder; -import libcore.util.ZoneInfoDB; - /** * A class containing utility methods related to time zones. */ public class TimeUtils { /** @hide */ public TimeUtils() {} - private static final boolean DBG = false; - private static final String TAG = "TimeUtils"; - - /** Cached results of getTimeZonesWithUniqueOffsets */ - private static final Object sLastUniqueLockObj = new Object(); - private static List<String> sLastUniqueZoneOffsets = null; - private static String sLastUniqueCountry = null; - /** {@hide} */ private static SimpleDateFormat sLoggingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @@ -76,86 +64,6 @@ public class TimeUtils { } /** - * Returns an immutable list of unique time zone IDs for the country. - * - * @param country to find - * @return unmodifiable list of unique time zones, maybe empty but never null. - * @hide - */ - public static List<String> getTimeZoneIdsWithUniqueOffsets(String country) { - synchronized(sLastUniqueLockObj) { - if ((country != null) && country.equals(sLastUniqueCountry)) { - if (DBG) { - Log.d(TAG, "getTimeZonesWithUniqueOffsets(" + - country + "): return cached version"); - } - return sLastUniqueZoneOffsets; - } - } - - Collection<android.icu.util.TimeZone> zones = getIcuTimeZones(country); - ArrayList<android.icu.util.TimeZone> uniqueTimeZones = new ArrayList<>(); - for (android.icu.util.TimeZone zone : zones) { - // See if we already have this offset, - // Using slow but space efficient and these are small. - boolean found = false; - for (int i = 0; i < uniqueTimeZones.size(); i++) { - if (uniqueTimeZones.get(i).getRawOffset() == zone.getRawOffset()) { - found = true; - break; - } - } - if (!found) { - if (DBG) { - Log.d(TAG, "getTimeZonesWithUniqueOffsets: add unique offset=" + - zone.getRawOffset() + " zone.getID=" + zone.getID()); - } - uniqueTimeZones.add(zone); - } - } - - synchronized(sLastUniqueLockObj) { - // Cache the last result - sLastUniqueZoneOffsets = extractZoneIds(uniqueTimeZones); - sLastUniqueCountry = country; - - return sLastUniqueZoneOffsets; - } - } - - private static List<String> extractZoneIds(List<android.icu.util.TimeZone> timeZones) { - List<String> ids = new ArrayList<>(timeZones.size()); - for (android.icu.util.TimeZone timeZone : timeZones) { - ids.add(timeZone.getID()); - } - return Collections.unmodifiableList(ids); - } - - /** - * Returns an immutable list of frozen ICU time zones for the country. - * - * @param countryIso is a two character country code. - * @return TimeZone list, maybe empty but never null. - * @hide - */ - private static List<android.icu.util.TimeZone> getIcuTimeZones(String countryIso) { - if (countryIso == null) { - if (DBG) Log.d(TAG, "getIcuTimeZones(null): return empty list"); - return Collections.emptyList(); - } - List<android.icu.util.TimeZone> timeZones = - TimeZoneFinder.getInstance().lookupTimeZonesByCountry(countryIso); - if (timeZones == null) { - if (DBG) { - Log.d(TAG, "getIcuTimeZones(" + countryIso - + "): returned null, converting to empty list"); - } - return Collections.emptyList(); - } - return timeZones; - } - - /** * Returns a String indicating the version of the time zone database currently * in use. The format of the string is dependent on the underlying time zone * database implementation, but will typically contain the year in which the database diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 57f9895f45fa..e5545405728d 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -1,17 +1,17 @@ /* - * Copyright (C) 2007-2008 The Android Open Source Project + * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package android.view.inputmethod; @@ -131,13 +131,13 @@ public interface InputConnection { * spans. <strong>Editor authors</strong>: you should strive to * send text with styles if possible, but it is not required. */ - static final int GET_TEXT_WITH_STYLES = 0x0001; + int GET_TEXT_WITH_STYLES = 0x0001; /** * Flag for use with {@link #getExtractedText} to indicate you * would like to receive updates when the extracted text changes. */ - public static final int GET_EXTRACTED_TEXT_MONITOR = 0x0001; + int GET_EXTRACTED_TEXT_MONITOR = 0x0001; /** * Get <var>n</var> characters of text before the current cursor @@ -176,7 +176,7 @@ public interface InputConnection { * @return the text before the cursor position; the length of the * returned text might be less than <var>n</var>. */ - public CharSequence getTextBeforeCursor(int n, int flags); + CharSequence getTextBeforeCursor(int n, int flags); /** * Get <var>n</var> characters of text after the current cursor @@ -215,7 +215,7 @@ public interface InputConnection { * @return the text after the cursor position; the length of the * returned text might be less than <var>n</var>. */ - public CharSequence getTextAfterCursor(int n, int flags); + CharSequence getTextAfterCursor(int n, int flags); /** * Gets the selected text, if any. @@ -249,7 +249,7 @@ public interface InputConnection { * later, returns false when the target application does not implement * this method. */ - public CharSequence getSelectedText(int flags); + CharSequence getSelectedText(int flags); /** * Retrieve the current capitalization mode in effect at the @@ -279,7 +279,7 @@ public interface InputConnection { * @return the caps mode flags that are in effect at the current * cursor position. See TYPE_TEXT_FLAG_CAPS_* in {@link android.text.InputType}. */ - public int getCursorCapsMode(int reqModes); + int getCursorCapsMode(int reqModes); /** * Retrieve the current text in the input connection's editor, and @@ -314,8 +314,7 @@ public interface InputConnection { * longer valid of the editor can't comply with the request for * some reason. */ - public ExtractedText getExtractedText(ExtractedTextRequest request, - int flags); + ExtractedText getExtractedText(ExtractedTextRequest request, int flags); /** * Delete <var>beforeLength</var> characters of text before the @@ -342,8 +341,8 @@ public interface InputConnection { * delete more characters than are in the editor, as that may have * ill effects on the application. Calling this method will cause * the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on your service after the batch input is over.</p> + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on your service after the batch input is over.</p> * * <p><strong>Editor authors:</strong> please be careful of race * conditions in implementing this call. An IME can make a change @@ -369,7 +368,7 @@ public interface InputConnection { * that range. * @return true on success, false if the input connection is no longer valid. */ - public boolean deleteSurroundingText(int beforeLength, int afterLength); + boolean deleteSurroundingText(int beforeLength, int afterLength); /** * A variant of {@link #deleteSurroundingText(int, int)}. Major differences are: @@ -397,7 +396,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer valid. Returns * {@code false} when the target application does not implement this method. */ - public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength); + boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength); /** * Replace the currently composing text with the given text, and @@ -416,8 +415,8 @@ public interface InputConnection { * <p>This is usually called by IMEs to add or remove or change * characters in the composing span. Calling this method will * cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over.</p> + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over.</p> * * <p><strong>Editor authors:</strong> please keep in mind the * text may be very similar or completely different than what was @@ -455,7 +454,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean setComposingText(CharSequence text, int newCursorPosition); + boolean setComposingText(CharSequence text, int newCursorPosition); /** * Mark a certain region of text as composing text. If there was a @@ -474,8 +473,8 @@ public interface InputConnection { * <p>Since this does not change the contents of the text, editors should not call * {@link InputMethodManager#updateSelection(View, int, int, int, int)} and * IMEs should not receive - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}. - * </p> + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)}.</p> * * <p>This has no impact on the cursor/selection position. It may * result in the cursor being anywhere inside or outside the @@ -488,7 +487,7 @@ public interface InputConnection { * valid. In {@link android.os.Build.VERSION_CODES#N} and later, false is returned when the * target application does not implement this method. */ - public boolean setComposingRegion(int start, int end); + boolean setComposingRegion(int start, int end); /** * Have the text editor finish whatever composing text is @@ -507,7 +506,7 @@ public interface InputConnection { * @return true on success, false if the input connection * is no longer valid. */ - public boolean finishComposingText(); + boolean finishComposingText(); /** * Commit text to the text box and set the new cursor position. @@ -522,8 +521,8 @@ public interface InputConnection { * then {@link #finishComposingText()}.</p> * * <p>Calling this method will cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -543,7 +542,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean commitText(CharSequence text, int newCursorPosition); + boolean commitText(CharSequence text, int newCursorPosition); /** * Commit a completion the user has selected from the possible ones @@ -569,8 +568,8 @@ public interface InputConnection { * * <p>Calling this method (with a valid {@link CompletionInfo} object) * will cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -581,15 +580,15 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean commitCompletion(CompletionInfo text); + boolean commitCompletion(CompletionInfo text); /** * Commit a correction automatically performed on the raw user's input. A * typical example would be to correct typos using a dictionary. * * <p>Calling this method will cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -601,7 +600,7 @@ public interface InputConnection { * In {@link android.os.Build.VERSION_CODES#N} and later, returns false * when the target application does not implement this method. */ - public boolean commitCorrection(CorrectionInfo correctionInfo); + boolean commitCorrection(CorrectionInfo correctionInfo); /** * Set the selection of the text editor. To set the cursor @@ -609,8 +608,8 @@ public interface InputConnection { * * <p>Since this moves the cursor, calling this method will cause * the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -628,7 +627,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean setSelection(int start, int end); + boolean setSelection(int start, int end); /** * Have the editor perform an action it has said it can do. @@ -642,7 +641,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean performEditorAction(int editorAction); + boolean performEditorAction(int editorAction); /** * Perform a context menu action on the field. The given id may be one of: @@ -652,7 +651,7 @@ public interface InputConnection { * {@link android.R.id#paste}, {@link android.R.id#copyUrl}, * or {@link android.R.id#switchInputMethod} */ - public boolean performContextMenuAction(int id); + boolean performContextMenuAction(int id); /** * Tell the editor that you are starting a batch of editor @@ -662,8 +661,8 @@ public interface InputConnection { * * <p><strong>IME authors:</strong> use this to avoid getting * calls to - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * corresponding to intermediate state. Also, use this to avoid + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} corresponding to intermediate state. Also, use this to avoid * flickers that may arise from displaying intermediate state. Be * sure to call {@link #endBatchEdit} for each call to this, or * you may block updates in the editor.</p> @@ -678,7 +677,7 @@ public interface InputConnection { * this method starts a batch edit, that means it will always return true * unless the input connection is no longer valid. */ - public boolean beginBatchEdit(); + boolean beginBatchEdit(); /** * Tell the editor that you are done with a batch edit previously @@ -696,7 +695,7 @@ public interface InputConnection { * the latest one (in other words, if the nesting count is > 0), false * otherwise or if the input connection is no longer valid. */ - public boolean endBatchEdit(); + boolean endBatchEdit(); /** * Send a key event to the process that is currently attached @@ -734,7 +733,7 @@ public interface InputConnection { * @see KeyCharacterMap#PREDICTIVE * @see KeyCharacterMap#ALPHA */ - public boolean sendKeyEvent(KeyEvent event); + boolean sendKeyEvent(KeyEvent event); /** * Clear the given meta key pressed states in the given input @@ -749,7 +748,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean clearMetaKeyStates(int states); + boolean clearMetaKeyStates(int states); /** * Called back when the connected IME switches between fullscreen and normal modes. @@ -766,7 +765,7 @@ public interface InputConnection { * devices. * @see InputMethodManager#isFullscreenMode() */ - public boolean reportFullscreenMode(boolean enabled); + boolean reportFullscreenMode(boolean enabled); /** * API to send private commands from an input method to its @@ -786,7 +785,7 @@ public interface InputConnection { * associated editor understood it), false if the input connection is no longer * valid. */ - public boolean performPrivateCommand(String action, Bundle data); + boolean performPrivateCommand(String action, Bundle data); /** * The editor is requested to call @@ -794,7 +793,7 @@ public interface InputConnection { * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be * used together with {@link #CURSOR_UPDATE_MONITOR}. */ - public static final int CURSOR_UPDATE_IMMEDIATE = 1 << 0; + int CURSOR_UPDATE_IMMEDIATE = 1 << 0; /** * The editor is requested to call @@ -805,7 +804,7 @@ public interface InputConnection { * This flag can be used together with {@link #CURSOR_UPDATE_IMMEDIATE}. * </p> */ - public static final int CURSOR_UPDATE_MONITOR = 1 << 1; + int CURSOR_UPDATE_MONITOR = 1 << 1; /** * Called by the input method to ask the editor for calling back @@ -821,7 +820,7 @@ public interface InputConnection { * In {@link android.os.Build.VERSION_CODES#N} and later, returns {@code false} also when the * target application does not implement this method. */ - public boolean requestCursorUpdates(int cursorUpdateMode); + boolean requestCursorUpdates(int cursorUpdateMode); /** * Called by the {@link InputMethodManager} to enable application developers to specify a @@ -832,7 +831,7 @@ public interface InputConnection { * * @return {@code null} to use the default {@link Handler}. */ - public Handler getHandler(); + Handler getHandler(); /** * Called by the system up to only once to notify that the system is about to invalidate @@ -846,7 +845,7 @@ public interface InputConnection { * * <p>Note: This does nothing when called from input methods.</p> */ - public void closeConnection(); + void closeConnection(); /** * When this flag is used, the editor will be able to request read access to the content URI @@ -863,7 +862,7 @@ public interface InputConnection { * client is able to request a temporary read-only access even after the current IME is switched * to any other IME as long as the client keeps {@link InputContentInfo} object.</p> **/ - public static int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = + int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; // 0x00000001 /** @@ -897,6 +896,6 @@ public interface InputConnection { * @return {@code true} if this request is accepted by the application, whether the request * is already handled or still being handled in background, {@code false} otherwise. */ - public boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags, + boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags, @Nullable Bundle opts); } diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index 317730ca092c..f671e22b4922 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -1,17 +1,17 @@ /* - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package android.view.inputmethod; @@ -74,6 +74,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public CharSequence getTextBeforeCursor(int n, int flags) { return mTarget.getTextBeforeCursor(n, flags); } @@ -82,6 +83,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public CharSequence getTextAfterCursor(int n, int flags) { return mTarget.getTextAfterCursor(n, flags); } @@ -90,6 +92,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public CharSequence getSelectedText(int flags) { return mTarget.getSelectedText(flags); } @@ -98,6 +101,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public int getCursorCapsMode(int reqModes) { return mTarget.getCursorCapsMode(reqModes); } @@ -106,6 +110,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { return mTarget.getExtractedText(request, flags); } @@ -114,6 +119,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { return mTarget.deleteSurroundingTextInCodePoints(beforeLength, afterLength); } @@ -122,6 +128,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean deleteSurroundingText(int beforeLength, int afterLength) { return mTarget.deleteSurroundingText(beforeLength, afterLength); } @@ -130,6 +137,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean setComposingText(CharSequence text, int newCursorPosition) { return mTarget.setComposingText(text, newCursorPosition); } @@ -138,6 +146,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean setComposingRegion(int start, int end) { return mTarget.setComposingRegion(start, end); } @@ -146,6 +155,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean finishComposingText() { return mTarget.finishComposingText(); } @@ -154,6 +164,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitText(CharSequence text, int newCursorPosition) { return mTarget.commitText(text, newCursorPosition); } @@ -162,6 +173,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitCompletion(CompletionInfo text) { return mTarget.commitCompletion(text); } @@ -170,6 +182,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitCorrection(CorrectionInfo correctionInfo) { return mTarget.commitCorrection(correctionInfo); } @@ -178,6 +191,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean setSelection(int start, int end) { return mTarget.setSelection(start, end); } @@ -186,6 +200,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean performEditorAction(int editorAction) { return mTarget.performEditorAction(editorAction); } @@ -194,6 +209,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean performContextMenuAction(int id) { return mTarget.performContextMenuAction(id); } @@ -202,6 +218,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean beginBatchEdit() { return mTarget.beginBatchEdit(); } @@ -210,6 +227,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean endBatchEdit() { return mTarget.endBatchEdit(); } @@ -218,6 +236,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean sendKeyEvent(KeyEvent event) { return mTarget.sendKeyEvent(event); } @@ -226,6 +245,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean clearMetaKeyStates(int states) { return mTarget.clearMetaKeyStates(states); } @@ -234,6 +254,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean reportFullscreenMode(boolean enabled) { return mTarget.reportFullscreenMode(enabled); } @@ -242,6 +263,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean performPrivateCommand(String action, Bundle data) { return mTarget.performPrivateCommand(action, data); } @@ -250,6 +272,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean requestCursorUpdates(int cursorUpdateMode) { return mTarget.requestCursorUpdates(cursorUpdateMode); } @@ -258,6 +281,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public Handler getHandler() { return mTarget.getHandler(); } @@ -266,6 +290,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public void closeConnection() { mTarget.closeConnection(); } @@ -274,6 +299,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { return mTarget.commitContent(inputContentInfo, flags, opts); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 80d7b6b7b7b2..7db5c3207296 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -337,20 +337,23 @@ public final class InputMethodManager { int mCursorCandEnd; /** - * Represents an invalid action notification sequence number. {@link InputMethodManagerService} - * always issues a positive integer for action notification sequence numbers. Thus -1 is - * guaranteed to be different from any valid sequence number. + * Represents an invalid action notification sequence number. + * {@link com.android.server.InputMethodManagerService} always issues a positive integer for + * action notification sequence numbers. Thus {@code -1} is guaranteed to be different from any + * valid sequence number. */ private static final int NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER = -1; /** - * The next sequence number that is to be sent to {@link InputMethodManagerService} via + * The next sequence number that is to be sent to + * {@link com.android.server.InputMethodManagerService} via * {@link IInputMethodManager#notifyUserAction(int)} at once when a user action is observed. */ private int mNextUserActionNotificationSequenceNumber = NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER; /** - * The last sequence number that is already sent to {@link InputMethodManagerService}. + * The last sequence number that is already sent to + * {@link com.android.server.InputMethodManagerService}. */ private int mLastSentUserActionNotificationSequenceNumber = NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER; @@ -1079,15 +1082,15 @@ public final class InputMethodManager { } /** - * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft - * input window should only be hidden if it was not explicitly shown + * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)} + * to indicate that the soft input window should only be hidden if it was not explicitly shown * by the user. */ public static final int HIDE_IMPLICIT_ONLY = 0x0001; /** - * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft - * input window should normally be hidden, unless it was originally + * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestShowSelf(int)} + * to indicate that the soft input window should normally be hidden, unless it was originally * shown with {@link #SHOW_FORCED}. */ public static final int HIDE_NOT_ALWAYS = 0x0002; @@ -1255,12 +1258,7 @@ public final class InputMethodManager { // The view is running on a different thread than our own, so // we need to reschedule our work for over there. if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread"); - vh.post(new Runnable() { - @Override - public void run() { - startInputInner(startInputReason, null, 0, 0, 0); - } - }); + vh.post(() -> startInputInner(startInputReason, null, 0, 0, 0)); return false; } @@ -1871,9 +1869,9 @@ public final class InputMethodManager { * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #HIDE_IMPLICIT_ONLY}, * {@link #HIDE_NOT_ALWAYS} bit set. - * @deprecated Use {@link InputMethodService#hideSoftInputFromInputMethod(int)} - * instead. This method was intended for IME developers who should be accessing APIs through - * the service. APIs in this class are intended for app developers interacting with the IME. + * @deprecated Use {@link InputMethodService#requestHideSelf(int)} instead. This method was + * intended for IME developers who should be accessing APIs through the service. APIs in this + * class are intended for app developers interacting with the IME. */ @Deprecated public void hideSoftInputFromInputMethod(IBinder token, int flags) { @@ -1903,9 +1901,9 @@ public final class InputMethodManager { * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #SHOW_IMPLICIT} or * {@link #SHOW_FORCED} bit set. - * @deprecated Use {@link InputMethodService#showSoftInputFromInputMethod(int)} - * instead. This method was intended for IME developers who should be accessing APIs through - * the service. APIs in this class are intended for app developers interacting with the IME. + * @deprecated Use {@link InputMethodService#requestShowSelf(int)} instead. This method was + * intended for IME developers who should be accessing APIs through the service. APIs in this + * class are intended for app developers interacting with the IME. */ @Deprecated public void showSoftInputFromInputMethod(IBinder token, int flags) { @@ -2429,8 +2427,8 @@ public final class InputMethodManager { * Allow the receiver of {@link InputContentInfo} to obtain a temporary read-only access * permission to the content. * - * <p>See {@link android.inputmethodservice.InputMethodService#exposeContent(InputContentInfo, EditorInfo)} - * for details.</p> + * <p>See {@link android.inputmethodservice.InputMethodService#exposeContent(InputContentInfo, + * InputConnection)} for details.</p> * * @param token Supplies the identifying token given to an input method when it was started, * which allows it to perform this operation on itself. diff --git a/core/java/android/view/textclassifier/EntityConfidence.java b/core/java/android/view/textclassifier/EntityConfidence.java index 19660d95e927..69a59a5b4b36 100644 --- a/core/java/android/view/textclassifier/EntityConfidence.java +++ b/core/java/android/view/textclassifier/EntityConfidence.java @@ -18,6 +18,8 @@ package android.view.textclassifier; import android.annotation.FloatRange; import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; import android.util.ArrayMap; import com.android.internal.util.Preconditions; @@ -30,17 +32,16 @@ import java.util.Map; /** * Helper object for setting and getting entity scores for classified text. * - * @param <T> the entity type. * @hide */ -final class EntityConfidence<T> { +final class EntityConfidence implements Parcelable { - private final ArrayMap<T, Float> mEntityConfidence = new ArrayMap<>(); - private final ArrayList<T> mSortedEntities = new ArrayList<>(); + private final ArrayMap<String, Float> mEntityConfidence = new ArrayMap<>(); + private final ArrayList<String> mSortedEntities = new ArrayList<>(); EntityConfidence() {} - EntityConfidence(@NonNull EntityConfidence<T> source) { + EntityConfidence(@NonNull EntityConfidence source) { Preconditions.checkNotNull(source); mEntityConfidence.putAll(source.mEntityConfidence); mSortedEntities.addAll(source.mSortedEntities); @@ -54,24 +55,16 @@ final class EntityConfidence<T> { * @param source a map from entity to a confidence value in the range 0 (low confidence) to * 1 (high confidence). */ - EntityConfidence(@NonNull Map<T, Float> source) { + EntityConfidence(@NonNull Map<String, Float> source) { Preconditions.checkNotNull(source); // Prune non-existent entities and clamp to 1. mEntityConfidence.ensureCapacity(source.size()); - for (Map.Entry<T, Float> it : source.entrySet()) { + for (Map.Entry<String, Float> it : source.entrySet()) { if (it.getValue() <= 0) continue; mEntityConfidence.put(it.getKey(), Math.min(1, it.getValue())); } - - // Create a list of entities sorted by decreasing confidence for getEntities(). - mSortedEntities.ensureCapacity(mEntityConfidence.size()); - mSortedEntities.addAll(mEntityConfidence.keySet()); - mSortedEntities.sort((e1, e2) -> { - float score1 = mEntityConfidence.get(e1); - float score2 = mEntityConfidence.get(e2); - return Float.compare(score2, score1); - }); + resetSortedEntitiesFromMap(); } /** @@ -79,7 +72,7 @@ final class EntityConfidence<T> { * high confidence to low confidence. */ @NonNull - public List<T> getEntities() { + public List<String> getEntities() { return Collections.unmodifiableList(mSortedEntities); } @@ -89,7 +82,7 @@ final class EntityConfidence<T> { * classified text. */ @FloatRange(from = 0.0, to = 1.0) - public float getConfidenceScore(T entity) { + public float getConfidenceScore(String entity) { if (mEntityConfidence.containsKey(entity)) { return mEntityConfidence.get(entity); } @@ -100,4 +93,51 @@ final class EntityConfidence<T> { public String toString() { return mEntityConfidence.toString(); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mEntityConfidence.size()); + for (Map.Entry<String, Float> entry : mEntityConfidence.entrySet()) { + dest.writeString(entry.getKey()); + dest.writeFloat(entry.getValue()); + } + } + + public static final Parcelable.Creator<EntityConfidence> CREATOR = + new Parcelable.Creator<EntityConfidence>() { + @Override + public EntityConfidence createFromParcel(Parcel in) { + return new EntityConfidence(in); + } + + @Override + public EntityConfidence[] newArray(int size) { + return new EntityConfidence[size]; + } + }; + + private EntityConfidence(Parcel in) { + final int numEntities = in.readInt(); + mEntityConfidence.ensureCapacity(numEntities); + for (int i = 0; i < numEntities; ++i) { + mEntityConfidence.put(in.readString(), in.readFloat()); + } + resetSortedEntitiesFromMap(); + } + + private void resetSortedEntitiesFromMap() { + mSortedEntities.clear(); + mSortedEntities.ensureCapacity(mEntityConfidence.size()); + mSortedEntities.addAll(mEntityConfidence.keySet()); + mSortedEntities.sort((e1, e2) -> { + float score1 = mEntityConfidence.get(e1); + float score2 = mEntityConfidence.get(e2); + return Float.compare(score2, score1); + }); + } } diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java index 7ffbf6357f45..7089677d744b 100644 --- a/core/java/android/view/textclassifier/TextClassification.java +++ b/core/java/android/view/textclassifier/TextClassification.java @@ -22,8 +22,13 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.LocaleList; +import android.os.Parcel; +import android.os.Parcelable; import android.util.ArrayMap; import android.view.View.OnClickListener; import android.view.textclassifier.TextClassifier.EntityType; @@ -52,7 +57,7 @@ import java.util.Map; * Button button = new Button(context); * button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null); * button.setText(classification.getLabel()); - * button.setOnClickListener(classification.getOnClickListener()); + * button.setOnClickListener(v -> context.startActivity(classification.getIntent())); * }</pre> * * <p>e.g. starting an action mode with menu items that can handle the classified text: @@ -90,7 +95,6 @@ import java.util.Map; * ... * }); * }</pre> - * */ public final class TextClassification { @@ -99,6 +103,10 @@ public final class TextClassification { */ static final TextClassification EMPTY = new TextClassification.Builder().build(); + // TODO(toki): investigate a way to derive this based on device properties. + private static final int MAX_PRIMARY_ICON_SIZE = 192; + private static final int MAX_SECONDARY_ICON_SIZE = 144; + @NonNull private final String mText; @Nullable private final Drawable mPrimaryIcon; @Nullable private final String mPrimaryLabel; @@ -107,8 +115,7 @@ public final class TextClassification { @NonNull private final List<Drawable> mSecondaryIcons; @NonNull private final List<String> mSecondaryLabels; @NonNull private final List<Intent> mSecondaryIntents; - @NonNull private final List<OnClickListener> mSecondaryOnClickListeners; - @NonNull private final EntityConfidence<String> mEntityConfidence; + @NonNull private final EntityConfidence mEntityConfidence; @NonNull private final String mSignature; private TextClassification( @@ -120,12 +127,10 @@ public final class TextClassification { @NonNull List<Drawable> secondaryIcons, @NonNull List<String> secondaryLabels, @NonNull List<Intent> secondaryIntents, - @NonNull List<OnClickListener> secondaryOnClickListeners, @NonNull Map<String, Float> entityConfidence, @NonNull String signature) { Preconditions.checkArgument(secondaryLabels.size() == secondaryIntents.size()); Preconditions.checkArgument(secondaryIcons.size() == secondaryIntents.size()); - Preconditions.checkArgument(secondaryOnClickListeners.size() == secondaryIntents.size()); mText = text; mPrimaryIcon = primaryIcon; mPrimaryLabel = primaryLabel; @@ -134,8 +139,7 @@ public final class TextClassification { mSecondaryIcons = secondaryIcons; mSecondaryLabels = secondaryLabels; mSecondaryIntents = secondaryIntents; - mSecondaryOnClickListeners = secondaryOnClickListeners; - mEntityConfidence = new EntityConfidence<>(entityConfidence); + mEntityConfidence = new EntityConfidence(entityConfidence); mSignature = signature; } @@ -186,7 +190,6 @@ public final class TextClassification { * @see #getSecondaryIntent(int) * @see #getSecondaryLabel(int) * @see #getSecondaryIcon(int) - * @see #getSecondaryOnClickListener(int) */ @IntRange(from = 0) public int getSecondaryActionsCount() { @@ -198,13 +201,10 @@ public final class TextClassification { * classified text. * * @param index Index of the action to get the icon for. - * * @throws IndexOutOfBoundsException if the specified index is out of range. - * * @see #getSecondaryActionsCount() for the number of actions available. * @see #getSecondaryIntent(int) * @see #getSecondaryLabel(int) - * @see #getSecondaryOnClickListener(int) * @see #getIcon() */ @Nullable @@ -228,13 +228,10 @@ public final class TextClassification { * the classified text. * * @param index Index of the action to get the label for. - * * @throws IndexOutOfBoundsException if the specified index is out of range. - * * @see #getSecondaryActionsCount() * @see #getSecondaryIntent(int) * @see #getSecondaryIcon(int) - * @see #getSecondaryOnClickListener(int) * @see #getLabel() */ @Nullable @@ -257,13 +254,10 @@ public final class TextClassification { * Returns one of the <i>secondary</i> intents that may be fired to act on the classified text. * * @param index Index of the action to get the intent for. - * * @throws IndexOutOfBoundsException if the specified index is out of range. - * * @see #getSecondaryActionsCount() * @see #getSecondaryLabel(int) * @see #getSecondaryIcon(int) - * @see #getSecondaryOnClickListener(int) * @see #getIntent() */ @Nullable @@ -282,29 +276,10 @@ public final class TextClassification { } /** - * Returns one of the <i>secondary</i> OnClickListeners that may be triggered to act on the - * classified text. - * - * @param index Index of the action to get the click listener for. - * - * @throws IndexOutOfBoundsException if the specified index is out of range. - * - * @see #getSecondaryActionsCount() - * @see #getSecondaryIntent(int) - * @see #getSecondaryLabel(int) - * @see #getSecondaryIcon(int) - * @see #getOnClickListener() - */ - @Nullable - public OnClickListener getSecondaryOnClickListener(int index) { - return mSecondaryOnClickListeners.get(index); - } - - /** * Returns the <i>primary</i> OnClickListener that may be triggered to act on the classified - * text. - * - * @see #getSecondaryOnClickListener(int) + * text. This field is not parcelable and will be null for all objects read from a parcel. + * Instead, call Context#startActivity(Intent) with the result of #getSecondaryIntent(int). + * Note that this may fail if the activity doesn't have permission to send the intent. */ @Nullable public OnClickListener getOnClickListener() { @@ -334,6 +309,42 @@ public final class TextClassification { mSignature); } + /** Helper for parceling via #ParcelableWrapper. */ + private void writeToParcel(Parcel dest, int flags) { + dest.writeString(mText); + final Bitmap primaryIconBitmap = drawableToBitmap(mPrimaryIcon, MAX_PRIMARY_ICON_SIZE); + dest.writeInt(primaryIconBitmap != null ? 1 : 0); + if (primaryIconBitmap != null) { + primaryIconBitmap.writeToParcel(dest, flags); + } + dest.writeString(mPrimaryLabel); + dest.writeInt(mPrimaryIntent != null ? 1 : 0); + if (mPrimaryIntent != null) { + mPrimaryIntent.writeToParcel(dest, flags); + } + // mPrimaryOnClickListener is not parcelable. + dest.writeTypedList(drawablesToBitmaps(mSecondaryIcons, MAX_SECONDARY_ICON_SIZE)); + dest.writeStringList(mSecondaryLabels); + dest.writeTypedList(mSecondaryIntents); + mEntityConfidence.writeToParcel(dest, flags); + dest.writeString(mSignature); + } + + /** Helper for unparceling via #ParcelableWrapper. */ + private TextClassification(Parcel in) { + mText = in.readString(); + mPrimaryIcon = in.readInt() == 0 + ? null : new BitmapDrawable(null, Bitmap.CREATOR.createFromParcel(in)); + mPrimaryLabel = in.readString(); + mPrimaryIntent = in.readInt() == 0 ? null : Intent.CREATOR.createFromParcel(in); + mPrimaryOnClickListener = null; // not parcelable + mSecondaryIcons = bitmapsToDrawables(in.createTypedArrayList(Bitmap.CREATOR)); + mSecondaryLabels = in.createStringArrayList(); + mSecondaryIntents = in.createTypedArrayList(Intent.CREATOR); + mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); + mSignature = in.readString(); + } + /** * Creates an OnClickListener that starts an activity with the specified intent. * @@ -349,6 +360,68 @@ public final class TextClassification { } /** + * Returns a Bitmap representation of the Drawable + * + * @param drawable The drawable to convert. + * @param maxDims The maximum edge length of the resulting bitmap (in pixels). + */ + @Nullable + private static Bitmap drawableToBitmap(@Nullable Drawable drawable, int maxDims) { + if (drawable == null) { + return null; + } + final int actualWidth = Math.max(1, drawable.getIntrinsicWidth()); + final int actualHeight = Math.max(1, drawable.getIntrinsicHeight()); + final double scaleWidth = ((double) maxDims) / actualWidth; + final double scaleHeight = ((double) maxDims) / actualHeight; + final double scale = Math.min(1.0, Math.min(scaleWidth, scaleHeight)); + final int width = (int) (actualWidth * scale); + final int height = (int) (actualHeight * scale); + if (drawable instanceof BitmapDrawable) { + final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; + if (actualWidth != width || actualHeight != height) { + return Bitmap.createScaledBitmap( + bitmapDrawable.getBitmap(), width, height, /*filter=*/false); + } else { + return bitmapDrawable.getBitmap(); + } + } else { + final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } + } + + /** + * Returns a list of drawables converted to Bitmaps + * + * @param drawables The drawables to convert. + * @param maxDims The maximum edge length of the resulting bitmaps (in pixels). + */ + private static List<Bitmap> drawablesToBitmaps(List<Drawable> drawables, int maxDims) { + final List<Bitmap> bitmaps = new ArrayList<>(drawables.size()); + for (Drawable drawable : drawables) { + bitmaps.add(drawableToBitmap(drawable, maxDims)); + } + return bitmaps; + } + + /** Returns a list of drawable wrappers for a list of bitmaps. */ + private static List<Drawable> bitmapsToDrawables(List<Bitmap> bitmaps) { + final List<Drawable> drawables = new ArrayList<>(bitmaps.size()); + for (Bitmap bitmap : bitmaps) { + if (bitmap != null) { + drawables.add(new BitmapDrawable(null, bitmap)); + } else { + drawables.add(null); + } + } + return drawables; + } + + /** * Builder for building {@link TextClassification} objects. * * <p>e.g. @@ -358,9 +431,9 @@ public final class TextClassification { * .setText(classifiedText) * .setEntityType(TextClassifier.TYPE_EMAIL, 0.9) * .setEntityType(TextClassifier.TYPE_OTHER, 0.1) - * .setPrimaryAction(intent, label, icon, onClickListener) - * .addSecondaryAction(intent1, label1, icon1, onClickListener1) - * .addSecondaryAction(intent2, label2, icon2, onClickListener2) + * .setPrimaryAction(intent, label, icon) + * .addSecondaryAction(intent1, label1, icon1) + * .addSecondaryAction(intent2, label2, icon2) * .build(); * }</pre> */ @@ -370,7 +443,6 @@ public final class TextClassification { @NonNull private final List<Drawable> mSecondaryIcons = new ArrayList<>(); @NonNull private final List<String> mSecondaryLabels = new ArrayList<>(); @NonNull private final List<Intent> mSecondaryIntents = new ArrayList<>(); - @NonNull private final List<OnClickListener> mSecondaryOnClickListeners = new ArrayList<>(); @NonNull private final Map<String, Float> mEntityConfidence = new ArrayMap<>(); @Nullable Drawable mPrimaryIcon; @Nullable String mPrimaryLabel; @@ -413,16 +485,14 @@ public final class TextClassification { * <p><stong>Note: </stong> If all input parameters are set to null, this method will be a * no-op. * - * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) + * @see #setPrimaryAction(Intent, String, Drawable) */ public Builder addSecondaryAction( - @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon, - @Nullable OnClickListener onClickListener) { - if (intent != null || label != null || icon != null || onClickListener != null) { + @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon) { + if (intent != null || label != null || icon != null) { mSecondaryIntents.add(intent); mSecondaryLabels.add(label); mSecondaryIcons.add(icon); - mSecondaryOnClickListeners.add(onClickListener); } return this; } @@ -432,7 +502,6 @@ public final class TextClassification { */ public Builder clearSecondaryActions() { mSecondaryIntents.clear(); - mSecondaryOnClickListeners.clear(); mSecondaryLabels.clear(); mSecondaryIcons.clear(); return this; @@ -440,26 +509,23 @@ public final class TextClassification { /** * Sets the <i>primary</i> action that may be performed on the classified text. This is - * equivalent to calling {@code - * setIntent(intent).setLabel(label).setIcon(icon).setOnClickListener(onClickListener)}. + * equivalent to calling {@code setIntent(intent).setLabel(label).setIcon(icon)}. * * <p><strong>Note: </strong>If all input parameters are null, there will be no * <i>primary</i> action but there may still be <i>secondary</i> actions. * - * @see #addSecondaryAction(Intent, String, Drawable, OnClickListener) + * @see #addSecondaryAction(Intent, String, Drawable) */ public Builder setPrimaryAction( - @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon, - @Nullable OnClickListener onClickListener) { - return setIntent(intent).setLabel(label).setIcon(icon) - .setOnClickListener(onClickListener); + @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon) { + return setIntent(intent).setLabel(label).setIcon(icon); } /** * Sets the icon for the <i>primary</i> action that may be rendered on a widget used to act * on the classified text. * - * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) + * @see #setPrimaryAction(Intent, String, Drawable) */ public Builder setIcon(@Nullable Drawable icon) { mPrimaryIcon = icon; @@ -470,7 +536,7 @@ public final class TextClassification { * Sets the label for the <i>primary</i> action that may be rendered on a widget used to * act on the classified text. * - * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) + * @see #setPrimaryAction(Intent, String, Drawable) */ public Builder setLabel(@Nullable String label) { mPrimaryLabel = label; @@ -481,7 +547,7 @@ public final class TextClassification { * Sets the intent for the <i>primary</i> action that may be fired to act on the classified * text. * - * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) + * @see #setPrimaryAction(Intent, String, Drawable) */ public Builder setIntent(@Nullable Intent intent) { mPrimaryIntent = intent; @@ -490,9 +556,8 @@ public final class TextClassification { /** * Sets the OnClickListener for the <i>primary</i> action that may be triggered to act on - * the classified text. - * - * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) + * the classified text. This field is not parcelable and will always be null when the + * object is read from a parcel. */ public Builder setOnClickListener(@Nullable OnClickListener onClickListener) { mPrimaryOnClickListener = onClickListener; @@ -515,10 +580,8 @@ public final class TextClassification { public TextClassification build() { return new TextClassification( mText, - mPrimaryIcon, mPrimaryLabel, - mPrimaryIntent, mPrimaryOnClickListener, - mSecondaryIcons, mSecondaryLabels, - mSecondaryIntents, mSecondaryOnClickListeners, + mPrimaryIcon, mPrimaryLabel, mPrimaryIntent, mPrimaryOnClickListener, + mSecondaryIcons, mSecondaryLabels, mSecondaryIntents, mEntityConfidence, mSignature); } } @@ -526,9 +589,11 @@ public final class TextClassification { /** * Optional input parameters for generating TextClassification. */ - public static final class Options { + public static final class Options implements Parcelable { + + private @Nullable LocaleList mDefaultLocales; - private LocaleList mDefaultLocales; + public Options() {} /** * @param defaultLocales ordered list of locale preferences that may be used to disambiguate @@ -548,5 +613,80 @@ public final class TextClassification { public LocaleList getDefaultLocales() { return mDefaultLocales; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mDefaultLocales != null ? 1 : 0); + if (mDefaultLocales != null) { + mDefaultLocales.writeToParcel(dest, flags); + } + } + + public static final Parcelable.Creator<Options> CREATOR = + new Parcelable.Creator<Options>() { + @Override + public Options createFromParcel(Parcel in) { + return new Options(in); + } + + @Override + public Options[] newArray(int size) { + return new Options[size]; + } + }; + + private Options(Parcel in) { + if (in.readInt() > 0) { + mDefaultLocales = LocaleList.CREATOR.createFromParcel(in); + } + } + } + + /** + * Parcelable wrapper for TextClassification objects. + * @hide + */ + public static final class ParcelableWrapper implements Parcelable { + + @NonNull private TextClassification mTextClassification; + + public ParcelableWrapper(@NonNull TextClassification textClassification) { + Preconditions.checkNotNull(textClassification); + mTextClassification = textClassification; + } + + @NonNull + public TextClassification getTextClassification() { + return mTextClassification; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + mTextClassification.writeToParcel(dest, flags); + } + + public static final Parcelable.Creator<ParcelableWrapper> CREATOR = + new Parcelable.Creator<ParcelableWrapper>() { + @Override + public ParcelableWrapper createFromParcel(Parcel in) { + return new ParcelableWrapper(new TextClassification(in)); + } + + @Override + public ParcelableWrapper[] newArray(int size) { + return new ParcelableWrapper[size]; + } + }; + } } diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index ed6043038600..b60209519f35 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -23,6 +23,8 @@ import android.annotation.Nullable; import android.annotation.StringDef; import android.annotation.WorkerThread; import android.os.LocaleList; +import android.os.Parcel; +import android.os.Parcelable; import android.util.ArraySet; import com.android.internal.util.Preconditions; @@ -305,7 +307,7 @@ public interface TextClassifier { * * Configs are initially based on a predefined preset, and can be modified from there. */ - final class EntityConfig { + final class EntityConfig implements Parcelable { private final @TextClassifier.EntityPreset int mEntityPreset; private final Collection<String> mExcludedEntityTypes; private final Collection<String> mIncludedEntityTypes; @@ -355,6 +357,37 @@ public interface TextClassifier { } return Collections.unmodifiableList(entities); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mEntityPreset); + dest.writeStringList(new ArrayList<>(mExcludedEntityTypes)); + dest.writeStringList(new ArrayList<>(mIncludedEntityTypes)); + } + + public static final Parcelable.Creator<EntityConfig> CREATOR = + new Parcelable.Creator<EntityConfig>() { + @Override + public EntityConfig createFromParcel(Parcel in) { + return new EntityConfig(in); + } + + @Override + public EntityConfig[] newArray(int size) { + return new EntityConfig[size]; + } + }; + + private EntityConfig(Parcel in) { + mEntityPreset = in.readInt(); + mExcludedEntityTypes = new ArraySet<>(in.createStringArrayList()); + mIncludedEntityTypes = new ArraySet<>(in.createStringArrayList()); + } } /** diff --git a/core/java/android/view/textclassifier/TextClassifierConstants.java b/core/java/android/view/textclassifier/TextClassifierConstants.java index 51e6168e9aa5..00695b797cb3 100644 --- a/core/java/android/view/textclassifier/TextClassifierConstants.java +++ b/core/java/android/view/textclassifier/TextClassifierConstants.java @@ -45,19 +45,24 @@ public final class TextClassifierConstants { "smart_selection_dark_launch"; private static final String SMART_SELECTION_ENABLED_FOR_EDIT_TEXT = "smart_selection_enabled_for_edit_text"; + private static final String SMART_LINKIFY_ENABLED = + "smart_linkify_enabled"; private static final boolean SMART_SELECTION_DARK_LAUNCH_DEFAULT = false; private static final boolean SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT = true; + private static final boolean SMART_LINKIFY_ENABLED_DEFAULT = true; /** Default settings. */ static final TextClassifierConstants DEFAULT = new TextClassifierConstants(); private final boolean mDarkLaunch; private final boolean mSuggestSelectionEnabledForEditableText; + private final boolean mSmartLinkifyEnabled; private TextClassifierConstants() { mDarkLaunch = SMART_SELECTION_DARK_LAUNCH_DEFAULT; mSuggestSelectionEnabledForEditableText = SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT; + mSmartLinkifyEnabled = SMART_LINKIFY_ENABLED_DEFAULT; } private TextClassifierConstants(@Nullable String settings) { @@ -74,6 +79,9 @@ public final class TextClassifierConstants { mSuggestSelectionEnabledForEditableText = parser.getBoolean( SMART_SELECTION_ENABLED_FOR_EDIT_TEXT, SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT); + mSmartLinkifyEnabled = parser.getBoolean( + SMART_LINKIFY_ENABLED, + SMART_LINKIFY_ENABLED_DEFAULT); } static TextClassifierConstants loadFromString(String settings) { @@ -87,4 +95,8 @@ public final class TextClassifierConstants { public boolean isSuggestSelectionEnabledForEditableText() { return mSuggestSelectionEnabledForEditableText; } + + public boolean isSmartLinkifyEnabled() { + return mSmartLinkifyEnabled; + } } diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index aea3cb0603f9..7db0e76d901f 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -32,7 +32,6 @@ import android.provider.ContactsContract; import android.provider.Settings; import android.text.util.Linkify; import android.util.Patterns; -import android.view.View.OnClickListener; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; @@ -187,6 +186,11 @@ final class TextClassifierImpl implements TextClassifier { Utils.validateInput(text); final String textString = text.toString(); final TextLinks.Builder builder = new TextLinks.Builder(textString); + + if (!getSettings().isSmartLinkifyEnabled()) { + return builder.build(); + } + try { final LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null; final Collection<String> entitiesToIdentify = @@ -457,12 +461,10 @@ final class TextClassifierImpl implements TextClassifier { } } final String labelString = (label != null) ? label.toString() : null; - final OnClickListener onClickListener = - TextClassification.createStartActivityOnClickListener(mContext, intent); if (i == 0) { - builder.setPrimaryAction(intent, labelString, icon, onClickListener); + builder.setPrimaryAction(intent, labelString, icon); } else { - builder.addSecondaryAction(intent, labelString, icon, onClickListener); + builder.addSecondaryAction(intent, labelString, icon); } } } diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java index 6c587cf9d475..ba854e040460 100644 --- a/core/java/android/view/textclassifier/TextLinks.java +++ b/core/java/android/view/textclassifier/TextLinks.java @@ -20,6 +20,8 @@ import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.LocaleList; +import android.os.Parcel; +import android.os.Parcelable; import android.text.SpannableString; import android.text.style.ClickableSpan; import android.view.View; @@ -38,7 +40,7 @@ import java.util.function.Function; * A collection of links, representing subsequences of text and the entity types (phone number, * address, url, etc) they may be. */ -public final class TextLinks { +public final class TextLinks implements Parcelable { private final String mFullText; private final List<TextLink> mLinks; @@ -83,11 +85,40 @@ public final class TextLinks { return true; } + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mFullText); + dest.writeTypedList(mLinks); + } + + public static final Parcelable.Creator<TextLinks> CREATOR = + new Parcelable.Creator<TextLinks>() { + @Override + public TextLinks createFromParcel(Parcel in) { + return new TextLinks(in); + } + + @Override + public TextLinks[] newArray(int size) { + return new TextLinks[size]; + } + }; + + private TextLinks(Parcel in) { + mFullText = in.readString(); + mLinks = in.createTypedArrayList(TextLink.CREATOR); + } + /** * A link, identifying a substring of text and possible entity types for it. */ - public static final class TextLink { - private final EntityConfidence<String> mEntityScores; + public static final class TextLink implements Parcelable { + private final EntityConfidence mEntityScores; private final String mOriginalText; private final int mStart; private final int mEnd; @@ -105,7 +136,7 @@ public final class TextLinks { mOriginalText = originalText; mStart = start; mEnd = end; - mEntityScores = new EntityConfidence<>(entityScores); + mEntityScores = new EntityConfidence(entityScores); } /** @@ -153,16 +184,51 @@ public final class TextLinks { @TextClassifier.EntityType String entityType) { return mEntityScores.getConfidenceScore(entityType); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + mEntityScores.writeToParcel(dest, flags); + dest.writeString(mOriginalText); + dest.writeInt(mStart); + dest.writeInt(mEnd); + } + + public static final Parcelable.Creator<TextLink> CREATOR = + new Parcelable.Creator<TextLink>() { + @Override + public TextLink createFromParcel(Parcel in) { + return new TextLink(in); + } + + @Override + public TextLink[] newArray(int size) { + return new TextLink[size]; + } + }; + + private TextLink(Parcel in) { + mEntityScores = EntityConfidence.CREATOR.createFromParcel(in); + mOriginalText = in.readString(); + mStart = in.readInt(); + mEnd = in.readInt(); + } } /** * Optional input parameters for generating TextLinks. */ - public static final class Options { + public static final class Options implements Parcelable { private LocaleList mDefaultLocales; private TextClassifier.EntityConfig mEntityConfig; + public Options() {} + /** * @param defaultLocales ordered list of locale preferences that may be used to * disambiguate the provided text. If no locale preferences exist, @@ -201,6 +267,45 @@ public final class TextLinks { public TextClassifier.EntityConfig getEntityConfig() { return mEntityConfig; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mDefaultLocales != null ? 1 : 0); + if (mDefaultLocales != null) { + mDefaultLocales.writeToParcel(dest, flags); + } + dest.writeInt(mEntityConfig != null ? 1 : 0); + if (mEntityConfig != null) { + mEntityConfig.writeToParcel(dest, flags); + } + } + + public static final Parcelable.Creator<Options> CREATOR = + new Parcelable.Creator<Options>() { + @Override + public Options createFromParcel(Parcel in) { + return new Options(in); + } + + @Override + public Options[] newArray(int size) { + return new Options[size]; + } + }; + + private Options(Parcel in) { + if (in.readInt() > 0) { + mDefaultLocales = LocaleList.CREATOR.createFromParcel(in); + } + if (in.readInt() > 0) { + mEntityConfig = TextClassifier.EntityConfig.CREATOR.createFromParcel(in); + } + } } /** diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java index 25e9e7ecf0e7..774d42db67a0 100644 --- a/core/java/android/view/textclassifier/TextSelection.java +++ b/core/java/android/view/textclassifier/TextSelection.java @@ -21,6 +21,8 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.LocaleList; +import android.os.Parcel; +import android.os.Parcelable; import android.util.ArrayMap; import android.view.textclassifier.TextClassifier.EntityType; @@ -36,7 +38,7 @@ public final class TextSelection { private final int mStartIndex; private final int mEndIndex; - @NonNull private final EntityConfidence<String> mEntityConfidence; + @NonNull private final EntityConfidence mEntityConfidence; @NonNull private final String mSignature; private TextSelection( @@ -44,7 +46,7 @@ public final class TextSelection { @NonNull String signature) { mStartIndex = startIndex; mEndIndex = endIndex; - mEntityConfidence = new EntityConfidence<>(entityConfidence); + mEntityConfidence = new EntityConfidence(entityConfidence); mSignature = signature; } @@ -110,6 +112,22 @@ public final class TextSelection { mStartIndex, mEndIndex, mEntityConfidence, mSignature); } + /** Helper for parceling via #ParcelableWrapper. */ + private void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mStartIndex); + dest.writeInt(mEndIndex); + mEntityConfidence.writeToParcel(dest, flags); + dest.writeString(mSignature); + } + + /** Helper for unparceling via #ParcelableWrapper. */ + private TextSelection(Parcel in) { + mStartIndex = in.readInt(); + mEndIndex = in.readInt(); + mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); + mSignature = in.readString(); + } + /** * Builder used to build {@link TextSelection} objects. */ @@ -170,11 +188,13 @@ public final class TextSelection { /** * Optional input parameters for generating TextSelection. */ - public static final class Options { + public static final class Options implements Parcelable { - private LocaleList mDefaultLocales; + private @Nullable LocaleList mDefaultLocales; private boolean mDarkLaunchAllowed; + public Options() {} + /** * @param defaultLocales ordered list of locale preferences that may be used to disambiguate * the provided text. If no locale preferences exist, set this to null or an empty @@ -216,5 +236,82 @@ public final class TextSelection { public boolean isDarkLaunchAllowed() { return mDarkLaunchAllowed; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mDefaultLocales != null ? 1 : 0); + if (mDefaultLocales != null) { + mDefaultLocales.writeToParcel(dest, flags); + } + dest.writeInt(mDarkLaunchAllowed ? 1 : 0); + } + + public static final Parcelable.Creator<Options> CREATOR = + new Parcelable.Creator<Options>() { + @Override + public Options createFromParcel(Parcel in) { + return new Options(in); + } + + @Override + public Options[] newArray(int size) { + return new Options[size]; + } + }; + + private Options(Parcel in) { + if (in.readInt() > 0) { + mDefaultLocales = LocaleList.CREATOR.createFromParcel(in); + } + mDarkLaunchAllowed = in.readInt() != 0; + } + } + + /** + * Parcelable wrapper for TextSelection objects. + * @hide + */ + public static final class ParcelableWrapper implements Parcelable { + + @NonNull private TextSelection mTextSelection; + + public ParcelableWrapper(@NonNull TextSelection textSelection) { + Preconditions.checkNotNull(textSelection); + mTextSelection = textSelection; + } + + @NonNull + public TextSelection getTextSelection() { + return mTextSelection; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + mTextSelection.writeToParcel(dest, flags); + } + + public static final Parcelable.Creator<ParcelableWrapper> CREATOR = + new Parcelable.Creator<ParcelableWrapper>() { + @Override + public ParcelableWrapper createFromParcel(Parcel in) { + return new ParcelableWrapper(new TextSelection(in)); + } + + @Override + public ParcelableWrapper[] newArray(int size) { + return new ParcelableWrapper[size]; + } + }; + } } diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index b3522ec94c0c..e9fe481112a2 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -27,7 +27,6 @@ import android.content.pm.PackageManager; import android.content.pm.Signature; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.StrictMode; import android.os.Trace; import android.util.AndroidRuntimeException; import android.util.ArraySet; @@ -251,7 +250,6 @@ public final class WebViewFactory { "WebView.disableWebView() was called: WebView is disabled"); } - StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()"); try { Class<WebViewFactoryProvider> providerClass = getProviderClass(); @@ -279,7 +277,6 @@ public final class WebViewFactory { } } finally { Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); - StrictMode.setThreadPolicy(oldPolicy); } } } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index bddba07766a9..247c806e928e 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -4006,7 +4006,6 @@ public class Editor { if (isValidAssistMenuItem( textClassification.getIcon(), textClassification.getLabel(), - textClassification.getOnClickListener(), textClassification.getIntent())) { final MenuItem item = menu.add( TextView.ID_ASSIST, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST, @@ -4014,14 +4013,15 @@ public class Editor { .setIcon(textClassification.getIcon()) .setIntent(textClassification.getIntent()); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - mAssistClickHandlers.put(item, textClassification.getOnClickListener()); + mAssistClickHandlers.put( + item, TextClassification.createStartActivityOnClickListener( + mTextView.getContext(), textClassification.getIntent())); } final int count = textClassification.getSecondaryActionsCount(); for (int i = 0; i < count; i++) { if (!isValidAssistMenuItem( textClassification.getSecondaryIcon(i), textClassification.getSecondaryLabel(i), - textClassification.getSecondaryOnClickListener(i), textClassification.getSecondaryIntent(i))) { continue; } @@ -4032,7 +4032,9 @@ public class Editor { .setIcon(textClassification.getSecondaryIcon(i)) .setIntent(textClassification.getSecondaryIntent(i)); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - mAssistClickHandlers.put(item, textClassification.getSecondaryOnClickListener(i)); + mAssistClickHandlers.put(item, + TextClassification.createStartActivityOnClickListener( + mTextView.getContext(), textClassification.getSecondaryIntent(i))); } } @@ -4048,10 +4050,9 @@ public class Editor { } } - private boolean isValidAssistMenuItem( - Drawable icon, CharSequence label, OnClickListener onClick, Intent intent) { + private boolean isValidAssistMenuItem(Drawable icon, CharSequence label, Intent intent) { final boolean hasUi = icon != null || !TextUtils.isEmpty(label); - final boolean hasAction = onClick != null || isSupportedIntent(intent); + final boolean hasAction = isSupportedIntent(intent); return hasUi && hasAction; } @@ -4626,7 +4627,7 @@ public class Editor { return 0; } - protected final void showMagnifier() { + protected final void showMagnifier(@NonNull final MotionEvent event) { if (mMagnifier == null) { return; } @@ -4652,9 +4653,10 @@ public class Editor { final Layout layout = mTextView.getLayout(); final int lineNumber = layout.getLineForOffset(offset); - // Horizontally snap to character offset. - final float xPosInView = getHorizontal(mTextView.getLayout(), offset) - + mTextView.getTotalPaddingLeft() - mTextView.getScrollX(); + // Horizontally move the magnifier smoothly. + final int[] textViewLocationOnScreen = new int[2]; + mTextView.getLocationOnScreen(textViewLocationOnScreen); + final float xPosInView = event.getRawX() - textViewLocationOnScreen[0]; // Vertically snap to middle of current line. final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber) + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f @@ -4849,11 +4851,11 @@ public class Editor { case MotionEvent.ACTION_DOWN: mDownPositionX = ev.getRawX(); mDownPositionY = ev.getRawY(); - showMagnifier(); + showMagnifier(ev); break; case MotionEvent.ACTION_MOVE: - showMagnifier(); + showMagnifier(ev); break; case MotionEvent.ACTION_UP: @@ -5207,11 +5209,11 @@ public class Editor { // re-engages the handle. mTouchWordDelta = 0.0f; mPrevX = UNSET_X_VALUE; - showMagnifier(); + showMagnifier(event); break; case MotionEvent.ACTION_MOVE: - showMagnifier(); + showMagnifier(event); break; case MotionEvent.ACTION_UP: diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 26dfcc2d668a..310b1708cb13 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -32,6 +32,7 @@ import android.view.PixelCopy; import android.view.Surface; import android.view.SurfaceView; import android.view.View; +import android.view.ViewParent; import com.android.internal.util.Preconditions; @@ -44,6 +45,8 @@ public final class Magnifier { private static final int NONEXISTENT_PREVIOUS_CONFIG_VALUE = -1; // The view to which this magnifier is attached. private final View mView; + // The coordinates of the view in the surface. + private final int[] mViewCoordinatesInSurface; // The window containing the magnifier. private final PopupWindow mWindow; // The center coordinates of the window containing the magnifier. @@ -87,6 +90,8 @@ public final class Magnifier { com.android.internal.R.dimen.magnifier_height); mZoomScale = context.getResources().getFloat( com.android.internal.R.dimen.magnifier_zoom_scale); + // The view's surface coordinates will not be updated until the magnifier is first shown. + mViewCoordinatesInSurface = new int[2]; mWindow = new PopupWindow(context); mWindow.setContentView(content); @@ -120,9 +125,34 @@ public final class Magnifier { configureCoordinates(xPosInView, yPosInView); // Clamp startX value to avoid distorting the rendering of the magnifier content. - final int startX = Math.max(0, Math.min( + // For this, we compute: + // - zeroScrollXInSurface: this is the start x of mView, where this is not masked by a + // potential scrolling container. For example, if mView is a + // TextView contained in a HorizontalScrollView, + // mViewCoordinatesInSurface will reflect the surface position of + // the first text character, rather than the position of the first + // visible one. Therefore, we need to add back the amount of + // scrolling from the parent containers. + // - actualWidth: similarly, the width of a View will be larger than its actually visible + // width when it is contained in a scrolling container. We need to use + // the minimum width of a scrolling container which contains this view. + int zeroScrollXInSurface = mViewCoordinatesInSurface[0]; + int actualWidth = mView.getWidth(); + ViewParent viewParent = mView.getParent(); + while (viewParent instanceof View) { + final View container = (View) viewParent; + if (container.canScrollHorizontally(-1 /* left scroll */) + || container.canScrollHorizontally(1 /* right scroll */)) { + zeroScrollXInSurface += container.getScrollX(); + actualWidth = Math.min(actualWidth, container.getWidth() + - container.getPaddingLeft() - container.getPaddingRight()); + } + viewParent = viewParent.getParent(); + } + + final int startX = Math.max(zeroScrollXInSurface, Math.min( mCenterZoomCoords.x - mBitmap.getWidth() / 2, - mView.getWidth() - mBitmap.getWidth())); + zeroScrollXInSurface + actualWidth - mBitmap.getWidth())); final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2; if (xPosInView != mPrevPosInView.x || yPosInView != mPrevPosInView.y) { @@ -169,10 +199,9 @@ public final class Magnifier { posX = xPosInView; posY = yPosInView; } else { - final int[] coordinatesInSurface = new int[2]; - mView.getLocationInSurface(coordinatesInSurface); - posX = xPosInView + coordinatesInSurface[0]; - posY = yPosInView + coordinatesInSurface[1]; + mView.getLocationInSurface(mViewCoordinatesInSurface); + posX = xPosInView + mViewCoordinatesInSurface[0]; + posY = yPosInView + mViewCoordinatesInSurface[1]; } mCenterZoomCoords.x = Math.round(posX); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 716b205380c2..8c4e422d457b 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -77,8 +77,8 @@ import android.text.GraphicsOperations; import android.text.InputFilter; import android.text.InputType; import android.text.Layout; +import android.text.MeasuredText; import android.text.ParcelableSpan; -import android.text.PremeasuredText; import android.text.Selection; import android.text.SpanWatcher; import android.text.Spannable; @@ -5393,7 +5393,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (imm != null) imm.restartInput(this); } else if (type == BufferType.SPANNABLE || mMovement != null) { text = mSpannableFactory.newSpannable(text); - } else if (!(text instanceof PremeasuredText || text instanceof CharWrapper)) { + } else if (!(text instanceof MeasuredText || text instanceof CharWrapper)) { text = TextUtils.stringOrSpannedString(text); } diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index c668e3de7e00..31d22e0f92fd 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -69,7 +69,7 @@ interface ILockSettings { void removeKey(String alias); void setSnapshotCreatedPendingIntent(in PendingIntent intent); Map getRecoverySnapshotVersions(); - void setServerParameters(long serverParameters); + void setServerParams(in byte[] serverParams); void setRecoveryStatus(in String packageName, in String[] aliases, int status); Map getRecoveryStatus(in String packageName); void setRecoverySecretTypes(in int[] secretTypes); diff --git a/core/jni/Android.bp b/core/jni/Android.bp index b3f66e9652f6..96f3308ec178 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -84,7 +84,7 @@ cc_library_shared { "android_view_VelocityTracker.cpp", "android_text_AndroidCharacter.cpp", "android_text_Hyphenator.cpp", - "android_text_MeasuredText.cpp", + "android_text_MeasuredParagraph.cpp", "android_text_StaticLayout.cpp", "android_os_Debug.cpp", "android_os_GraphicsEnvironment.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 6d7fe056acdf..6569b4783e67 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -178,7 +178,7 @@ extern int register_android_net_LocalSocketImpl(JNIEnv* env); extern int register_android_net_NetworkUtils(JNIEnv* env); extern int register_android_text_AndroidCharacter(JNIEnv *env); extern int register_android_text_Hyphenator(JNIEnv *env); -extern int register_android_text_MeasuredText(JNIEnv* env); +extern int register_android_text_MeasuredParagraph(JNIEnv* env); extern int register_android_text_StaticLayout(JNIEnv *env); extern int register_android_opengl_classes(JNIEnv *env); extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env); @@ -1342,7 +1342,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_content_XmlBlock), REG_JNI(register_android_text_AndroidCharacter), REG_JNI(register_android_text_Hyphenator), - REG_JNI(register_android_text_MeasuredText), + REG_JNI(register_android_text_MeasuredParagraph), REG_JNI(register_android_text_StaticLayout), REG_JNI(register_android_view_InputDevice), REG_JNI(register_android_view_KeyCharacterMap), diff --git a/core/jni/android_text_MeasuredText.cpp b/core/jni/android_text_MeasuredParagraph.cpp index af9d13122201..bdae0b22987f 100644 --- a/core/jni/android_text_MeasuredText.cpp +++ b/core/jni/android_text_MeasuredParagraph.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "MeasuredText" +#define LOG_TAG "MeasuredParagraph" #include "ScopedIcuLocale.h" #include "unicode/locid.h" @@ -49,7 +49,7 @@ static inline Paint* toPaint(jlong ptr) { return reinterpret_cast<Paint*>(ptr); } -static inline minikin::MeasuredText* toMeasuredText(jlong ptr) { +static inline minikin::MeasuredText* toMeasuredParagraph(jlong ptr) { return reinterpret_cast<minikin::MeasuredText*>(ptr); } @@ -57,8 +57,8 @@ template<typename Ptr> static inline jlong toJLong(Ptr ptr) { return reinterpret_cast<jlong>(ptr); } -static void releaseMeasuredText(jlong measuredTextPtr) { - delete toMeasuredText(measuredTextPtr); +static void releaseMeasuredParagraph(jlong measuredTextPtr) { + delete toMeasuredParagraph(measuredTextPtr); } // Regular JNI @@ -84,7 +84,7 @@ static void nAddReplacementRun(JNIEnv* /* unused */, jclass /* unused */, jlong } // Regular JNI -static jlong nBuildNativeMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, +static jlong nBuildNativeMeasuredParagraph(JNIEnv* env, jclass /* unused */, jlong builderPtr, jcharArray javaText) { ScopedCharArrayRO text(env, javaText); const minikin::U16StringPiece textBuffer(text.get(), text.size()); @@ -100,23 +100,23 @@ static void nFreeBuilder(JNIEnv* env, jclass /* unused */, jlong builderPtr) { // CriticalNative static jlong nGetReleaseFunc() { - return toJLong(&releaseMeasuredText); + return toJLong(&releaseMeasuredParagraph); } static const JNINativeMethod gMethods[] = { - // MeasuredTextBuilder native functions. + // MeasuredParagraphBuilder native functions. {"nInitBuilder", "()J", (void*) nInitBuilder}, {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun}, {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun}, - {"nBuildNativeMeasuredText", "(J[C)J", (void*) nBuildNativeMeasuredText}, + {"nBuildNativeMeasuredParagraph", "(J[C)J", (void*) nBuildNativeMeasuredParagraph}, {"nFreeBuilder", "(J)V", (void*) nFreeBuilder}, - // MeasuredText native functions. + // MeasuredParagraph native functions. {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives }; -int register_android_text_MeasuredText(JNIEnv* env) { - return RegisterMethodsOrDie(env, "android/text/MeasuredText", gMethods, NELEM(gMethods)); +int register_android_text_MeasuredParagraph(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/text/MeasuredParagraph", gMethods, NELEM(gMethods)); } } diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp index b5c23dfa873d..682dc8739869 100644 --- a/core/jni/android_text_StaticLayout.cpp +++ b/core/jni/android_text_StaticLayout.cpp @@ -174,7 +174,7 @@ static const JNINativeMethod gMethods[] = { // Inputs "[C" // text - "J" // MeasuredText ptr. + "J" // MeasuredParagraph ptr. "I" // length "F" // firstWidth "I" // firstWidthLineCount diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index b5c5fa61227e..a3e8f1ebd3ff 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -575,6 +575,7 @@ <!-- Added in P --> <protected-broadcast android:name="android.app.action.PROFILE_OWNER_CHANGED" /> <protected-broadcast android:name="android.app.action.TRANSFER_OWNERSHIP_COMPLETE" /> + <protected-broadcast android:name="android.app.action.DATA_SHARING_RESTRICTION_CHANGED" /> <!-- ====================================================================== --> <!-- RUNTIME PERMISSIONS --> @@ -2952,13 +2953,14 @@ <!-- Allows an application to collect usage infomation about brightness slider changes. <p>Not for use by third-party applications.</p> - TODO: make a System API - @hide --> + @hide + @SystemApi --> <permission android:name="android.permission.BRIGHTNESS_SLIDER_USAGE" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|development" /> <!-- Allows an application to modify the display brightness configuration - @hide --> + @hide + @SystemApi --> <permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS" android:protectionLevel="signature|privileged|development" /> diff --git a/core/res/res/raw/color_fade_frag.frag b/core/res/res/raw/color_fade_frag.frag index a66a5a761a1f..29975d5f7b5e 100644 --- a/core/res/res/raw/color_fade_frag.frag +++ b/core/res/res/raw/color_fade_frag.frag @@ -3,40 +3,12 @@ precision mediump float; uniform samplerExternalOES texUnit; uniform float opacity; -uniform float saturation; uniform float gamma; varying vec2 UV; -vec3 rgb2hsl(vec3 rgb) -{ - float e = 1.0e-7; - - vec4 p = rgb.g < rgb.b ? vec4(rgb.bg, -1.0, 2.0 / 3.0) : vec4(rgb.gb, 0.0, -1.0 / 3.0); - vec4 q = rgb.r < p.x ? vec4(p.xyw, rgb.r) : vec4(rgb.r, p.yzx); - - float v = q.x; - float c = v - min(q.w, q.y); - float h = abs((q.w - q.y) / (6.0 * c + e) + q.z); - float l = v - c * 0.5; - float s = c / (1.0 - abs(2.0 * l - 1.0) + e); - return clamp(vec3(h, s, l), 0.0, 1.0); -} - -vec3 hsl2rgb(vec3 hsl) -{ - vec3 h = vec3(hsl.x * 6.0); - vec3 p = abs(h - vec3(3.0, 2.0, 4.0)); - vec3 q = 2.0 - p; - - vec3 rgb = clamp(vec3(p.x - 1.0, q.yz), 0.0, 1.0); - float c = (1.0 - abs(2.0 * hsl.z - 1.0)) * hsl.y; - return (rgb - vec3(0.5)) * c + hsl.z; -} - void main() { vec4 color = texture2D(texUnit, UV); - vec3 hsl = rgb2hsl(color.xyz); - vec3 rgb = pow(hsl2rgb(vec3(hsl.x, hsl.y * saturation, hsl.z * opacity)), vec3(gamma)); + vec3 rgb = pow(color.rgb * opacity, vec3(gamma)); gl_FragColor = vec4(rgb, 1.0); } diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index efbe9c2566fe..287f29615e2b 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2568,9 +2568,9 @@ e.g. name=ro.oem.sku value=MKT210. Overlay will be ignored unless system property exists and is set to specified value --> - <!-- @hide @SystemApi This shouldn't be public. --> + <!-- @hide This shouldn't be public. --> <attr name="requiredSystemPropertyName" format="string" /> - <!-- @hide @SystemApi This shouldn't be public. --> + <!-- @hide This shouldn't be public. --> <attr name="requiredSystemPropertyValue" format="string" /> </declare-styleable> diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml index 04131009b141..f8a77f8f1c32 100644 --- a/core/res/res/values/colors_material.xml +++ b/core/res/res/values/colors_material.xml @@ -77,9 +77,9 @@ <item name="secondary_content_alpha_material_dark" format="float" type="dimen">.7</item> <item name="secondary_content_alpha_material_light" format="float" type="dimen">0.54</item> - <item name="highlight_alpha_material_light" format="float" type="dimen">0.12</item> - <item name="highlight_alpha_material_dark" format="float" type="dimen">0.20</item> - <item name="highlight_alpha_material_colored" format="float" type="dimen">0.26</item> + <item name="highlight_alpha_material_light" format="float" type="dimen">0.16</item> + <item name="highlight_alpha_material_dark" format="float" type="dimen">0.32</item> + <item name="highlight_alpha_material_colored" format="float" type="dimen">0.48</item> <!-- Primary & accent colors --> <eat-comment /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ed94f8471130..e3a910f5cc76 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2784,6 +2784,11 @@ the display's native orientation. --> <string translatable="false" name="config_mainBuiltInDisplayCutout"></string> + <!-- Whether the display cutout region of the main built-in display should be forced to + black in software (to avoid aliasing or emulate a cutout that is not physically existent). + --> + <bool name="config_fillMainBuiltInDisplayCutout">false</bool> + <!-- Ultrasound support for Mic/speaker path --> <!-- Whether the default microphone audio source supports near-ultrasound frequencies (range of 18 - 21 kHz). --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 1251c11c4daa..f4ced5821e53 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3203,6 +3203,7 @@ <java-symbol type="string" name="global_action_logout" /> <java-symbol type="string" name="config_mainBuiltInDisplayCutout" /> + <java-symbol type="bool" name="config_fillMainBuiltInDisplayCutout" /> <java-symbol type="drawable" name="ic_logout" /> <java-symbol type="array" name="config_autoBrightnessDisplayValuesNits" /> diff --git a/core/tests/coretests/src/android/text/MeasuredTextTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java index ddef0c651b31..5d33397e13f2 100644 --- a/core/tests/coretests/src/android/text/MeasuredTextTest.java +++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java @@ -31,7 +31,7 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidJUnit4.class) -public class MeasuredTextTest { +public class MeasuredParagraphTest { private static final TextDirectionHeuristic LTR = TextDirectionHeuristics.LTR; private static final TextDirectionHeuristic RTL = TextDirectionHeuristics.RTL; @@ -60,9 +60,9 @@ public class MeasuredTextTest { @Test public void buildForBidi() { - MeasuredText mt = null; + MeasuredParagraph mt = null; - mt = MeasuredText.buildForBidi("XXX", 0, 3, LTR, null); + mt = MeasuredParagraph.buildForBidi("XXX", 0, 3, LTR, null); assertNotNull(mt); assertNotNull(mt.getChars()); assertEquals("XXX", charsToString(mt.getChars())); @@ -75,7 +75,7 @@ public class MeasuredTextTest { assertEquals(0, mt.getNativePtr()); // Recycle it - MeasuredText mt2 = MeasuredText.buildForBidi("_VVV_", 1, 4, RTL, mt); + MeasuredParagraph mt2 = MeasuredParagraph.buildForBidi("_VVV_", 1, 4, RTL, mt); assertEquals(mt2, mt); assertNotNull(mt2.getChars()); assertEquals("VVV", charsToString(mt.getChars())); @@ -91,9 +91,9 @@ public class MeasuredTextTest { @Test public void buildForMeasurement() { - MeasuredText mt = null; + MeasuredParagraph mt = null; - mt = MeasuredText.buildForMeasurement(PAINT, "XXX", 0, 3, LTR, null); + mt = MeasuredParagraph.buildForMeasurement(PAINT, "XXX", 0, 3, LTR, null); assertNotNull(mt); assertNotNull(mt.getChars()); assertEquals("XXX", charsToString(mt.getChars())); @@ -109,7 +109,8 @@ public class MeasuredTextTest { assertEquals(0, mt.getNativePtr()); // Recycle it - MeasuredText mt2 = MeasuredText.buildForMeasurement(PAINT, "_VVV_", 1, 4, RTL, mt); + MeasuredParagraph mt2 = + MeasuredParagraph.buildForMeasurement(PAINT, "_VVV_", 1, 4, RTL, mt); assertEquals(mt2, mt); assertNotNull(mt2.getChars()); assertEquals("VVV", charsToString(mt.getChars())); @@ -129,9 +130,9 @@ public class MeasuredTextTest { @Test public void buildForStaticLayout() { - MeasuredText mt = null; + MeasuredParagraph mt = null; - mt = MeasuredText.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null); + mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null); assertNotNull(mt); assertNotNull(mt.getChars()); assertEquals("XXX", charsToString(mt.getChars())); @@ -145,7 +146,8 @@ public class MeasuredTextTest { assertNotEquals(0, mt.getNativePtr()); // Recycle it - MeasuredText mt2 = MeasuredText.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt); + MeasuredParagraph mt2 = + MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt); assertEquals(mt2, mt); assertNotNull(mt2.getChars()); assertEquals("VVV", charsToString(mt.getChars())); @@ -163,6 +165,6 @@ public class MeasuredTextTest { @Test public void testFor70146381() { - MeasuredText.buildForMeasurement(PAINT, "X…", 0, 2, RTL, null); + MeasuredParagraph.buildForMeasurement(PAINT, "X…", 0, 2, RTL, null); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java new file mode 100644 index 000000000000..9ee7facce47f --- /dev/null +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import static org.junit.Assert.assertEquals; + +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.os.LocaleList; +import android.os.Parcel; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.View; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Locale; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TextClassificationTest { + + public BitmapDrawable generateTestDrawable(int width, int height, int colorValue) { + final int numPixels = width * height; + final int[] colors = new int[numPixels]; + for (int i = 0; i < numPixels; ++i) { + colors[i] = colorValue; + } + final Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888); + final BitmapDrawable drawable = new BitmapDrawable(null, bitmap); + drawable.setTargetDensity(bitmap.getDensity()); + return drawable; + } + + @Test + public void testParcel() { + final String text = "text"; + final BitmapDrawable primaryIcon = generateTestDrawable(16, 16, Color.RED); + final String primaryLabel = "primarylabel"; + final Intent primaryIntent = new Intent("primaryintentaction"); + final View.OnClickListener primaryOnClick = v -> { }; + final BitmapDrawable secondaryIcon0 = generateTestDrawable(32, 288, Color.GREEN); + final String secondaryLabel0 = "secondarylabel0"; + final Intent secondaryIntent0 = new Intent("secondaryintentaction0"); + final BitmapDrawable secondaryIcon1 = generateTestDrawable(576, 288, Color.BLUE); + final String secondaryLabel1 = "secondaryLabel1"; + final Intent secondaryIntent1 = null; + final BitmapDrawable secondaryIcon2 = null; + final String secondaryLabel2 = null; + final Intent secondaryIntent2 = new Intent("secondaryintentaction2"); + final ColorDrawable secondaryIcon3 = new ColorDrawable(Color.CYAN); + final String secondaryLabel3 = null; + final Intent secondaryIntent3 = null; + final String signature = "signature"; + final TextClassification reference = new TextClassification.Builder() + .setText(text) + .setPrimaryAction(primaryIntent, primaryLabel, primaryIcon) + .setOnClickListener(primaryOnClick) + .addSecondaryAction(null, null, null) // ignored + .addSecondaryAction(secondaryIntent0, secondaryLabel0, secondaryIcon0) + .addSecondaryAction(secondaryIntent1, secondaryLabel1, secondaryIcon1) + .addSecondaryAction(secondaryIntent2, secondaryLabel2, secondaryIcon2) + .addSecondaryAction(secondaryIntent3, secondaryLabel3, secondaryIcon3) + .setEntityType(TextClassifier.TYPE_ADDRESS, 0.3f) + .setEntityType(TextClassifier.TYPE_PHONE, 0.7f) + .setSignature(signature) + .build(); + + // Parcel and unparcel using ParcelableWrapper. + final TextClassification.ParcelableWrapper parcelableReference = new TextClassification + .ParcelableWrapper(reference); + final Parcel parcel = Parcel.obtain(); + parcelableReference.writeToParcel(parcel, parcelableReference.describeContents()); + parcel.setDataPosition(0); + final TextClassification result = + TextClassification.ParcelableWrapper.CREATOR.createFromParcel( + parcel).getTextClassification(); + + assertEquals(text, result.getText()); + assertEquals(signature, result.getSignature()); + assertEquals(4, result.getSecondaryActionsCount()); + + // Primary action (re-use existing icon). + final Bitmap resPrimaryIcon = ((BitmapDrawable) result.getIcon()).getBitmap(); + assertEquals(primaryIcon.getBitmap().getPixel(0, 0), resPrimaryIcon.getPixel(0, 0)); + assertEquals(16, resPrimaryIcon.getWidth()); + assertEquals(16, resPrimaryIcon.getHeight()); + assertEquals(primaryLabel, result.getLabel()); + assertEquals(primaryIntent.getAction(), result.getIntent().getAction()); + assertEquals(null, result.getOnClickListener()); // Non-parcelable. + + // Secondary action 0 (scale with height limit). + final Bitmap resSecondaryIcon0 = ((BitmapDrawable) result.getSecondaryIcon(0)).getBitmap(); + assertEquals(secondaryIcon0.getBitmap().getPixel(0, 0), resSecondaryIcon0.getPixel(0, 0)); + assertEquals(16, resSecondaryIcon0.getWidth()); + assertEquals(144, resSecondaryIcon0.getHeight()); + assertEquals(secondaryLabel0, result.getSecondaryLabel(0)); + assertEquals(secondaryIntent0.getAction(), result.getSecondaryIntent(0).getAction()); + + // Secondary action 1 (scale with width limit). + final Bitmap resSecondaryIcon1 = ((BitmapDrawable) result.getSecondaryIcon(1)).getBitmap(); + assertEquals(secondaryIcon1.getBitmap().getPixel(0, 0), resSecondaryIcon1.getPixel(0, 0)); + assertEquals(144, resSecondaryIcon1.getWidth()); + assertEquals(72, resSecondaryIcon1.getHeight()); + assertEquals(secondaryLabel1, result.getSecondaryLabel(1)); + assertEquals(null, result.getSecondaryIntent(1)); + + // Secondary action 2 (no icon). + assertEquals(null, result.getSecondaryIcon(2)); + assertEquals(null, result.getSecondaryLabel(2)); + assertEquals(secondaryIntent2.getAction(), result.getSecondaryIntent(2).getAction()); + + // Secondary action 3 (convert non-bitmap drawable with negative size). + final Bitmap resSecondaryIcon3 = ((BitmapDrawable) result.getSecondaryIcon(3)).getBitmap(); + assertEquals(secondaryIcon3.getColor(), resSecondaryIcon3.getPixel(0, 0)); + assertEquals(1, resSecondaryIcon3.getWidth()); + assertEquals(1, resSecondaryIcon3.getHeight()); + assertEquals(null, result.getSecondaryLabel(3)); + assertEquals(null, result.getSecondaryIntent(3)); + + // Entities. + assertEquals(2, result.getEntityCount()); + assertEquals(TextClassifier.TYPE_PHONE, result.getEntity(0)); + assertEquals(TextClassifier.TYPE_ADDRESS, result.getEntity(1)); + assertEquals(0.7f, result.getConfidenceScore(TextClassifier.TYPE_PHONE), 1e-7f); + assertEquals(0.3f, result.getConfidenceScore(TextClassifier.TYPE_ADDRESS), 1e-7f); + } + + @Test + public void testParcelOptions() { + TextClassification.Options reference = new TextClassification.Options(); + reference.setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY)); + + // Parcel and unparcel. + final Parcel parcel = Parcel.obtain(); + reference.writeToParcel(parcel, reference.describeContents()); + parcel.setDataPosition(0); + TextClassification.Options result = TextClassification.Options.CREATOR.createFromParcel( + parcel); + + assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags()); + } +} diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java new file mode 100644 index 000000000000..a82542cd91c5 --- /dev/null +++ b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import static org.junit.Assert.assertEquals; + +import android.os.LocaleList; +import android.os.Parcel; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.ArrayMap; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TextLinksTest { + + private TextClassificationManager mTcm; + private TextClassifier mClassifier; + + @Before + public void setup() { + mTcm = InstrumentationRegistry.getTargetContext() + .getSystemService(TextClassificationManager.class); + mTcm.setTextClassifier(null); + mClassifier = mTcm.getTextClassifier(); + } + + private Map<String, Float> getEntityScores(float address, float phone, float other) { + final Map<String, Float> result = new ArrayMap<>(); + if (address > 0.f) { + result.put(TextClassifier.TYPE_ADDRESS, address); + } + if (phone > 0.f) { + result.put(TextClassifier.TYPE_PHONE, phone); + } + if (other > 0.f) { + result.put(TextClassifier.TYPE_OTHER, other); + } + return result; + } + + @Test + public void testParcel() { + final String fullText = "this is just a test"; + final TextLinks reference = new TextLinks.Builder(fullText) + .addLink(new TextLinks.TextLink(fullText, 0, 4, getEntityScores(0.f, 0.f, 1.f))) + .addLink(new TextLinks.TextLink(fullText, 5, 12, getEntityScores(.8f, .1f, .5f))) + .build(); + + // Parcel and unparcel. + final Parcel parcel = Parcel.obtain(); + reference.writeToParcel(parcel, reference.describeContents()); + parcel.setDataPosition(0); + final TextLinks result = TextLinks.CREATOR.createFromParcel(parcel); + final List<TextLinks.TextLink> resultList = new ArrayList<>(result.getLinks()); + + assertEquals(2, resultList.size()); + assertEquals(0, resultList.get(0).getStart()); + assertEquals(4, resultList.get(0).getEnd()); + assertEquals(1, resultList.get(0).getEntityCount()); + assertEquals(TextClassifier.TYPE_OTHER, resultList.get(0).getEntity(0)); + assertEquals(1.f, resultList.get(0).getConfidenceScore(TextClassifier.TYPE_OTHER), 1e-7f); + assertEquals(5, resultList.get(1).getStart()); + assertEquals(12, resultList.get(1).getEnd()); + assertEquals(3, resultList.get(1).getEntityCount()); + assertEquals(TextClassifier.TYPE_ADDRESS, resultList.get(1).getEntity(0)); + assertEquals(TextClassifier.TYPE_OTHER, resultList.get(1).getEntity(1)); + assertEquals(TextClassifier.TYPE_PHONE, resultList.get(1).getEntity(2)); + assertEquals(.8f, resultList.get(1).getConfidenceScore(TextClassifier.TYPE_ADDRESS), 1e-7f); + assertEquals(.5f, resultList.get(1).getConfidenceScore(TextClassifier.TYPE_OTHER), 1e-7f); + assertEquals(.1f, resultList.get(1).getConfidenceScore(TextClassifier.TYPE_PHONE), 1e-7f); + } + + @Test + public void testParcelOptions() { + TextClassifier.EntityConfig entityConfig = new TextClassifier.EntityConfig( + TextClassifier.ENTITY_PRESET_NONE); + entityConfig.includeEntities("a", "b", "c"); + entityConfig.excludeEntities("b"); + TextLinks.Options reference = new TextLinks.Options(); + reference.setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY)); + reference.setEntityConfig(entityConfig); + + // Parcel and unparcel. + final Parcel parcel = Parcel.obtain(); + reference.writeToParcel(parcel, reference.describeContents()); + parcel.setDataPosition(0); + TextLinks.Options result = TextLinks.Options.CREATOR.createFromParcel(parcel); + + assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags()); + assertEquals(Arrays.asList("a", "c"), result.getEntityConfig().getEntities(mClassifier)); + } +} diff --git a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java new file mode 100644 index 000000000000..e9202361c84a --- /dev/null +++ b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.os.LocaleList; +import android.os.Parcel; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Locale; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TextSelectionTest { + + @Test + public void testParcel() { + final int startIndex = 13; + final int endIndex = 37; + final String signature = "signature"; + final TextSelection reference = new TextSelection.Builder(startIndex, endIndex) + .setEntityType(TextClassifier.TYPE_ADDRESS, 0.3f) + .setEntityType(TextClassifier.TYPE_PHONE, 0.7f) + .setEntityType(TextClassifier.TYPE_URL, 0.1f) + .setSignature(signature) + .build(); + + // Parcel and unparcel using ParcelableWrapper. + final TextSelection.ParcelableWrapper parcelableReference = new TextSelection + .ParcelableWrapper(reference); + final Parcel parcel = Parcel.obtain(); + parcelableReference.writeToParcel(parcel, parcelableReference.describeContents()); + parcel.setDataPosition(0); + final TextSelection result = + TextSelection.ParcelableWrapper.CREATOR.createFromParcel( + parcel).getTextSelection(); + + assertEquals(startIndex, result.getSelectionStartIndex()); + assertEquals(endIndex, result.getSelectionEndIndex()); + assertEquals(signature, result.getSignature()); + + assertEquals(3, result.getEntityCount()); + assertEquals(TextClassifier.TYPE_PHONE, result.getEntity(0)); + assertEquals(TextClassifier.TYPE_ADDRESS, result.getEntity(1)); + assertEquals(TextClassifier.TYPE_URL, result.getEntity(2)); + assertEquals(0.7f, result.getConfidenceScore(TextClassifier.TYPE_PHONE), 1e-7f); + assertEquals(0.3f, result.getConfidenceScore(TextClassifier.TYPE_ADDRESS), 1e-7f); + assertEquals(0.1f, result.getConfidenceScore(TextClassifier.TYPE_URL), 1e-7f); + } + + @Test + public void testParcelOptions() { + TextSelection.Options reference = new TextSelection.Options(); + reference.setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY)); + reference.setDarkLaunchAllowed(true); + + // Parcel and unparcel. + final Parcel parcel = Parcel.obtain(); + reference.writeToParcel(parcel, reference.describeContents()); + parcel.setDataPosition(0); + TextSelection.Options result = TextSelection.Options.CREATOR.createFromParcel(parcel); + + assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags()); + assertTrue(result.isDarkLaunchAllowed()); + } +} diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java index dea194e4ffde..4571553d4d4e 100644 --- a/graphics/java/android/graphics/drawable/RippleBackground.java +++ b/graphics/java/android/graphics/drawable/RippleBackground.java @@ -16,17 +16,12 @@ package android.graphics.drawable; -import android.animation.Animator; -import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.graphics.Canvas; -import android.graphics.CanvasProperty; import android.graphics.Paint; import android.graphics.Rect; import android.util.FloatProperty; -import android.view.DisplayListCanvas; -import android.view.RenderNodeAnimator; import android.view.animation.LinearInterpolator; /** @@ -78,8 +73,8 @@ class RippleBackground extends RippleComponent { private void onStateChanged(boolean animateChanged) { float newOpacity = 0.0f; - if (mHovered) newOpacity += 1.0f; - if (mFocused) newOpacity += 1.0f; + if (mHovered) newOpacity += .25f; + if (mFocused) newOpacity += .75f; if (mAnimator != null) { mAnimator.cancel(); mAnimator = null; diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 734cff542c51..b883656d784a 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -264,8 +264,8 @@ public class RippleDrawable extends LayerDrawable { } setRippleActive(enabled && pressed); - setBackgroundActive(hovered, focused); + return changed; } @@ -879,22 +879,18 @@ public class RippleDrawable extends LayerDrawable { // Grab the color for the current state and cut the alpha channel in // half so that the ripple and background together yield full alpha. final int color = mState.mColor.getColorForState(getState(), Color.BLACK); - final int halfAlpha = (Color.alpha(color) / 2) << 24; final Paint p = mRipplePaint; if (mMaskColorFilter != null) { // The ripple timing depends on the paint's alpha value, so we need // to push just the alpha channel into the paint and let the filter // handle the full-alpha color. - final int fullAlphaColor = color | (0xFF << 24); - mMaskColorFilter.setColor(fullAlphaColor); - - p.setColor(halfAlpha); + mMaskColorFilter.setColor(color | 0xFF000000); + p.setColor(color & 0xFF000000); p.setColorFilter(mMaskColorFilter); p.setShader(mMaskShader); } else { - final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha; - p.setColor(halfAlphaColor); + p.setColor(color); p.setColorFilter(null); p.setShader(null); } diff --git a/native/android/net.c b/native/android/net.c index de4b90cc1956..60296a7bd00c 100644 --- a/native/android/net.c +++ b/native/android/net.c @@ -27,7 +27,7 @@ static int getnetidfromhandle(net_handle_t handle, unsigned *netid) { static const uint32_t k32BitMask = 0xffffffff; // This value MUST be kept in sync with the corresponding value in // the android.net.Network#getNetworkHandle() implementation. - static const uint32_t kHandleMagic = 0xfacade; + static const uint32_t kHandleMagic = 0xcafed00d; // Check for minimum acceptable version of the API in the low bits. if (handle != NETWORK_UNSPECIFIED && diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml index c97cfc4bb835..347cf1cb0921 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml @@ -34,7 +34,6 @@ android:orientation="vertical"> <RelativeLayout android:id="@+id/keyguard_clock_container" - android:animateLayoutChanges="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|top"> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 6ff239ebd2ee..dde4dcfb23fe 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -61,11 +61,26 @@ <!-- When the battery is low, this is displayed to the user in a dialog. The title of the low battery alert. [CHAR LIMIT=NONE]--> <string name="battery_low_title">Battery is low</string> + <!-- When the battery is low and hybrid notifications are enabled, this is displayed to the user in a dialog. + The title of the low battery alert. [CHAR LIMIT=NONE]--> + <string name="battery_low_title_hybrid">Battery is low. Turn on Battery Saver</string> + <!-- A message that appears when the battery level is getting low in a dialog. This is - appened to the subtitle of the low battery alert. "percentage" is the percentage of battery + appended to the subtitle of the low battery alert. "percentage" is the percentage of battery remaining [CHAR LIMIT=none]--> <string name="battery_low_percent_format"><xliff:g id="percentage">%s</xliff:g> remaining</string> + <!-- A message that appears when the battery remaining estimate is low in a dialog. This is + appended to the subtitle of the low battery alert. "percentage" is the percentage of battery + remaining. "time" is the amount of time remaining before the phone runs out of battery [CHAR LIMIT=none]--> + <string name="battery_low_percent_format_hybrid"><xliff:g id="percentage">%s</xliff:g> remaining, about <xliff:g id="time">%s</xliff:g> left based on your usage</string> + + <!-- A message that appears when the battery remaining estimate is low in a dialog and insufficient + data was present to say it is customized to the user. This is appended to the subtitle of the + low battery alert. "percentage" is the percentage of battery remaining. "time" is the amount + of time remaining before the phone runs out of battery [CHAR LIMIT=none]--> + <string name="battery_low_percent_format_hybrid_short"><xliff:g id="percentage">%s</xliff:g> remaining, about <xliff:g id="time">%s</xliff:g> left</string> + <!-- Same as battery_low_percent_format, with a notice about battery saver if on. [CHAR LIMIT=none]--> <string name="battery_low_percent_format_saver_started"><xliff:g id="percentage">%s</xliff:g> remaining. Battery Saver is on.</string> diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index e7e70afa20ce..7403ddc441f6 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -40,6 +40,8 @@ import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.PluginManagerImpl; import com.android.systemui.plugins.VolumeDialogController; +import com.android.systemui.power.EnhancedEstimates; +import com.android.systemui.power.EnhancedEstimatesImpl; import com.android.systemui.power.PowerNotificationWarnings; import com.android.systemui.power.PowerUI; import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; @@ -310,6 +312,8 @@ public class Dependency extends SystemUI { mProviders.put(OverviewProxyService.class, () -> new OverviewProxyService(mContext)); + mProviders.put(EnhancedEstimates.class, () -> new EnhancedEstimatesImpl()); + // Put all dependencies above here so the factory can override them if it wants. SystemUIFactory.getInstance().injectDependencies(mProviders, mContext); } diff --git a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java index a1022604c27f..f41425a1f848 100644 --- a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java +++ b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java @@ -38,6 +38,9 @@ import android.view.ViewGroup.LayoutParams; import android.view.WindowInsets; import android.view.WindowManager; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; + import java.util.Collections; import java.util.List; @@ -45,18 +48,28 @@ import java.util.List; * Emulates a display cutout by drawing its shape in an overlay as supplied by * {@link DisplayCutout}. */ -public class EmulatedDisplayCutout extends SystemUI { +public class EmulatedDisplayCutout extends SystemUI implements ConfigurationListener { private View mOverlay; private boolean mAttached; private WindowManager mWindowManager; @Override public void start() { + Dependency.get(ConfigurationController.class).addCallback(this); + mWindowManager = mContext.getSystemService(WindowManager.class); - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.EMULATE_DISPLAY_CUTOUT), - false, mObserver, UserHandle.USER_ALL); - mObserver.onChange(false); + updateAttached(); + } + + @Override + public void onOverlayChanged() { + updateAttached(); + } + + private void updateAttached() { + boolean shouldAttach = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout); + setAttached(shouldAttach); } private void setAttached(boolean attached) { @@ -94,17 +107,6 @@ public class EmulatedDisplayCutout extends SystemUI { return lp; } - private ContentObserver mObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { - @Override - public void onChange(boolean selfChange) { - boolean emulateCutout = Settings.Global.getInt( - mContext.getContentResolver(), Settings.Global.EMULATE_DISPLAY_CUTOUT, - Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF) - != Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF; - setAttached(emulateCutout); - } - }; - private static class CutoutView extends View { private final Paint mPaint = new Paint(); private final Path mBounds = new Path(); diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java new file mode 100644 index 000000000000..8f41a6036072 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java @@ -0,0 +1,8 @@ +package com.android.systemui.power; + +public interface EnhancedEstimates { + + boolean isHybridNotificationEnabled(); + + Estimate getEstimate(); +} diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java new file mode 100644 index 000000000000..d447542588c5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java @@ -0,0 +1,16 @@ +package com.android.systemui.power; + +import android.util.Log; + +public class EnhancedEstimatesImpl implements EnhancedEstimates { + + @Override + public boolean isHybridNotificationEnabled() { + return false; + } + + @Override + public Estimate getEstimate() { + return null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/power/Estimate.java b/packages/SystemUI/src/com/android/systemui/power/Estimate.java new file mode 100644 index 000000000000..12a8f0a435b4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/power/Estimate.java @@ -0,0 +1,11 @@ +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/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index c29b362bda13..736286f21bfe 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -17,40 +17,40 @@ package com.android.systemui.power; import android.app.Notification; -import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnDismissListener; import android.content.Intent; import android.content.IntentFilter; +import android.icu.text.MeasureFormat; +import android.icu.text.MeasureFormat.FormatWidth; +import android.icu.util.Measure; +import android.icu.util.MeasureUnit; import android.media.AudioAttributes; -import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; -import android.os.SystemClock; import android.os.UserHandle; -import android.provider.Settings; import android.support.annotation.VisibleForTesting; +import android.text.format.DateUtils; import android.util.Slog; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.internal.notification.SystemNotificationChannels; import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.SystemUI; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.util.NotificationChannels; import java.io.PrintWriter; import java.text.NumberFormat; +import java.util.Locale; +import java.util.concurrent.TimeUnit; public class PowerNotificationWarnings implements PowerUI.WarningsUI { private static final String TAG = PowerUI.TAG + ".Notification"; @@ -96,8 +96,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { private long mScreenOffTime; private int mShowing; - private long mBucketDroppedNegativeTimeMs; + private long mWarningTriggerTimeMs; + private Estimate mEstimate; private boolean mWarning; private boolean mPlaySound; private boolean mInvalidCharger; @@ -130,14 +131,22 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { public void update(int batteryLevel, int bucket, long screenOffTime) { mBatteryLevel = batteryLevel; if (bucket >= 0) { - mBucketDroppedNegativeTimeMs = 0; + mWarningTriggerTimeMs = 0; } else if (bucket < mBucket) { - mBucketDroppedNegativeTimeMs = System.currentTimeMillis(); + mWarningTriggerTimeMs = System.currentTimeMillis(); } mBucket = bucket; mScreenOffTime = screenOffTime; } + @Override + public void updateEstimate(Estimate estimate) { + mEstimate = estimate; + if (estimate.estimateMillis <= PowerUI.THREE_HOURS_IN_MILLIS) { + mWarningTriggerTimeMs = System.currentTimeMillis(); + } + } + private void updateNotification() { if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning + " mPlaySound=" + mPlaySound + " mInvalidCharger=" + mInvalidCharger); @@ -171,25 +180,43 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_BAD_CHARGER, n, UserHandle.ALL); } - private void showWarningNotification() { - final int textRes = R.string.battery_low_percent_format; + protected void showWarningNotification() { final String percentage = NumberFormat.getPercentInstance().format((double) mBatteryLevel / 100.0); + // get standard notification copy + String title = mContext.getString(R.string.battery_low_title); + String contentText = mContext.getString(R.string.battery_low_percent_format, percentage); + + // override notification copy if hybrid notification enabled + if (mEstimate != null) { + title = mContext.getString(R.string.battery_low_title_hybrid); + contentText = mContext.getString( + mEstimate.isBasedOnUsage + ? R.string.battery_low_percent_format_hybrid + : R.string.battery_low_percent_format_hybrid_short, + percentage, + getTimeRemainingFormatted()); + } + final Notification.Builder nb = new Notification.Builder(mContext, NotificationChannels.BATTERY) .setSmallIcon(R.drawable.ic_power_low) // Bump the notification when the bucket dropped. - .setWhen(mBucketDroppedNegativeTimeMs) + .setWhen(mWarningTriggerTimeMs) .setShowWhen(false) - .setContentTitle(mContext.getString(R.string.battery_low_title)) - .setContentText(mContext.getString(textRes, percentage)) + .setContentTitle(title) + .setContentText(contentText) .setOnlyAlertOnce(true) .setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_WARNING)) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .setColor(Utils.getColorAttr(mContext, android.R.attr.colorError)); + .setVisibility(Notification.VISIBILITY_PUBLIC); if (hasBatterySettings()) { nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS)); } + // Make the notification red if the percentage goes below a certain amount or the time + // remaining estimate is disabled + if (mEstimate == null || mBucket < 0) { + nb.setColor(Utils.getColorAttr(mContext, android.R.attr.colorError)); + } nb.addAction(0, mContext.getString(R.string.battery_saver_start_action), pendingBroadcast(ACTION_START_SAVER)); @@ -201,6 +228,23 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, n, UserHandle.ALL); } + @VisibleForTesting + String getTimeRemainingFormatted() { + final Locale currentLocale = mContext.getResources().getConfiguration().getLocales().get(0); + MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.NARROW); + + final long remainder = mEstimate.estimateMillis % DateUtils.HOUR_IN_MILLIS; + final long hours = TimeUnit.MILLISECONDS.toHours( + mEstimate.estimateMillis - remainder); + // round down to the nearest 15 min for now to not appear overly precise + final long minutes = TimeUnit.MILLISECONDS.toMinutes( + remainder - (remainder % TimeUnit.MINUTES.toMillis(15))); + final Measure hoursMeasure = new Measure(hours, MeasureUnit.HOUR); + final Measure minutesMeasure = new Measure(minutes, MeasureUnit.MINUTE); + + return frmt.formatMeasures(hoursMeasure, minutesMeasure); + } + private PendingIntent pendingBroadcast(String action) { return PendingIntent.getBroadcastAsUser(mContext, 0, new Intent(action), 0, UserHandle.CURRENT); diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index a351c09f68b7..c5aab601283c 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -52,6 +52,7 @@ import com.android.systemui.statusbar.phone.StatusBar; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Arrays; +import java.util.concurrent.TimeUnit; public class PowerUI extends SystemUI { static final String TAG = "PowerUI"; @@ -59,6 +60,7 @@ public class PowerUI extends SystemUI { private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS; private static final long TEMPERATURE_LOGGING_INTERVAL = DateUtils.HOUR_IN_MILLIS; private static final int MAX_RECENT_TEMPS = 125; // TEMPERATURE_LOGGING_INTERVAL plus a buffer + static final long THREE_HOURS_IN_MILLIS = DateUtils.HOUR_IN_MILLIS * 3; private final Handler mHandler = new Handler(); private final Receiver mReceiver = new Receiver(); @@ -68,9 +70,11 @@ public class PowerUI extends SystemUI { private WarningsUI mWarnings; private final Configuration mLastConfiguration = new Configuration(); private int mBatteryLevel = 100; + private long mTimeRemaining = Long.MAX_VALUE; private int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN; private int mPlugType = 0; private int mInvalidCharger = 0; + private EnhancedEstimates mEnhancedEstimates; private int mLowBatteryAlertCloseLevel; private final int[] mLowBatteryReminderLevels = new int[2]; @@ -83,8 +87,8 @@ public class PowerUI extends SystemUI { private long mNextLogTime; private IThermalService mThermalService; - // We create a method reference here so that we are guaranteed that we can remove a callback // by using the same instance (method references are not guaranteed to be the same object + // We create a method reference here so that we are guaranteed that we can remove a callback // each time they are created). private final Runnable mUpdateTempCallback = this::updateTemperatureWarning; @@ -94,6 +98,7 @@ public class PowerUI extends SystemUI { mContext.getSystemService(Context.HARDWARE_PROPERTIES_SERVICE); mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime(); mWarnings = Dependency.get(WarningsUI.class); + mEnhancedEstimates = Dependency.get(EnhancedEstimates.class); mLastConfiguration.setTo(mContext.getResources().getConfiguration()); ContentObserver obs = new ContentObserver(mHandler) { @@ -236,21 +241,9 @@ public class PowerUI extends SystemUI { return; } - boolean isPowerSaver = mPowerManager.isPowerSaveMode(); - if (!plugged - && !isPowerSaver - && (bucket < oldBucket || oldPlugged) - && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN - && bucket < 0) { - - // only play SFX when the dialog comes up or the bucket changes - final boolean playSound = bucket != oldBucket || oldPlugged; - mWarnings.showLowBatteryWarning(playSound); - } else if (isPowerSaver || plugged || (bucket > oldBucket && bucket > 0)) { - mWarnings.dismissLowBatteryWarning(); - } else { - mWarnings.updateLowBatteryWarning(); - } + // Show the correct version of low battery warning if needed + maybeShowBatteryWarning(plugged, oldPlugged, oldBucket, bucket); + } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { mScreenOffTime = SystemClock.elapsedRealtime(); } else if (Intent.ACTION_SCREEN_ON.equals(action)) { @@ -261,7 +254,65 @@ public class PowerUI extends SystemUI { Slog.w(TAG, "unknown intent: " + intent); } } - }; + } + + protected void maybeShowBatteryWarning(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; + long oldTimeRemaining = mTimeRemaining; + if (mEnhancedEstimates.isHybridNotificationEnabled()) { + final Estimate estimate = mEnhancedEstimates.getEstimate(); + // Turbo is not always booted once SysUI is running so we have ot make sure we actually + // get data back + if (estimate != null) { + mTimeRemaining = estimate.estimateMillis; + mWarnings.updateEstimate(estimate); + } + } + + if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket, oldTimeRemaining, + mTimeRemaining, + isPowerSaver, mBatteryStatus)) { + mWarnings.showLowBatteryWarning(playSound); + } else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining, + isPowerSaver)) { + mWarnings.dismissLowBatteryWarning(); + } else { + mWarnings.updateLowBatteryWarning(); + } + } + + @VisibleForTesting + boolean shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket, + int bucket, long oldTimeRemaining, long timeRemaining, + boolean isPowerSaver, int mBatteryStatus) { + return !plugged + && !isPowerSaver + && (((bucket < oldBucket || oldPlugged) && bucket < 0) + || (mEnhancedEstimates.isHybridNotificationEnabled() + && timeRemaining < THREE_HOURS_IN_MILLIS + && isHourLess(oldTimeRemaining, timeRemaining))) + && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN; + } + + private boolean isHourLess(long oldTimeRemaining, long timeRemaining) { + final long dif = oldTimeRemaining - timeRemaining; + return dif >= TimeUnit.HOURS.toMillis(1); + } + + @VisibleForTesting + boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket, + long timeRemaining, boolean isPowerSaver) { + final boolean hybridWouldDismiss = mEnhancedEstimates.isHybridNotificationEnabled() + && timeRemaining > THREE_HOURS_IN_MILLIS; + final boolean standardWouldDismiss = (bucket > oldBucket && bucket > 0); + return isPowerSaver + || plugged + || (standardWouldDismiss && (!mEnhancedEstimates.isHybridNotificationEnabled() + || hybridWouldDismiss)); + } private void initTemperatureWarning() { ContentResolver resolver = mContext.getContentResolver(); @@ -433,6 +484,7 @@ public class PowerUI extends SystemUI { public interface WarningsUI { void update(int batteryLevel, int bucket, long screenOffTime); + void updateEstimate(Estimate estimate); void dismissLowBatteryWarning(); void showLowBatteryWarning(boolean playSound); void dismissInvalidChargerWarning(); 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 7f07e0c70e8a..3e37cfe75e0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java @@ -38,6 +38,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.SysuiTestCase; import com.android.systemui.util.NotificationChannels; +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,6 +47,9 @@ import org.mockito.ArgumentCaptor; @SmallTest @RunWith(AndroidJUnit4.class) public class PowerNotificationWarningsTest extends SysuiTestCase { + + public static final String FORMATTED_45M = "0h 45m"; + public static final String FORMATTED_HOUR = "1h 0m"; private final NotificationManager mMockNotificationManager = mock(NotificationManager.class); private PowerNotificationWarnings mPowerNotificationWarnings; @@ -147,4 +151,22 @@ public class PowerNotificationWarningsTest extends SysuiTestCase { verify(mMockNotificationManager, times(1)).cancelAsUser(anyString(), eq(SystemMessage.NOTE_THERMAL_SHUTDOWN), any()); } + + @Test + public void testGetTimeRemainingFormatted_roundsDownTo15() { + mPowerNotificationWarnings.updateEstimate( + new Estimate(TimeUnit.MINUTES.toMillis(57), true)); + String time = mPowerNotificationWarnings.getTimeRemainingFormatted(); + + assertTrue("time:" + time + ", expected: " + FORMATTED_45M, time.equals(FORMATTED_45M)); + } + + @Test + public void testGetTimeRemainingFormatted_keepsMinutesWhenZero() { + mPowerNotificationWarnings.updateEstimate( + new Estimate(TimeUnit.MINUTES.toMillis(65), true)); + String time = mPowerNotificationWarnings.getTimeRemainingFormatted(); + + assertTrue("time:" + time + ", expected: " + FORMATTED_HOUR, time.equals(FORMATTED_HOUR)); + } } 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 e4734a474b62..fdb7f8d005b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java @@ -19,12 +19,15 @@ import static android.os.HardwarePropertiesManager.TEMPERATURE_CURRENT; import static android.os.HardwarePropertiesManager.TEMPERATURE_SHUTDOWN; import static android.provider.Settings.Global.SHOW_TEMPERATURE_WARNING; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.os.BatteryManager; import android.os.HardwarePropertiesManager; import android.provider.Settings; import android.testing.AndroidTestingRunner; @@ -37,6 +40,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.power.PowerUI.WarningsUI; import com.android.systemui.statusbar.phone.StatusBar; +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,14 +50,23 @@ import org.junit.runner.RunWith; @SmallTest public class PowerUITest extends SysuiTestCase { + private static final boolean UNPLUGGED = false; + private static final boolean POWER_SAVER_OFF = false; + private static final int ABOVE_WARNING_BUCKET = 1; + public static final int BELOW_WARNING_BUCKET = -1; + public static final long BELOW_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(2); + public static final long ABOVE_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(4); private HardwarePropertiesManager mHardProps; private WarningsUI mMockWarnings; private PowerUI mPowerUI; + private EnhancedEstimates mEnhacedEstimates; @Before public void setup() { mMockWarnings = mDependency.injectMockDependency(WarningsUI.class); + mEnhacedEstimates = mDependency.injectMockDependency(EnhancedEstimates.class); mHardProps = mock(HardwarePropertiesManager.class); + mContext.putComponent(StatusBar.class, mock(StatusBar.class)); mContext.addMockSystemService(Context.HARDWARE_PROPERTIES_SERVICE, mHardProps); @@ -128,6 +141,180 @@ public class PowerUITest extends SysuiTestCase { verify(mMockWarnings).showHighTemperatureWarning(); } + @Test + public void testShouldShowLowBatteryWarning_showHybridOnly_returnsShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // 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, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertTrue(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_showHybrid_showStandard_returnsShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // unplugged device that would show the non-hybrid notification and the hybrid + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + BELOW_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertTrue(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_showStandardOnly_returnsShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + 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, Long.MAX_VALUE, ABOVE_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertTrue(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_deviceHighBattery_returnsNoShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + 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, ABOVE_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertFalse(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_devicePlugged_returnsNoShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + 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, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertFalse(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_deviceBatteryStatusUnkown_returnsNoShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // Unknown battery status device that would show the neither due + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD, + !POWER_SAVER_OFF, BatteryManager.BATTERY_STATUS_UNKNOWN); + assertFalse(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_batterySaverEnabled_returnsNoShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + 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, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD, + !POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertFalse(shouldShow); + } + + @Test + public void testShouldDismissLowBatteryWarning_dismissWhenPowerSaverEnabled() { + mPowerUI.start(); + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + // 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 testShouldDismissLowBatteryWarning_dismissWhenPlugged() { + mPowerUI.start(); + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + + // 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(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + // 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(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + + // 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(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + + // 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(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + + //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(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(false); + + // 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); + } + private void setCurrentTemp(float temp) { when(mHardProps.getDeviceTemperatures(DEVICE_TEMPERATURE_SKIN, TEMPERATURE_CURRENT)) .thenReturn(new float[] { temp }); diff --git a/packages/overlays/DisplayCutoutEmulationOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationOverlay/Android.mk new file mode 100644 index 000000000000..f4205ad68a1b --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationOverlay/Android.mk @@ -0,0 +1,13 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_RRO_THEME := DisplayCutoutEmulation +LOCAL_CERTIFICATE := platform + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := DisplayCutoutEmulationOverlay + +include $(BUILD_RRO_PACKAGE) diff --git a/packages/overlays/DisplayCutoutEmulationOverlay/AndroidManifest.xml b/packages/overlays/DisplayCutoutEmulationOverlay/AndroidManifest.xml new file mode 100644 index 000000000000..dd4369094900 --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationOverlay/AndroidManifest.xml @@ -0,0 +1,8 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.internal.display.cutout.emulation" + android:versionCode="1" + android:versionName="1.0"> + <overlay android:targetPackage="android" android:priority="1"/> + + <application android:label="@string/display_cutout_emulation_overlay" android:hasCode="false"/> +</manifest> diff --git a/packages/overlays/DisplayCutoutEmulationOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationOverlay/res/values/config.xml new file mode 100644 index 000000000000..88c19c7ccf05 --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationOverlay/res/values/config.xml @@ -0,0 +1,44 @@ +<!-- + ~ 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. + --> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- The bounding path of the cutout region of the main built-in display. + Must either be empty if there is no cutout region, or a string that is parsable by + {@link android.util.PathParser}. --> + <string translatable="false" name="config_mainBuiltInDisplayCutout"> + M 687.0,0 + l -66,50 + l 0,50 + l 66,50 + l 66,0 + l 66,-50 + l 0,-50 + l -66,-50 + z + </string> + + <!-- Whether the display cutout region of the main built-in display should be forced to + black in software (to avoid aliasing or emulate a cutout that is not physically existent). + --> + <bool name="config_fillMainBuiltInDisplayCutout">true</bool> + + <!-- Height of the status bar --> + <dimen name="status_bar_height">150px</dimen> + +</resources> + + diff --git a/packages/overlays/DisplayCutoutEmulationOverlay/res/values/strings.xml b/packages/overlays/DisplayCutoutEmulationOverlay/res/values/strings.xml new file mode 100644 index 000000000000..5d5c42590b21 --- /dev/null +++ b/packages/overlays/DisplayCutoutEmulationOverlay/res/values/strings.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <string name="display_cutout_emulation_overlay">Display Cutout Emulation</string> + +</resources> + diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 10a809aaae08..04cee676075d 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -5134,6 +5134,15 @@ message MetricsEvent { // OS: P ACTION_SCREENSHOT_POWER_MENU = 1282; + // OPEN: Settings > Apps & Notifications -> Special app access -> Storage Access + // CATEGORY: SETTINGS + // OS: P + STORAGE_ACCESS = 1283; + + // OPEN: Settings > Apps & Notifications -> Special app access -> Storage Access -> Package + // CATEGORY: SETTINGS + // OS: P + APPLICATIONS_STORAGE_DETAIL = 1284; // ---- End P Constants, all P constants go above this line ---- // Add new aosp constants above this line. diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 6f52692eff91..fc6058c6df7b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1362,14 +1362,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private int computeRelevantEventTypes(UserState userState, Client client) { int relevantEventTypes = 0; - int numBoundServices = userState.mBoundServices.size(); - for (int i = 0; i < numBoundServices; i++) { - AccessibilityServiceConnection service = - userState.mBoundServices.get(i); + // Use iterator for thread-safety + for (AccessibilityServiceConnection service : userState.mBoundServices) { relevantEventTypes |= isClientInPackageWhitelist(service.getServiceInfo(), client) ? service.getRelevantEventTypes() : 0; } + relevantEventTypes |= isClientInPackageWhitelist( mUiAutomationManager.getServiceInfo(), client) ? mUiAutomationManager.getRelevantEventTypes() diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java index 74d2dddcdfb3..0a03b7fc85b3 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java @@ -17,6 +17,7 @@ package com.android.server.accessibility; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; +import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_POINTER_DOWN; @@ -364,7 +365,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { persistScaleAndTransitionTo(mViewportDraggingState); - } else if (action == ACTION_UP) { + } else if (action == ACTION_UP || action == ACTION_CANCEL) { persistScaleAndTransitionTo(mDetectingState); @@ -496,7 +497,9 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { } } break; - case ACTION_UP: { + + case ACTION_UP: + case ACTION_CANCEL: { if (!mZoomedInBeforeDrag) zoomOff(); clear(); transitionTo(mDetectingState); @@ -533,12 +536,15 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (event.getActionMasked() == ACTION_UP) { - transitionTo(mDetectingState); - } + switch (event.getActionMasked()) { + case ACTION_UP: + case ACTION_CANCEL: { + transitionTo(mDetectingState); + } break; - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mLastDelegatedDownEventTime = event.getDownTime(); + case ACTION_DOWN: { + mLastDelegatedDownEventTime = event.getDownTime(); + } break; } if (getNext() != null) { // We cache some events to see if the user wants to trigger magnification. diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java index 03591a812bdd..350d7af78a4c 100644 --- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java @@ -207,6 +207,10 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter public static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED"; public static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName"; + // Time delay for initialization operations that can be delayed so as not to consume too much CPU + // on bring-up and increase time-to-UI. + private static final long INITIALIZATION_DELAY_MILLIS = 3000; + // Timeout interval for deciding that a bind or clear-data has taken too long private static final long TIMEOUT_INTERVAL = 10 * 1000; @@ -282,6 +286,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter private final BackupPasswordManager mBackupPasswordManager; + // Time when we post the transport registration operation + private final long mRegisterTransportsRequestedTime; + @GuardedBy("mPendingRestores") private boolean mIsRestoreInProgress; @GuardedBy("mPendingRestores") @@ -735,6 +742,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter // Set up our transport options and initialize the default transport SystemConfig systemConfig = SystemConfig.getInstance(); Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist(); + if (transportWhitelist == null) { + transportWhitelist = Collections.emptySet(); + } String transport = Settings.Secure.getString( @@ -749,8 +759,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter new TransportManager( context, transportWhitelist, - transport, - backupThread.getLooper()); + transport); // If encrypted file systems is enabled or disabled, this call will return the // correct directory. @@ -852,12 +861,14 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter } mTransportManager = transportManager; - mTransportManager.setTransportBoundListener(mTransportBoundListener); - mTransportManager.registerAllTransports(); + mTransportManager.setOnTransportRegisteredListener(this::onTransportRegistered); + mRegisterTransportsRequestedTime = SystemClock.elapsedRealtime(); + mBackupHandler.postDelayed( + mTransportManager::registerTransports, INITIALIZATION_DELAY_MILLIS); - // Now that we know about valid backup participants, parse any - // leftover journal files into the pending backup set - mBackupHandler.post(this::parseLeftoverJournals); + // Now that we know about valid backup participants, parse any leftover journal files into + // the pending backup set + mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS); // Power management mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*"); @@ -1151,39 +1162,28 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter } } - private TransportManager.TransportBoundListener mTransportBoundListener = - new TransportManager.TransportBoundListener() { - @Override - public boolean onTransportBound(IBackupTransport transport) { - // If the init sentinel file exists, we need to be sure to perform the init - // as soon as practical. We also create the state directory at registration - // time to ensure it's present from the outset. - String name = null; - try { - name = transport.name(); - String transportDirName = transport.transportDirName(); - File stateDir = new File(mBaseStateDir, transportDirName); - stateDir.mkdirs(); + private void onTransportRegistered(String transportName, String transportDirName) { + if (DEBUG) { + long timeMs = SystemClock.elapsedRealtime() - mRegisterTransportsRequestedTime; + Slog.d(TAG, "Transport " + transportName + " registered " + timeMs + + "ms after first request (delay = " + INITIALIZATION_DELAY_MILLIS + "ms)"); + } - File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME); - if (initSentinel.exists()) { - synchronized (mQueueLock) { - mPendingInits.add(name); + File stateDir = new File(mBaseStateDir, transportDirName); + stateDir.mkdirs(); - // TODO: pick a better starting time than now + 1 minute - long delay = 1000 * 60; // one minute, in milliseconds - mAlarmManager.set(AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + delay, mRunInitIntent); - } - } - return true; - } catch (Exception e) { - // the transport threw when asked its file naming prefs; declare it invalid - Slog.w(TAG, "Failed to regiser transport: " + name); - return false; - } - } - }; + File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME); + if (initSentinel.exists()) { + synchronized (mQueueLock) { + mPendingInits.add(transportName); + + // TODO: pick a better starting time than now + 1 minute + long delay = 1000 * 60; // one minute, in milliseconds + mAlarmManager.set(AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + delay, mRunInitIntent); + } + } + } // ----- Track installation/removal of packages ----- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -2891,14 +2891,14 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransports"); - return mTransportManager.getBoundTransportNames(); + return mTransportManager.getRegisteredTransportNames(); } @Override public ComponentName[] listAllTransportComponents() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransportComponents"); - return mTransportManager.getAllTransportComponents(); + return mTransportManager.getRegisteredTransportComponents(); } @Override @@ -3003,6 +3003,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter /** Selects transport {@code transportName} and returns previous selected transport. */ @Override + @Deprecated + @Nullable public String selectBackupTransport(String transportName) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BACKUP, "selectBackupTransport"); diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java index 34b8935939ff..09456b68e1c0 100644 --- a/services/backup/java/com/android/server/backup/TransportManager.java +++ b/services/backup/java/com/android/server/backup/TransportManager.java @@ -16,93 +16,56 @@ package com.android.server.backup; -import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; - import android.annotation.Nullable; +import android.annotation.WorkerThread; import android.app.backup.BackupManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; import android.os.RemoteException; import android.os.UserHandle; -import android.provider.Settings; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.EventLog; -import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.backup.IBackupTransport; +import com.android.internal.util.Preconditions; import com.android.server.EventLogTags; +import com.android.server.backup.transport.OnTransportRegisteredListener; import com.android.server.backup.transport.TransportClient; import com.android.server.backup.transport.TransportClientManager; import com.android.server.backup.transport.TransportConnectionListener; import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.transport.TransportNotRegisteredException; -import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; -/** - * Handles in-memory bookkeeping of all BackupTransport objects. - */ +/** Handles in-memory bookkeeping of all BackupTransport objects. */ public class TransportManager { - private static final String TAG = "BackupTransportManager"; @VisibleForTesting public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST"; - private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec - private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins - private static final int REBINDING_TIMEOUT_MSG = 1; - private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST); private final Context mContext; private final PackageManager mPackageManager; private final Set<ComponentName> mTransportWhitelist; - private final Handler mHandler; private final TransportClientManager mTransportClientManager; - - /** - * This listener is called after we bind to any transport. If it returns true, this is a valid - * transport. - */ - private TransportBoundListener mTransportBoundListener; - private final Object mTransportLock = new Object(); - - /** - * We have detected these transports on the device. Unless in exceptional cases, we are also - * bound to all of these. - */ - @GuardedBy("mTransportLock") - private final Map<ComponentName, TransportConnection> mValidTransports = new ArrayMap<>(); - - /** We are currently bound to these transports. */ - @GuardedBy("mTransportLock") - private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>(); - - /** @see #getEligibleTransportComponents() */ - @GuardedBy("mTransportLock") - private final Set<ComponentName> mEligibleTransports = new ArraySet<>(); + private OnTransportRegisteredListener mOnTransportRegisteredListener = (c, n) -> {}; /** @see #getRegisteredTransportNames() */ @GuardedBy("mTransportLock") @@ -110,120 +73,98 @@ public class TransportManager { new ArrayMap<>(); @GuardedBy("mTransportLock") + @Nullable private volatile String mCurrentTransportName; - TransportManager( - Context context, - Set<ComponentName> whitelist, - String defaultTransport, - TransportBoundListener listener, - Looper looper) { - this(context, whitelist, defaultTransport, looper); - mTransportBoundListener = listener; + TransportManager(Context context, Set<ComponentName> whitelist, String selectedTransport) { + this(context, whitelist, selectedTransport, new TransportClientManager(context)); } + @VisibleForTesting TransportManager( Context context, Set<ComponentName> whitelist, - String defaultTransport, - Looper looper) { + String selectedTransport, + TransportClientManager transportClientManager) { mContext = context; mPackageManager = context.getPackageManager(); - if (whitelist != null) { - mTransportWhitelist = whitelist; - } else { - mTransportWhitelist = new ArraySet<>(); - } - mCurrentTransportName = defaultTransport; - mHandler = new RebindOnTimeoutHandler(looper); - mTransportClientManager = new TransportClientManager(context); + mTransportWhitelist = Preconditions.checkNotNull(whitelist); + mCurrentTransportName = selectedTransport; + mTransportClientManager = transportClientManager; } - public void setTransportBoundListener(TransportBoundListener transportBoundListener) { - mTransportBoundListener = transportBoundListener; + /* Sets a listener to be called whenever a transport is registered. */ + public void setOnTransportRegisteredListener(OnTransportRegisteredListener listener) { + mOnTransportRegisteredListener = listener; } + @WorkerThread void onPackageAdded(String packageName) { - // New package added. Bind to all transports it contains. - synchronized (mTransportLock) { - log_verbose("Package added. Binding to all transports. " + packageName); - bindToAllInternal(packageName, null /* all components */); - } + registerTransportsFromPackage(packageName, transportComponent -> true); } void onPackageRemoved(String packageName) { - // Package removed. Remove all its transports from our list. These transports have already - // been removed from mBoundTransports because onServiceDisconnected would already been - // called on TransportConnection objects. synchronized (mTransportLock) { - Iterator<Map.Entry<ComponentName, TransportConnection>> iter = - mValidTransports.entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry<ComponentName, TransportConnection> validTransport = iter.next(); - ComponentName componentName = validTransport.getKey(); - if (componentName.getPackageName().equals(packageName)) { - TransportConnection transportConnection = validTransport.getValue(); - iter.remove(); - if (transportConnection != null) { - mContext.unbindService(transportConnection); - log_verbose("Package removed, removing transport: " - + componentName.flattenToShortString()); - } - } - } - removeTransportsIfLocked( - componentName -> packageName.equals(componentName.getPackageName())); + mRegisteredTransportsDescriptionMap.keySet().removeIf(fromPackageFilter(packageName)); } } - void onPackageChanged(String packageName, String[] components) { + @WorkerThread + void onPackageChanged(String packageName, String... components) { synchronized (mTransportLock) { - // Remove all changed components from mValidTransports. We'll bind to them again - // and re-add them if still valid. - Set<ComponentName> transportsToBeRemoved = new ArraySet<>(); - for (String component : components) { - ComponentName componentName = new ComponentName(packageName, component); - transportsToBeRemoved.add(componentName); - TransportConnection removed = mValidTransports.remove(componentName); - if (removed != null) { - mContext.unbindService(removed); - log_verbose("Package changed. Removing transport: " + - componentName.flattenToShortString()); - } - } - removeTransportsIfLocked(transportsToBeRemoved::contains); - bindToAllInternal(packageName, components); + Set<ComponentName> transportComponents = + Stream.of(components) + .map(component -> new ComponentName(packageName, component)) + .collect(Collectors.toSet()); + + mRegisteredTransportsDescriptionMap.keySet().removeIf(transportComponents::contains); + registerTransportsFromPackage(packageName, transportComponents::contains); } } - @GuardedBy("mTransportLock") - private void removeTransportsIfLocked(Predicate<ComponentName> filter) { - mEligibleTransports.removeIf(filter); - mRegisteredTransportsDescriptionMap.keySet().removeIf(filter); + /** + * Returns the {@link ComponentName}s of the registered transports. + * + * <p>A *registered* transport is a transport that satisfies intent with action + * android.backup.TRANSPORT_HOST, returns true for {@link #isTransportTrusted(ComponentName)} + * and that we have successfully connected to once. + */ + ComponentName[] getRegisteredTransportComponents() { + synchronized (mTransportLock) { + return mRegisteredTransportsDescriptionMap + .keySet() + .toArray(new ComponentName[mRegisteredTransportsDescriptionMap.size()]); + } } - public IBackupTransport getTransportBinder(String transportName) { + /** + * Returns the names of the registered transports. + * + * @see #getRegisteredTransportComponents() + */ + String[] getRegisteredTransportNames() { synchronized (mTransportLock) { - ComponentName component = mBoundTransports.get(transportName); - if (component == null) { - Slog.w(TAG, "Transport " + transportName + " not bound."); - return null; - } - TransportConnection conn = mValidTransports.get(component); - if (conn == null) { - Slog.w(TAG, "Transport " + transportName + " not valid."); - return null; - } - return conn.getBinder(); + return mRegisteredTransportsDescriptionMap + .values() + .stream() + .map(transportDescription -> transportDescription.name) + .toArray(String[]::new); } } - public IBackupTransport getCurrentTransportBinder() { - return getTransportBinder(mCurrentTransportName); + /** Returns a set with the whitelisted transports. */ + Set<ComponentName> getTransportWhitelist() { + return mTransportWhitelist; + } + + @Nullable + String getCurrentTransportName() { + return mCurrentTransportName; } /** * Returns the transport name associated with {@code transportComponent}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ public String getTransportName(ComponentName transportComponent) @@ -234,33 +175,46 @@ public class TransportManager { } /** - * Retrieve the configuration intent of {@code transportName}. + * Retrieves the transport dir name of {@code transportComponent}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ - @Nullable - public Intent getTransportConfigurationIntent(String transportName) + public String getTransportDirName(ComponentName transportComponent) throws TransportNotRegisteredException { synchronized (mTransportLock) { - return getRegisteredTransportDescriptionOrThrowLocked(transportName) - .configurationIntent; + return getRegisteredTransportDescriptionOrThrowLocked(transportComponent) + .transportDirName; + } + } + + /** + * Retrieves the transport dir name of {@code transportName}. + * + * @throws TransportNotRegisteredException if the transport is not registered. + */ + public String getTransportDirName(String transportName) throws TransportNotRegisteredException { + synchronized (mTransportLock) { + return getRegisteredTransportDescriptionOrThrowLocked(transportName).transportDirName; } } /** - * Retrieve the data management intent of {@code transportName}. + * Retrieves the configuration intent of {@code transportName}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ @Nullable - public Intent getTransportDataManagementIntent(String transportName) + public Intent getTransportConfigurationIntent(String transportName) throws TransportNotRegisteredException { synchronized (mTransportLock) { return getRegisteredTransportDescriptionOrThrowLocked(transportName) - .dataManagementIntent; + .configurationIntent; } } /** - * Retrieve the current destination string of {@code transportName}. + * Retrieves the current destination string of {@code transportName}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ public String getTransportCurrentDestinationString(String transportName) @@ -272,66 +226,101 @@ public class TransportManager { } /** - * Retrieve the data management label of {@code transportName}. + * Retrieves the data management intent of {@code transportName}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ @Nullable - public String getTransportDataManagementLabel(String transportName) + public Intent getTransportDataManagementIntent(String transportName) throws TransportNotRegisteredException { synchronized (mTransportLock) { return getRegisteredTransportDescriptionOrThrowLocked(transportName) - .dataManagementLabel; + .dataManagementIntent; } } /** - * Retrieve the transport dir name of {@code transportName}. + * Retrieves the data management label of {@code transportName}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ - public String getTransportDirName(String transportName) + @Nullable + public String getTransportDataManagementLabel(String transportName) throws TransportNotRegisteredException { synchronized (mTransportLock) { return getRegisteredTransportDescriptionOrThrowLocked(transportName) - .transportDirName; + .dataManagementLabel; } } - /** - * Retrieve the transport dir name of {@code transportComponent}. - * @throws TransportNotRegisteredException if the transport is not registered. - */ - public String getTransportDirName(ComponentName transportComponent) - throws TransportNotRegisteredException { + /* Returns true if the transport identified by {@code transportName} is registered. */ + public boolean isTransportRegistered(String transportName) { synchronized (mTransportLock) { - return getRegisteredTransportDescriptionOrThrowLocked(transportComponent) - .transportDirName; + return getRegisteredTransportEntryLocked(transportName) != null; } } /** * Execute {@code transportConsumer} for each registered transport passing the transport name. * This is called with an internal lock held, ensuring that the transport will remain registered - * while {@code transportConsumer} is being executed. Don't do heavy operations in - * {@code transportConsumer}. + * while {@code transportConsumer} is being executed. Don't do heavy operations in {@code + * transportConsumer}. */ public void forEachRegisteredTransport(Consumer<String> transportConsumer) { synchronized (mTransportLock) { - for (TransportDescription transportDescription - : mRegisteredTransportsDescriptionMap.values()) { + for (TransportDescription transportDescription : + mRegisteredTransportsDescriptionMap.values()) { transportConsumer.accept(transportDescription.name); } } } - public String getTransportName(IBackupTransport binder) { + /** + * Updates given values for the transport already registered and identified with {@param + * transportComponent}. If the transport is not registered it will log and return. + */ + public void updateTransportAttributes( + ComponentName transportComponent, + String name, + @Nullable Intent configurationIntent, + String currentDestinationString, + @Nullable Intent dataManagementIntent, + @Nullable String dataManagementLabel) { synchronized (mTransportLock) { - for (TransportConnection conn : mValidTransports.values()) { - if (conn.getBinder() == binder) { - return conn.getName(); - } + TransportDescription description = + mRegisteredTransportsDescriptionMap.get(transportComponent); + if (description == null) { + Slog.e(TAG, "Transport " + name + " not registered tried to change description"); + return; } + description.name = name; + description.configurationIntent = configurationIntent; + description.currentDestinationString = currentDestinationString; + description.dataManagementIntent = dataManagementIntent; + description.dataManagementLabel = dataManagementLabel; + Slog.d(TAG, "Transport " + name + " updated its attributes"); } - return null; + } + + @GuardedBy("mTransportLock") + private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( + ComponentName transportComponent) throws TransportNotRegisteredException { + TransportDescription description = + mRegisteredTransportsDescriptionMap.get(transportComponent); + if (description == null) { + throw new TransportNotRegisteredException(transportComponent); + } + return description; + } + + @GuardedBy("mTransportLock") + private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( + String transportName) throws TransportNotRegisteredException { + TransportDescription description = getRegisteredTransportDescriptionLocked(transportName); + if (description == null) { + throw new TransportNotRegisteredException(transportName); + } + return description; } @GuardedBy("mTransportLock") @@ -351,21 +340,11 @@ public class TransportManager { } @GuardedBy("mTransportLock") - private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( - String transportName) throws TransportNotRegisteredException { - TransportDescription description = getRegisteredTransportDescriptionLocked(transportName); - if (description == null) { - throw new TransportNotRegisteredException(transportName); - } - return description; - } - - @GuardedBy("mTransportLock") @Nullable private Map.Entry<ComponentName, TransportDescription> getRegisteredTransportEntryLocked( String transportName) { - for (Map.Entry<ComponentName, TransportDescription> entry - : mRegisteredTransportsDescriptionMap.entrySet()) { + for (Map.Entry<ComponentName, TransportDescription> entry : + mRegisteredTransportsDescriptionMap.entrySet()) { TransportDescription description = entry.getValue(); if (transportName.equals(description.name)) { return entry; @@ -374,17 +353,16 @@ public class TransportManager { return null; } - @GuardedBy("mTransportLock") - private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( - ComponentName transportComponent) throws TransportNotRegisteredException { - TransportDescription description = - mRegisteredTransportsDescriptionMap.get(transportComponent); - if (description == null) { - throw new TransportNotRegisteredException(transportComponent); - } - return description; - } - + /** + * Returns a {@link TransportClient} for {@code transportName} or {@code null} if not + * registered. + * + * @param transportName The name of the transport. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient} or null if not registered. + */ @Nullable public TransportClient getTransportClient(String transportName, String caller) { try { @@ -395,6 +373,16 @@ public class TransportManager { } } + /** + * Returns a {@link TransportClient} for {@code transportName} or throws if not registered. + * + * @param transportName The name of the transport. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient}. + * @throws TransportNotRegisteredException if the transport is not registered. + */ public TransportClient getTransportClientOrThrow(String transportName, String caller) throws TransportNotRegisteredException { synchronized (mTransportLock) { @@ -406,19 +394,14 @@ public class TransportManager { } } - public boolean isTransportRegistered(String transportName) { - synchronized (mTransportLock) { - return getRegisteredTransportEntryLocked(transportName) != null; - } - } - /** - * Returns a {@link TransportClient} for the current transport or null if not found. + * Returns a {@link TransportClient} for the current transport or {@code null} if not + * registered. * * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more * details. - * @return A {@link TransportClient} or null if not found. + * @return A {@link TransportClient} or null if not registered. */ @Nullable public TransportClient getCurrentTransportClient(String caller) { @@ -455,130 +438,88 @@ public class TransportManager { mTransportClientManager.disposeOfTransportClient(transportClient, caller); } - String[] getBoundTransportNames() { - synchronized (mTransportLock) { - return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]); - } - } - - ComponentName[] getAllTransportComponents() { - synchronized (mTransportLock) { - return mValidTransports.keySet().toArray(new ComponentName[mValidTransports.size()]); - } - } - /** - * An *eligible* transport is a service component that satisfies intent with action - * android.backup.TRANSPORT_HOST and returns true for - * {@link #isTransportTrusted(ComponentName)}. It may be registered or not registered. - * This method returns the {@link ComponentName}s of those transports. + * Sets {@code transportName} as selected transport and returns previously selected transport + * name. If there was no previous transport it returns null. + * + * <p>You should NOT call this method in new code. This won't make any checks against {@code + * transportName}, putting any operation at risk of a {@link TransportNotRegisteredException} or + * another error at the time it's being executed. + * + * <p>{@link Deprecated} as public, this method can be used as private. */ - ComponentName[] getEligibleTransportComponents() { + @Deprecated + @Nullable + String selectTransport(String transportName) { synchronized (mTransportLock) { - return mEligibleTransports.toArray(new ComponentName[mEligibleTransports.size()]); + String prevTransport = mCurrentTransportName; + mCurrentTransportName = transportName; + return prevTransport; } } - Set<ComponentName> getTransportWhitelist() { - return mTransportWhitelist; - } - /** - * A *registered* transport is an eligible transport that has been successfully connected and - * that returned true for method - * {@link TransportBoundListener#onTransportBound(IBackupTransport)} of TransportBoundListener - * provided in the constructor. This method returns the names of the registered transports. + * Tries to register the transport if not registered. If successful also selects the transport. + * + * @param transportComponent Host of the transport. + * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID} + * or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}. */ - String[] getRegisteredTransportNames() { + @WorkerThread + public int registerAndSelectTransport(ComponentName transportComponent) { synchronized (mTransportLock) { - return mRegisteredTransportsDescriptionMap.values().stream() - .map(transportDescription -> transportDescription.name) - .toArray(String[]::new); - } - } + if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) { + int result = registerTransport(transportComponent); + if (result != BackupManager.SUCCESS) { + return result; + } + } - /** - * Updates given values for the transport already registered and identified with - * {@param transportComponent}. If the transport is not registered it will log and return. - */ - public void updateTransportAttributes( - ComponentName transportComponent, - String name, - @Nullable Intent configurationIntent, - String currentDestinationString, - @Nullable Intent dataManagementIntent, - @Nullable String dataManagementLabel) { - synchronized (mTransportLock) { - TransportDescription description = - mRegisteredTransportsDescriptionMap.get(transportComponent); - if (description == null) { - Slog.e(TAG, "Transport " + name + " not registered tried to change description"); - return; + try { + selectTransport(getTransportName(transportComponent)); + return BackupManager.SUCCESS; + } catch (TransportNotRegisteredException e) { + // Shouldn't happen because we are holding the lock + Slog.wtf(TAG, "Transport unexpectedly not registered"); + return BackupManager.ERROR_TRANSPORT_UNAVAILABLE; } - description.name = name; - description.configurationIntent = configurationIntent; - description.currentDestinationString = currentDestinationString; - description.dataManagementIntent = dataManagementIntent; - description.dataManagementLabel = dataManagementLabel; - Slog.d(TAG, "Transport " + name + " updated its attributes"); } } - @Nullable - String getCurrentTransportName() { - return mCurrentTransportName; - } - - // This is for mocking, Mockito can't mock if package-protected and in the same package but - // different class loaders. Checked with the debugger and class loaders are different - // See https://github.com/mockito/mockito/issues/796 - @VisibleForTesting(visibility = PACKAGE) - public void registerAllTransports() { - bindToAllInternal(null /* all packages */, null /* all components */); + @WorkerThread + public void registerTransports() { + registerTransportsForIntent(mTransportServiceIntent, transportComponent -> true); } - /** - * Bind to all transports belonging to the given package and the given component list. - * null acts a wildcard. - * - * If packageName is null, bind to all transports in all packages. - * If components is null, bind to all transports in the given package. - */ - private void bindToAllInternal(String packageName, String[] components) { - PackageInfo pkgInfo = null; - if (packageName != null) { - try { - pkgInfo = mPackageManager.getPackageInfo(packageName, 0); - } catch (PackageManager.NameNotFoundException e) { - Slog.w(TAG, "Package not found: " + packageName); - return; - } + @WorkerThread + private void registerTransportsFromPackage( + String packageName, Predicate<ComponentName> transportComponentFilter) { + try { + mPackageManager.getPackageInfo(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Trying to register transports from package not found " + packageName); + return; } - Intent intent = new Intent(mTransportServiceIntent); - if (packageName != null) { - intent.setPackage(packageName); - } + registerTransportsForIntent( + new Intent(mTransportServiceIntent).setPackage(packageName), + transportComponentFilter.and(fromPackageFilter(packageName))); + } - List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser( - intent, 0, UserHandle.USER_SYSTEM); - if (hosts != null) { + @WorkerThread + private void registerTransportsForIntent( + Intent intent, Predicate<ComponentName> transportComponentFilter) { + List<ResolveInfo> hosts = + mPackageManager.queryIntentServicesAsUser(intent, 0, UserHandle.USER_SYSTEM); + if (hosts == null) { + return; + } + synchronized (mTransportLock) { for (ResolveInfo host : hosts) { - final ComponentName infoComponentName = getComponentName(host.serviceInfo); - boolean shouldBind = false; - if (components != null && packageName != null) { - for (String component : components) { - ComponentName cn = new ComponentName(pkgInfo.packageName, component); - if (infoComponentName.equals(cn)) { - shouldBind = true; - break; - } - } - } else { - shouldBind = true; - } - if (shouldBind && isTransportTrusted(infoComponentName)) { - tryBindTransport(infoComponentName); + ComponentName transportComponent = host.serviceInfo.getComponentName(); + if (transportComponentFilter.test(transportComponent) + && isTransportTrusted(transportComponent)) { + registerTransport(transportComponent); } } } @@ -605,64 +546,6 @@ public class TransportManager { return true; } - private void tryBindTransport(ComponentName transportComponentName) { - Slog.d(TAG, "Binding to transport: " + transportComponentName.flattenToShortString()); - // TODO: b/22388012 (Multi user backup and restore) - TransportConnection connection = new TransportConnection(transportComponentName); - synchronized (mTransportLock) { - mEligibleTransports.add(transportComponentName); - } - if (bindToTransport(transportComponentName, connection)) { - synchronized (mTransportLock) { - mValidTransports.put(transportComponentName, connection); - } - } else { - Slog.w(TAG, "Couldn't bind to transport " + transportComponentName); - } - } - - private boolean bindToTransport(ComponentName componentName, ServiceConnection connection) { - Intent intent = new Intent(mTransportServiceIntent) - .setComponent(componentName); - return mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE, - createSystemUserHandle()); - } - - String selectTransport(String transportName) { - synchronized (mTransportLock) { - String prevTransport = mCurrentTransportName; - mCurrentTransportName = transportName; - return prevTransport; - } - } - - /** - * Tries to register the transport if not registered. If successful also selects the transport. - * - * @param transportComponent Host of the transport. - * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID} - * or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}. - */ - public int registerAndSelectTransport(ComponentName transportComponent) { - synchronized (mTransportLock) { - if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) { - int result = registerTransport(transportComponent); - if (result != BackupManager.SUCCESS) { - return result; - } - } - - try { - selectTransport(getTransportName(transportComponent)); - return BackupManager.SUCCESS; - } catch (TransportNotRegisteredException e) { - // Shouldn't happen because we are holding the lock - Slog.wtf(TAG, "Transport unexpectedly not registered"); - return BackupManager.ERROR_TRANSPORT_UNAVAILABLE; - } - } - } - /** * Tries to register transport represented by {@code transportComponent}. * @@ -670,7 +553,12 @@ public class TransportManager { * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID} * or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}. */ + @WorkerThread private int registerTransport(ComponentName transportComponent) { + if (!isTransportTrusted(transportComponent)) { + return BackupManager.ERROR_TRANSPORT_INVALID; + } + String transportString = transportComponent.flattenToShortString(); String callerLogString = "TransportManager.registerTransport()"; @@ -689,26 +577,21 @@ public class TransportManager { EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 1); int result; - if (isTransportValid(transport)) { - try { - registerTransport(transportComponent, transport); - // If registerTransport() hasn't thrown... - result = BackupManager.SUCCESS; - } catch (RemoteException e) { - Slog.e(TAG, "Transport " + transportString + " died while registering"); - result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE; - } - } else { - Slog.w(TAG, "Can't register invalid transport " + transportString); - result = BackupManager.ERROR_TRANSPORT_INVALID; - } - - mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString); - if (result == BackupManager.SUCCESS) { + try { + String transportName = transport.name(); + String transportDirName = transport.transportDirName(); + registerTransport(transportComponent, transport); + // If registerTransport() hasn't thrown... Slog.d(TAG, "Transport " + transportString + " registered"); - } else { + mOnTransportRegisteredListener.onTransportRegistered(transportName, transportDirName); + result = BackupManager.SUCCESS; + } catch (RemoteException e) { + Slog.e(TAG, "Transport " + transportString + " died while registering"); EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 0); + result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE; } + + mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString); return result; } @@ -717,204 +600,20 @@ public class TransportManager { throws RemoteException { synchronized (mTransportLock) { String name = transport.name(); - TransportDescription description = new TransportDescription( - name, - transport.transportDirName(), - transport.configurationIntent(), - transport.currentDestinationString(), - transport.dataManagementIntent(), - transport.dataManagementLabel()); + TransportDescription description = + new TransportDescription( + name, + transport.transportDirName(), + transport.configurationIntent(), + transport.currentDestinationString(), + transport.dataManagementIntent(), + transport.dataManagementLabel()); mRegisteredTransportsDescriptionMap.put(transportComponent, description); } } - private boolean isTransportValid(IBackupTransport transport) { - if (mTransportBoundListener == null) { - Slog.w(TAG, "setTransportBoundListener() not called, assuming transport invalid"); - return false; - } - return mTransportBoundListener.onTransportBound(transport); - } - - private class TransportConnection implements ServiceConnection { - - // Hold mTransportLock to access these fields so as to provide a consistent view of them. - private volatile IBackupTransport mBinder; - private volatile String mTransportName; - - private final ComponentName mTransportComponent; - - private TransportConnection(ComponentName transportComponent) { - mTransportComponent = transportComponent; - } - - @Override - public void onServiceConnected(ComponentName component, IBinder binder) { - synchronized (mTransportLock) { - mBinder = IBackupTransport.Stub.asInterface(binder); - boolean success = false; - - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, - component.flattenToShortString(), 1); - - try { - mTransportName = mBinder.name(); - // BackupManager requests some fields from the transport. If they are - // invalid, throw away this transport. - if (isTransportValid(mBinder)) { - // We're now using the always-bound connection to do the registration but - // when we remove the always-bound code this will be in the first binding - // TODO: Move registration to first binding - registerTransport(component, mBinder); - // If registerTransport() hasn't thrown... - success = true; - } - } catch (RemoteException e) { - success = false; - Slog.e(TAG, "Couldn't get transport name.", e); - } finally { - // we need to intern() the String of the component, so that we can use it with - // Handler's removeMessages(), which uses == operator to compare the tokens - String componentShortString = component.flattenToShortString().intern(); - if (success) { - Slog.d(TAG, "Bound to transport: " + componentShortString); - mBoundTransports.put(mTransportName, component); - // cancel rebinding on timeout for this component as we've already connected - mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString); - } else { - Slog.w(TAG, "Bound to transport " + componentShortString + - " but it is invalid"); - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, - componentShortString, 0); - mContext.unbindService(this); - mValidTransports.remove(component); - mEligibleTransports.remove(component); - mBinder = null; - } - } - } - } - - @Override - public void onServiceDisconnected(ComponentName component) { - synchronized (mTransportLock) { - mBinder = null; - mBoundTransports.remove(mTransportName); - } - String componentShortString = component.flattenToShortString(); - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, componentShortString, 0); - Slog.w(TAG, "Disconnected from transport " + componentShortString); - scheduleRebindTimeout(component); - } - - /** - * We'll attempt to explicitly rebind to a transport if it hasn't happened automatically - * for a few minutes after the binding went away. - */ - private void scheduleRebindTimeout(ComponentName component) { - // we need to intern() the String of the component, so that we can use it with Handler's - // removeMessages(), which uses == operator to compare the tokens - final String componentShortString = component.flattenToShortString().intern(); - final long rebindTimeout = getRebindTimeout(); - mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString); - Message msg = mHandler.obtainMessage(REBINDING_TIMEOUT_MSG); - msg.obj = componentShortString; - mHandler.sendMessageDelayed(msg, rebindTimeout); - Slog.d(TAG, "Scheduled explicit rebinding for " + componentShortString + " in " - + rebindTimeout + "ms"); - } - - // Intentionally not synchronized -- the variable is volatile and changes to its value - // are inside synchronized blocks, providing a memory sync barrier; and this method - // does not touch any other state protected by that lock. - private IBackupTransport getBinder() { - return mBinder; - } - - // Intentionally not synchronized; same as getBinder() - private String getName() { - return mTransportName; - } - - // Intentionally not synchronized; same as getBinder() - private void bindIfUnbound() { - if (mBinder == null) { - Slog.d(TAG, - "Rebinding to transport " + mTransportComponent.flattenToShortString()); - bindToTransport(mTransportComponent, this); - } - } - - private long getRebindTimeout() { - final boolean isDeviceProvisioned = Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 0) != 0; - return isDeviceProvisioned - ? REBINDING_TIMEOUT_PROVISIONED_MS - : REBINDING_TIMEOUT_UNPROVISIONED_MS; - } - } - - public interface TransportBoundListener { - /** Should return true if this is a valid transport. */ - boolean onTransportBound(IBackupTransport binder); - } - - private class RebindOnTimeoutHandler extends Handler { - - RebindOnTimeoutHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - if (msg.what == REBINDING_TIMEOUT_MSG) { - String componentShortString = (String) msg.obj; - ComponentName transportComponent = - ComponentName.unflattenFromString(componentShortString); - synchronized (mTransportLock) { - if (mBoundTransports.containsValue(transportComponent)) { - Slog.d(TAG, "Explicit rebinding timeout passed, but already bound to " - + componentShortString + " so not attempting to rebind"); - return; - } - Slog.d(TAG, "Explicit rebinding timeout passed, attempting rebinding to: " - + componentShortString); - // unbind the existing (broken) connection - TransportConnection conn = mValidTransports.get(transportComponent); - if (conn != null) { - mContext.unbindService(conn); - Slog.d(TAG, "Unbinding the existing (broken) connection to transport: " - + componentShortString); - } - } - // rebind to transport - tryBindTransport(transportComponent); - } else { - Slog.e(TAG, "Unknown message sent to RebindOnTimeoutHandler, msg.what: " - + msg.what); - } - } - } - - private static void log_verbose(String message) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Slog.v(TAG, message); - } - } - - // These only exists to make it testable with Robolectric, which is not updated to API level 24 - // yet. - // TODO: Get rid of this once Robolectric is updated. - private static ComponentName getComponentName(ServiceInfo serviceInfo) { - return new ComponentName(serviceInfo.packageName, serviceInfo.name); - } - - // These only exists to make it testable with Robolectric, which is not updated to API level 24 - // yet. - // TODO: Get rid of this once Robolectric is updated. - public static UserHandle createSystemUserHandle() { - return new UserHandle(UserHandle.USER_SYSTEM); + private static Predicate<ComponentName> fromPackageFilter(String packageName) { + return transportComponent -> packageName.equals(transportComponent.getPackageName()); } private static class TransportDescription { diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java index c2322413d2a7..cc3af8c8c933 100644 --- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java +++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java @@ -907,7 +907,15 @@ public class PerformBackupTask implements BackupRestoreTask { backupData = ParcelFileDescriptor.open(mBackupDataName, ParcelFileDescriptor.MODE_READ_ONLY); backupManagerService.addBackupTrace("sending data to transport"); - int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0; + + int userInitiatedFlag = + mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0; + int incrementalFlag = + mSavedStateName.length() == 0 + ? BackupTransport.FLAG_NON_INCREMENTAL + : BackupTransport.FLAG_INCREMENTAL; + int flags = userInitiatedFlag | incrementalFlag; + mStatus = transport.performBackup(mCurrentPackage, backupData, flags); } diff --git a/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java b/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java new file mode 100644 index 000000000000..391ec2d7f294 --- /dev/null +++ b/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java @@ -0,0 +1,33 @@ +/* + * 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 com.android.server.backup.transport; + +import com.android.server.backup.TransportManager; + +/** + * Listener called when a transport is registered with the {@link TransportManager}. Can be set + * using {@link TransportManager#setOnTransportRegisteredListener(OnTransportRegisteredListener)}. + */ +@FunctionalInterface +public interface OnTransportRegisteredListener { + /** + * Called when a transport is successfully registered. + * @param transportName The name of the transport. + * @param transportDirName The dir name of the transport. + */ + public void onTransportRegistered(String transportName, String transportDirName); +} diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java index 7bd9111028cb..bd4a0bb57072 100644 --- a/services/backup/java/com/android/server/backup/transport/TransportClient.java +++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java @@ -236,7 +236,7 @@ public class TransportClient { mBindIntent, mConnection, Context.BIND_AUTO_CREATE, - TransportManager.createSystemUserHandle()); + UserHandle.SYSTEM); if (hasBound) { // We don't need to set a time-out because we are guaranteed to get a call // back in ServiceConnection, either an onServiceConnected() or diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 63ee9fa7ec95..77521df7e06a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -226,7 +226,11 @@ public class ConnectivityService extends IConnectivityManager.Stub @GuardedBy("mVpns") private final SparseArray<Vpn> mVpns = new SparseArray<Vpn>(); + // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by + // a direct call to LockdownVpnTracker.isEnabled(). + @GuardedBy("mVpns") private boolean mLockdownEnabled; + @GuardedBy("mVpns") private LockdownVpnTracker mLockdownTracker; final private Context mContext; @@ -997,9 +1001,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } private Network[] getVpnUnderlyingNetworks(int uid) { - if (!mLockdownEnabled) { - int user = UserHandle.getUserId(uid); - synchronized (mVpns) { + synchronized (mVpns) { + if (!mLockdownEnabled) { + int user = UserHandle.getUserId(uid); Vpn vpn = mVpns.get(user); if (vpn != null && vpn.appliesToUid(uid)) { return vpn.getUnderlyingNetworks(); @@ -1087,8 +1091,10 @@ public class ConnectivityService extends IConnectivityManager.Stub if (isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid, ignoreBlocked)) { state.networkInfo.setDetailedState(DetailedState.BLOCKED, null, null); } - if (mLockdownTracker != null) { - mLockdownTracker.augmentNetworkInfo(state.networkInfo); + synchronized (mVpns) { + if (mLockdownTracker != null) { + mLockdownTracker.augmentNetworkInfo(state.networkInfo); + } } } @@ -1253,8 +1259,8 @@ public class ConnectivityService extends IConnectivityManager.Stub result.put(nai.network, nc); } - if (!mLockdownEnabled) { - synchronized (mVpns) { + synchronized (mVpns) { + if (!mLockdownEnabled) { Vpn vpn = mVpns.get(userId); if (vpn != null) { Network[] networks = vpn.getUnderlyingNetworks(); @@ -1580,9 +1586,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } private Intent makeGeneralIntent(NetworkInfo info, String bcastType) { - if (mLockdownTracker != null) { - info = new NetworkInfo(info); - mLockdownTracker.augmentNetworkInfo(info); + synchronized (mVpns) { + if (mLockdownTracker != null) { + info = new NetworkInfo(info); + mLockdownTracker.augmentNetworkInfo(info); + } } Intent intent = new Intent(bcastType); @@ -2500,6 +2508,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleRemoveNetworkRequest(final NetworkRequestInfo nri) { nri.unlinkDeathRecipient(); mNetworkRequests.remove(nri.request); + synchronized (mUidToNetworkRequestCount) { int requests = mUidToNetworkRequestCount.get(nri.mUid, 0); if (requests < 1) { @@ -2512,6 +2521,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mUidToNetworkRequestCount.put(nri.mUid, requests - 1); } } + mNetworkRequestInfoLogs.log("RELEASE " + nri); if (nri.request.isRequest()) { boolean wasKept = false; @@ -3434,9 +3444,9 @@ public class ConnectivityService extends IConnectivityManager.Stub public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage, int userId) { enforceCrossUserPermission(userId); - throwIfLockdownEnabled(); synchronized (mVpns) { + throwIfLockdownEnabled(); Vpn vpn = mVpns.get(userId); if (vpn != null) { return vpn.prepare(oldPackage, newPackage); @@ -3480,9 +3490,9 @@ public class ConnectivityService extends IConnectivityManager.Stub */ @Override public ParcelFileDescriptor establishVpn(VpnConfig config) { - throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { + throwIfLockdownEnabled(); return mVpns.get(user).establish(config); } } @@ -3493,13 +3503,13 @@ public class ConnectivityService extends IConnectivityManager.Stub */ @Override public void startLegacyVpn(VpnProfile profile) { - throwIfLockdownEnabled(); + int user = UserHandle.getUserId(Binder.getCallingUid()); final LinkProperties egress = getActiveLinkProperties(); if (egress == null) { throw new IllegalStateException("Missing active network connection"); } - int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { + throwIfLockdownEnabled(); mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress); } } @@ -3525,11 +3535,11 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public VpnInfo[] getAllVpnInfo() { enforceConnectivityInternalPermission(); - if (mLockdownEnabled) { - return new VpnInfo[0]; - } - synchronized (mVpns) { + if (mLockdownEnabled) { + return new VpnInfo[0]; + } + List<VpnInfo> infoList = new ArrayList<>(); for (int i = 0; i < mVpns.size(); i++) { VpnInfo info = createVpnInfo(mVpns.valueAt(i)); @@ -3594,33 +3604,33 @@ public class ConnectivityService extends IConnectivityManager.Stub return false; } - // Tear down existing lockdown if profile was removed - mLockdownEnabled = LockdownVpnTracker.isEnabled(); - if (mLockdownEnabled) { - byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); - if (profileTag == null) { - Slog.e(TAG, "Lockdown VPN configured but cannot be read from keystore"); - return false; - } - String profileName = new String(profileTag); - final VpnProfile profile = VpnProfile.decode( - profileName, mKeyStore.get(Credentials.VPN + profileName)); - if (profile == null) { - Slog.e(TAG, "Lockdown VPN configured invalid profile " + profileName); - setLockdownTracker(null); - return true; - } - int user = UserHandle.getUserId(Binder.getCallingUid()); - synchronized (mVpns) { + synchronized (mVpns) { + // Tear down existing lockdown if profile was removed + mLockdownEnabled = LockdownVpnTracker.isEnabled(); + if (mLockdownEnabled) { + byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); + if (profileTag == null) { + Slog.e(TAG, "Lockdown VPN configured but cannot be read from keystore"); + return false; + } + String profileName = new String(profileTag); + final VpnProfile profile = VpnProfile.decode( + profileName, mKeyStore.get(Credentials.VPN + profileName)); + if (profile == null) { + Slog.e(TAG, "Lockdown VPN configured invalid profile " + profileName); + setLockdownTracker(null); + return true; + } + int user = UserHandle.getUserId(Binder.getCallingUid()); Vpn vpn = mVpns.get(user); if (vpn == null) { Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown"); return false; } setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, vpn, profile)); + } else { + setLockdownTracker(null); } - } else { - setLockdownTracker(null); } return true; @@ -3630,6 +3640,7 @@ public class ConnectivityService extends IConnectivityManager.Stub * Internally set new {@link LockdownVpnTracker}, shutting down any existing * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown. */ + @GuardedBy("mVpns") private void setLockdownTracker(LockdownVpnTracker tracker) { // Shutdown any existing tracker final LockdownVpnTracker existing = mLockdownTracker; @@ -3644,6 +3655,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + @GuardedBy("mVpns") private void throwIfLockdownEnabled() { if (mLockdownEnabled) { throw new IllegalStateException("Unavailable in lockdown mode"); @@ -3691,12 +3703,12 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceConnectivityInternalPermission(); enforceCrossUserPermission(userId); - // Can't set always-on VPN if legacy VPN is already in lockdown mode. - if (LockdownVpnTracker.isEnabled()) { - return false; - } - synchronized (mVpns) { + // Can't set always-on VPN if legacy VPN is already in lockdown mode. + if (LockdownVpnTracker.isEnabled()) { + return false; + } + Vpn vpn = mVpns.get(userId); if (vpn == null) { Slog.w(TAG, "User " + userId + " has no Vpn configuration"); @@ -3872,9 +3884,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId); mVpns.put(userId, userVpn); - } - if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { - updateLockdownVpn(); + if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { + updateLockdownVpn(); + } } } @@ -3911,11 +3923,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void onUserUnlocked(int userId) { - // User present may be sent because of an unlock, which might mean an unlocked keystore. - if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { - updateLockdownVpn(); - } else { - startAlwaysOnVpn(userId); + synchronized (mVpns) { + // User present may be sent because of an unlock, which might mean an unlocked keystore. + if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { + updateLockdownVpn(); + } else { + startAlwaysOnVpn(userId); + } } } @@ -4595,51 +4609,67 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Update the NetworkCapabilities for {@code networkAgent} to {@code networkCapabilities} - * augmented with any stateful capabilities implied from {@code networkAgent} - * (e.g., validated status and captive portal status). - * - * @param oldScore score of the network before any of the changes that prompted us - * to call this function. - * @param nai the network having its capabilities updated. - * @param networkCapabilities the new network capabilities. + * Augments the NetworkCapabilities passed in by a NetworkAgent with capabilities that are + * maintained here that the NetworkAgent is not aware of (e.g., validated, captive portal, + * and foreground status). */ - private void updateCapabilities( - int oldScore, NetworkAgentInfo nai, NetworkCapabilities networkCapabilities) { + private NetworkCapabilities mixInCapabilities(NetworkAgentInfo nai, NetworkCapabilities nc) { // Once a NetworkAgent is connected, complain if some immutable capabilities are removed. - if (nai.everConnected && !nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities( - networkCapabilities)) { - // TODO: consider not complaining when a network agent degrade its capabilities if this + if (nai.everConnected && + !nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities(nc)) { + // TODO: consider not complaining when a network agent degrades its capabilities if this // does not cause any request (that is not a listen) currently matching that agent to // stop being matched by the updated agent. - String diff = nai.networkCapabilities.describeImmutableDifferences(networkCapabilities); + String diff = nai.networkCapabilities.describeImmutableDifferences(nc); if (!TextUtils.isEmpty(diff)) { Slog.wtf(TAG, "BUG: " + nai + " lost immutable capabilities:" + diff); } } // Don't modify caller's NetworkCapabilities. - networkCapabilities = new NetworkCapabilities(networkCapabilities); + NetworkCapabilities newNc = new NetworkCapabilities(nc); if (nai.lastValidated) { - networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED); + newNc.addCapability(NET_CAPABILITY_VALIDATED); } else { - networkCapabilities.removeCapability(NET_CAPABILITY_VALIDATED); + newNc.removeCapability(NET_CAPABILITY_VALIDATED); } if (nai.lastCaptivePortalDetected) { - networkCapabilities.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL); + newNc.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL); } else { - networkCapabilities.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL); + newNc.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL); } if (nai.isBackgroundNetwork()) { - networkCapabilities.removeCapability(NET_CAPABILITY_FOREGROUND); + newNc.removeCapability(NET_CAPABILITY_FOREGROUND); } else { - networkCapabilities.addCapability(NET_CAPABILITY_FOREGROUND); + newNc.addCapability(NET_CAPABILITY_FOREGROUND); } - if (Objects.equals(nai.networkCapabilities, networkCapabilities)) return; + return newNc; + } + + /** + * Update the NetworkCapabilities for {@code nai} to {@code nc}. Specifically: + * + * 1. Calls mixInCapabilities to merge the passed-in NetworkCapabilities {@code nc} with the + * capabilities we manage and store in {@code nai}, such as validated status and captive + * portal status) + * 2. Takes action on the result: changes network permissions, sends CAP_CHANGED callbacks, and + * potentially triggers rematches. + * 3. Directly informs other network stack components (NetworkStatsService, VPNs, etc. of the + * change.) + * + * @param oldScore score of the network before any of the changes that prompted us + * to call this function. + * @param nai the network having its capabilities updated. + * @param nc the new network capabilities. + */ + private void updateCapabilities(int oldScore, NetworkAgentInfo nai, NetworkCapabilities nc) { + NetworkCapabilities newNc = mixInCapabilities(nai, nc); + + if (Objects.equals(nai.networkCapabilities, newNc)) return; final String oldPermission = getNetworkPermission(nai.networkCapabilities); - final String newPermission = getNetworkPermission(networkCapabilities); + final String newPermission = getNetworkPermission(newNc); if (!Objects.equals(oldPermission, newPermission) && nai.created && !nai.isVPN()) { try { mNetd.setNetworkPermission(nai.network.netId, newPermission); @@ -4651,11 +4681,10 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkCapabilities prevNc; synchronized (nai) { prevNc = nai.networkCapabilities; - nai.networkCapabilities = networkCapabilities; + nai.networkCapabilities = newNc; } - if (nai.getCurrentScore() == oldScore && - networkCapabilities.equalRequestableCapabilities(prevNc)) { + if (nai.getCurrentScore() == oldScore && newNc.equalRequestableCapabilities(prevNc)) { // If the requestable capabilities haven't changed, and the score hasn't changed, then // the change we're processing can't affect any requests, it can only affect the listens // on this network. We might have been called by rematchNetworkAndRequests when a @@ -4671,15 +4700,15 @@ public class ConnectivityService extends IConnectivityManager.Stub // Report changes that are interesting for network statistics tracking. if (prevNc != null) { final boolean meteredChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_METERED) != - networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED); + newNc.hasCapability(NET_CAPABILITY_NOT_METERED); final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) != - networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING); + newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); if (meteredChanged || roamingChanged) { notifyIfacesChangedForNetworkStats(); } } - if (!networkCapabilities.hasTransport(TRANSPORT_VPN)) { + if (!newNc.hasTransport(TRANSPORT_VPN)) { // Tell VPNs about updated capabilities, since they may need to // bubble those changes through. synchronized (mVpns) { @@ -5203,11 +5232,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void notifyLockdownVpn(NetworkAgentInfo nai) { - if (mLockdownTracker != null) { - if (nai != null && nai.isVPN()) { - mLockdownTracker.onVpnStateChanged(nai.networkInfo); - } else { - mLockdownTracker.onNetworkInfoChanged(); + synchronized (mVpns) { + if (mLockdownTracker != null) { + if (nai != null && nai.isVPN()) { + mLockdownTracker.onVpnStateChanged(nai.networkInfo); + } else { + mLockdownTracker.onNetworkInfoChanged(); + } } } } @@ -5437,28 +5468,28 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public boolean addVpnAddress(String address, int prefixLength) { - throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { + throwIfLockdownEnabled(); return mVpns.get(user).addAddress(address, prefixLength); } } @Override public boolean removeVpnAddress(String address, int prefixLength) { - throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { + throwIfLockdownEnabled(); return mVpns.get(user).removeAddress(address, prefixLength); } } @Override public boolean setUnderlyingNetworksForVpn(Network[] networks) { - throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); - boolean success; + final boolean success; synchronized (mVpns) { + throwIfLockdownEnabled(); success = mVpns.get(user).setUnderlyingNetworks(networks); } if (success) { @@ -5518,31 +5549,31 @@ public class ConnectivityService extends IConnectivityManager.Stub setAlwaysOnVpnPackage(userId, null, false); setVpnPackageAuthorization(alwaysOnPackage, userId, false); } - } - // Turn Always-on VPN off - if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { - final long ident = Binder.clearCallingIdentity(); - try { - mKeyStore.delete(Credentials.LOCKDOWN_VPN); - mLockdownEnabled = false; - setLockdownTracker(null); - } finally { - Binder.restoreCallingIdentity(ident); + // Turn Always-on VPN off + if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { + final long ident = Binder.clearCallingIdentity(); + try { + mKeyStore.delete(Credentials.LOCKDOWN_VPN); + mLockdownEnabled = false; + setLockdownTracker(null); + } finally { + Binder.restoreCallingIdentity(ident); + } } - } - // Turn VPN off - VpnConfig vpnConfig = getVpnConfig(userId); - if (vpnConfig != null) { - if (vpnConfig.legacy) { - prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId); - } else { - // Prevent this app (packagename = vpnConfig.user) from initiating VPN connections - // in the future without user intervention. - setVpnPackageAuthorization(vpnConfig.user, userId, false); + // Turn VPN off + VpnConfig vpnConfig = getVpnConfig(userId); + if (vpnConfig != null) { + if (vpnConfig.legacy) { + prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId); + } else { + // Prevent this app (packagename = vpnConfig.user) from initiating + // VPN connections in the future without user intervention. + setVpnPackageAuthorization(vpnConfig.user, userId, false); - prepareVpn(null, VpnConfig.LEGACY_VPN, userId); + prepareVpn(null, VpnConfig.LEGACY_VPN, userId); + } } } } diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 40e6d2645b69..8f646e75862c 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -2508,12 +2508,16 @@ public class NetworkManagementService extends INetworkManagementService.Stub @Override public void removeNetwork(int netId) { - mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG); try { - mConnector.execute("network", "destroy", netId); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + mNetdService.networkDestroy(netId); + } catch (ServiceSpecificException e) { + Log.w(TAG, "removeNetwork(" + netId + "): ", e); + throw e; + } catch (RemoteException e) { + Log.w(TAG, "removeNetwork(" + netId + "): ", e); + throw e.rethrowAsRuntimeException(); } } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 831c9cbc2ef5..6747be340d46 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -147,6 +147,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private int[] mDataActivationState; + private boolean[] mUserMobileDataState; + private SignalStrength[] mSignalStrength; private boolean[] mMessageWaiting; @@ -304,6 +306,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mServiceState = new ServiceState[numPhones]; mVoiceActivationState = new int[numPhones]; mDataActivationState = new int[numPhones]; + mUserMobileDataState = new boolean[numPhones]; mSignalStrength = new SignalStrength[numPhones]; mMessageWaiting = new boolean[numPhones]; mCallForwarding = new boolean[numPhones]; @@ -320,6 +323,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mCallIncomingNumber[i] = ""; mServiceState[i] = new ServiceState(); mSignalStrength[i] = new SignalStrength(); + mUserMobileDataState[i] = false; mMessageWaiting[i] = false; mCallForwarding[i] = false; mCellLocation[i] = new Bundle(); @@ -656,6 +660,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } + if ((events & PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) != 0) { + try { + r.callback.onUserMobileDataStateChanged(mUserMobileDataState[phoneId]); + } catch (RemoteException ex) { + remove(r.binder); + } + } } } } else { @@ -1012,6 +1023,33 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + public void notifyUserMobileDataStateChangedForPhoneId(int phoneId, int subId, boolean state) { + if (!checkNotifyPermission("notifyUserMobileDataStateChanged()")) { + return; + } + if (VDBG) { + log("notifyUserMobileDataStateChangedForSubscriberPhoneID: subId=" + phoneId + + " state=" + state); + } + synchronized (mRecords) { + if (validatePhoneId(phoneId)) { + mMessageWaiting[phoneId] = state; + for (Record r : mRecords) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) && + idMatch(r.subId, subId, phoneId)) { + try { + r.callback.onUserMobileDataStateChanged(state); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + } + handleRemoveListLocked(); + } + } + public void notifyCallForwardingChanged(boolean cfi) { notifyCallForwardingChangedForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cfi); } @@ -1374,6 +1412,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("mServiceState=" + mServiceState[i]); pw.println("mVoiceActivationState= " + mVoiceActivationState[i]); pw.println("mDataActivationState= " + mDataActivationState[i]); + pw.println("mUserMobileDataState= " + mUserMobileDataState[i]); pw.println("mSignalStrength=" + mSignalStrength[i]); pw.println("mMessageWaiting=" + mMessageWaiting[i]); pw.println("mCallForwarding=" + mCallForwarding[i]); @@ -1755,6 +1794,18 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + if ((events & PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) != 0) { + try { + if (VDBG) { + log("checkPossibleMissNotify: onUserMobileDataStateChanged phoneId=" + + phoneId + " umds=" + mUserMobileDataState[phoneId]); + } + r.callback.onUserMobileDataStateChanged(mUserMobileDataState[phoneId]); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + if ((events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) { try { if (VDBG) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 2bf8b7c05f11..c9b9a4038d4e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -25130,6 +25130,11 @@ public class ActivityManagerService extends IActivityManager.Stub public void registerScreenObserver(ScreenObserver observer) { mScreenObservers.add(observer); } + + @Override + public boolean canStartMoreUsers() { + return mUserController.canStartMoreUsers(); + } } /** @@ -25340,6 +25345,10 @@ public class ActivityManagerService extends IActivityManager.Stub } } } + if (updateFrameworkRes && mWindowManager != null) { + ActivityThread.currentActivityThread().getExecutor().execute( + mWindowManager::onOverlayChanged); + } } /** diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index c4fdffa0f67a..a327a018bc25 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -101,6 +101,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; @@ -249,39 +250,51 @@ class UserController implements Handler.Callback { } } - void stopRunningUsersLU(int maxRunningUsers) { - int currentlyRunning = mUserLru.size(); - int i = 0; - while (currentlyRunning > maxRunningUsers && i < mUserLru.size()) { - Integer oldUserId = mUserLru.get(i); - UserState oldUss = mStartedUsers.get(oldUserId); - if (oldUss == null) { + List<Integer> getRunningUsersLU() { + ArrayList<Integer> runningUsers = new ArrayList<>(); + for (Integer userId : mUserLru) { + UserState uss = mStartedUsers.get(userId); + if (uss == null) { // Shouldn't happen, but be sane if it does. - mUserLru.remove(i); - currentlyRunning--; continue; } - if (oldUss.state == UserState.STATE_STOPPING - || oldUss.state == UserState.STATE_SHUTDOWN) { + if (uss.state == UserState.STATE_STOPPING + || uss.state == UserState.STATE_SHUTDOWN) { // This user is already stopping, doesn't count. - currentlyRunning--; - i++; continue; } - if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId) { - // Owner/System user and current user can't be stopped. We count it as running - // when it is not a pure system user. - if (UserInfo.isSystemOnly(oldUserId)) { - currentlyRunning--; + if (userId == UserHandle.USER_SYSTEM) { + // We only count system user as running when it is not a pure system user. + if (UserInfo.isSystemOnly(userId)) { + continue; } - i++; + } + runningUsers.add(userId); + } + return runningUsers; + } + + void stopRunningUsersLU(int maxRunningUsers) { + List<Integer> currentlyRunning = getRunningUsersLU(); + Iterator<Integer> iterator = currentlyRunning.iterator(); + while (currentlyRunning.size() > maxRunningUsers && iterator.hasNext()) { + Integer userId = iterator.next(); + if (userId == UserHandle.USER_SYSTEM || userId == mCurrentUserId) { + // Owner/System user and current user can't be stopped continue; } - // This is a user to be stopped. - if (stopUsersLU(oldUserId, false, null) == USER_OP_SUCCESS) { - currentlyRunning--; + if (stopUsersLU(userId, false, null) == USER_OP_SUCCESS) { + iterator.remove(); } - i++; + } + } + + /** + * Returns if more users can be started without stopping currently running users. + */ + boolean canStartMoreUsers() { + synchronized (mLock) { + return getRunningUsersLU().size() < mMaxRunningUsers; } } @@ -768,34 +781,23 @@ class UserController implements Handler.Callback { /** * Stops the guest or ephemeral user if it has gone to the background. */ - private void stopGuestOrEphemeralUserIfBackground() { - IntArray userIds = new IntArray(); - synchronized (mLock) { - final int num = mUserLru.size(); - for (int i = 0; i < num; i++) { - Integer oldUserId = mUserLru.get(i); - UserState oldUss = mStartedUsers.get(oldUserId); - if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId - || oldUss.state == UserState.STATE_STOPPING - || oldUss.state == UserState.STATE_SHUTDOWN) { - continue; - } - userIds.add(oldUserId); - } + private void stopGuestOrEphemeralUserIfBackground(int oldUserId) { + if (DEBUG_MU) Slog.i(TAG, "Stop guest or ephemeral user if background: " + oldUserId); + UserState oldUss = mStartedUsers.get(oldUserId); + if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId + || oldUss.state == UserState.STATE_STOPPING + || oldUss.state == UserState.STATE_SHUTDOWN) { + return; } - final int userIdsSize = userIds.size(); - for (int i = 0; i < userIdsSize; i++) { - int oldUserId = userIds.get(i); - UserInfo userInfo = getUserInfo(oldUserId); - if (userInfo.isEphemeral()) { - LocalServices.getService(UserManagerInternal.class).onEphemeralUserStop(oldUserId); - } - if (userInfo.isGuest() || userInfo.isEphemeral()) { - // This is a user to be stopped. - synchronized (mLock) { - stopUsersLU(oldUserId, true, null); - } - break; + + UserInfo userInfo = getUserInfo(oldUserId); + if (userInfo.isEphemeral()) { + LocalServices.getService(UserManagerInternal.class).onEphemeralUserStop(oldUserId); + } + if (userInfo.isGuest() || userInfo.isEphemeral()) { + // This is a user to be stopped. + synchronized (mLock) { + stopUsersLU(oldUserId, true, null); } } } @@ -1333,7 +1335,7 @@ class UserController implements Handler.Callback { mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG); mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0)); - stopGuestOrEphemeralUserIfBackground(); + stopGuestOrEphemeralUserIfBackground(oldUserId); stopBackgroundUsersIfEnforced(oldUserId); } diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java index 7d3b670bdfa3..10e6cad70bcd 100644 --- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java @@ -25,6 +25,7 @@ import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; import android.hardware.radio.RadioManager; import android.os.ParcelableException; +import android.os.RemoteException; import android.util.Slog; import com.android.server.SystemService; @@ -86,7 +87,7 @@ public class BroadcastRadioService extends SystemService { @Override public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig, - boolean withAudio, ITunerCallback callback) { + boolean withAudio, ITunerCallback callback) throws RemoteException { Slog.i(TAG, "openTuner(" + moduleId + ", _, " + withAudio + ", _)"); enforcePolicyAccess(); if (callback == null) { diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java index e5090ed5b715..f9b35f53d4d5 100644 --- a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java +++ b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java @@ -21,6 +21,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; +import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.os.IBinder; @@ -249,8 +250,7 @@ class Tuner extends ITuner.Stub { } } - @Override - public List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) { + List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) { Map<String, String> sFilter = vendorFilter; synchronized (mLock) { checkNotClosedLocked(); @@ -263,6 +263,16 @@ class Tuner extends ITuner.Stub { } @Override + public void startProgramListUpdates(ProgramList.Filter filter) { + mTunerCallback.startProgramListUpdates(filter); + } + + @Override + public void stopProgramListUpdates() { + mTunerCallback.stopProgramListUpdates(); + } + + @Override public boolean isConfigFlagSupported(int flag) { return flag == RadioManager.CONFIG_FORCE_ANALOG; } diff --git a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java index 673ff88d5c98..18f56ed58475 100644 --- a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java +++ b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java @@ -19,6 +19,7 @@ package com.android.server.broadcastradio.hal1; import android.annotation.NonNull; import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; +import android.hardware.radio.ProgramList; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; import android.hardware.radio.RadioTuner; @@ -28,6 +29,10 @@ import android.util.Slog; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; class TunerCallback implements ITunerCallback { private static final String TAG = "BroadcastRadioService.TunerCallback"; @@ -40,6 +45,8 @@ class TunerCallback implements ITunerCallback { @NonNull private final Tuner mTuner; @NonNull private final ITunerCallback mClientCallback; + private final AtomicReference<ProgramList.Filter> mProgramListFilter = new AtomicReference<>(); + TunerCallback(@NonNull Tuner tuner, @NonNull ITunerCallback clientCallback, int halRev) { mTuner = tuner; mClientCallback = clientCallback; @@ -78,6 +85,15 @@ class TunerCallback implements ITunerCallback { mTuner.close(); } + void startProgramListUpdates(@NonNull ProgramList.Filter filter) { + mProgramListFilter.set(Objects.requireNonNull(filter)); + sendProgramListUpdate(); + } + + void stopProgramListUpdates() { + mProgramListFilter.set(null); + } + @Override public void onError(int status) { dispatch(() -> mClientCallback.onError(status)); @@ -121,6 +137,28 @@ class TunerCallback implements ITunerCallback { @Override public void onProgramListChanged() { dispatch(() -> mClientCallback.onProgramListChanged()); + sendProgramListUpdate(); + } + + private void sendProgramListUpdate() { + ProgramList.Filter filter = mProgramListFilter.get(); + if (filter == null) return; + + List<RadioManager.ProgramInfo> modified; + try { + modified = mTuner.getProgramList(filter.getVendorFilter()); + } catch (IllegalStateException ex) { + Slog.d(TAG, "Program list not ready yet"); + return; + } + Set<RadioManager.ProgramInfo> modifiedSet = modified.stream().collect(Collectors.toSet()); + ProgramList.Chunk chunk = new ProgramList.Chunk(true, true, modifiedSet, null); + dispatch(() -> mClientCallback.onProgramListUpdated(chunk)); + } + + @Override + public void onProgramListUpdated(ProgramList.Chunk chunk) { + dispatch(() -> mClientCallback.onProgramListUpdated(chunk)); } @Override diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java index 9158ff0cb66f..fc9a5d61704d 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java @@ -82,7 +82,7 @@ public class BroadcastRadioService { } public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig, - boolean withAudio, @NonNull ITunerCallback callback) { + boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException { Objects.requireNonNull(callback); if (!withAudio) { diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java index 2c129bbac33e..60a927c56959 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java @@ -20,15 +20,22 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.broadcastradio.V2_0.AmFmBandRange; import android.hardware.broadcastradio.V2_0.AmFmRegionConfig; +import android.hardware.broadcastradio.V2_0.ProgramFilter; +import android.hardware.broadcastradio.V2_0.ProgramIdentifier; +import android.hardware.broadcastradio.V2_0.ProgramInfo; +import android.hardware.broadcastradio.V2_0.ProgramInfoFlags; +import android.hardware.broadcastradio.V2_0.ProgramListChunk; import android.hardware.broadcastradio.V2_0.Properties; import android.hardware.broadcastradio.V2_0.Result; import android.hardware.broadcastradio.V2_0.VendorKeyValue; +import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.os.ParcelableException; import android.util.Slog; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -36,6 +43,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; class Convert { private static final String TAG = "BcRadio2Srv.convert"; @@ -78,43 +86,52 @@ class Convert { return map; } + private static @ProgramSelector.ProgramType int identifierTypeToProgramType( + @ProgramSelector.IdentifierType int idType) { + switch (idType) { + case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY: + case ProgramSelector.IDENTIFIER_TYPE_RDS_PI: + // TODO(b/69958423): verify AM/FM with frequency range + return ProgramSelector.PROGRAM_TYPE_FM; + case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT: + // TODO(b/69958423): verify AM/FM with frequency range + return ProgramSelector.PROGRAM_TYPE_FM_HD; + case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC: + case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE: + case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID: + case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY: + return ProgramSelector.PROGRAM_TYPE_DAB; + case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID: + case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY: + return ProgramSelector.PROGRAM_TYPE_DRMO; + case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID: + case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL: + return ProgramSelector.PROGRAM_TYPE_SXM; + } + if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START + && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) { + return idType; + } + return ProgramSelector.PROGRAM_TYPE_INVALID; + } + private static @NonNull int[] identifierTypesToProgramTypes(@NonNull int[] idTypes) { Set<Integer> pTypes = new HashSet<>(); for (int idType : idTypes) { - switch (idType) { - case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY: - case ProgramSelector.IDENTIFIER_TYPE_RDS_PI: - // TODO(b/69958423): verify AM/FM with region info - pTypes.add(ProgramSelector.PROGRAM_TYPE_AM); - pTypes.add(ProgramSelector.PROGRAM_TYPE_FM); - break; - case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT: - // TODO(b/69958423): verify AM/FM with region info - pTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD); - pTypes.add(ProgramSelector.PROGRAM_TYPE_FM_HD); - break; - case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC: - case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE: - case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID: - case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY: - pTypes.add(ProgramSelector.PROGRAM_TYPE_DAB); - break; - case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID: - case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY: - pTypes.add(ProgramSelector.PROGRAM_TYPE_DRMO); - break; - case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID: - case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL: - pTypes.add(ProgramSelector.PROGRAM_TYPE_SXM); - break; - default: - break; + int pType = identifierTypeToProgramType(idType); + + if (pType == ProgramSelector.PROGRAM_TYPE_INVALID) continue; + + pTypes.add(pType); + if (pType == ProgramSelector.PROGRAM_TYPE_FM) { + // TODO(b/69958423): verify AM/FM with region info + pTypes.add(ProgramSelector.PROGRAM_TYPE_AM); } - if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START - && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) { - pTypes.add(idType); + if (pType == ProgramSelector.PROGRAM_TYPE_FM_HD) { + // TODO(b/69958423): verify AM/FM with region info + pTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD); } } @@ -189,6 +206,64 @@ class Convert { false, // isBgScanSupported is deprecated supportedProgramTypes, supportedIdentifierTypes, - vendorInfoFromHal(prop.vendorInfo)); + vendorInfoFromHal(prop.vendorInfo) + ); + } + + static @NonNull ProgramIdentifier programIdentifierToHal( + @NonNull ProgramSelector.Identifier id) { + ProgramIdentifier hwId = new ProgramIdentifier(); + hwId.type = id.getType(); + hwId.value = id.getValue(); + return hwId; + } + + static @NonNull ProgramSelector.Identifier programIdentifierFromHal(@NonNull ProgramIdentifier id) { + return new ProgramSelector.Identifier(id.type, id.value); + } + + static @NonNull ProgramSelector programSelectorFromHal( + @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) { + ProgramSelector.Identifier[] secondaryIds = sel.secondaryIds.stream().map( + id -> programIdentifierFromHal(id)).toArray(ProgramSelector.Identifier[]::new); + + return new ProgramSelector( + identifierTypeToProgramType(sel.primaryId.type), + programIdentifierFromHal(sel.primaryId), + secondaryIds, null); + } + + static @NonNull RadioManager.ProgramInfo programInfoFromHal(@NonNull ProgramInfo info) { + return new RadioManager.ProgramInfo( + programSelectorFromHal(info.selector), + (info.infoFlags & ProgramInfoFlags.TUNED) != 0, + (info.infoFlags & ProgramInfoFlags.STEREO) != 0, + false, // TODO(b/69860743): digital + info.signalQuality, + null, // TODO(b/69860743): metadata + info.infoFlags, + vendorInfoFromHal(info.vendorInfo) + ); + } + + static @NonNull ProgramFilter programFilterToHal(@NonNull ProgramList.Filter filter) { + ProgramFilter hwFilter = new ProgramFilter(); + + filter.getIdentifierTypes().stream().forEachOrdered(hwFilter.identifierTypes::add); + filter.getIdentifiers().stream().forEachOrdered( + id -> hwFilter.identifiers.add(programIdentifierToHal(id))); + hwFilter.includeCategories = filter.areCategoriesIncluded(); + hwFilter.excludeModifications = filter.areModificationsExcluded(); + + return hwFilter; + } + + static @NonNull ProgramList.Chunk programListChunkFromHal(@NonNull ProgramListChunk chunk) { + Set<RadioManager.ProgramInfo> modified = chunk.modified.stream().map( + info -> programInfoFromHal(info)).collect(Collectors.toSet()); + Set<ProgramSelector.Identifier> removed = chunk.removed.stream().map( + id -> programIdentifierFromHal(id)).collect(Collectors.toSet()); + + return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed); } } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java index 45b21900ff33..c8e15c1a2f4f 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java @@ -63,20 +63,16 @@ class RadioModule { } } - public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb) { + public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb) + throws RemoteException { TunerCallback cb = new TunerCallback(Objects.requireNonNull(userCb)); Mutable<ITunerSession> hwSession = new Mutable<>(); MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR); - try { - mService.openSession(cb, (int result, ITunerSession session) -> { - hwSession.value = session; - halResult.value = result; - }); - } catch (RemoteException ex) { - Slog.e(TAG, "failed to open session", ex); - throw new ParcelableException(ex); - } + mService.openSession(cb, (int result, ITunerSession session) -> { + hwSession.value = session; + halResult.value = result; + }); Convert.throwOnError("openSession", halResult.value); Objects.requireNonNull(hwSession.value); diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java index c9084ee9517a..ed2a1b3ce851 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java @@ -56,7 +56,9 @@ class TunerCallback extends ITunerCallback.Stub { public void onCurrentProgramInfoChanged(ProgramInfo info) {} @Override - public void onProgramListUpdated(ProgramListChunk chunk) {} + public void onProgramListUpdated(ProgramListChunk chunk) { + dispatch(() -> mClientCb.onProgramListUpdated(Convert.programListChunkFromHal(chunk))); + } @Override public void onAntennaStateChange(boolean connected) {} diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java index 8ed646af2b88..e093c9df7c4b 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java @@ -22,6 +22,7 @@ import android.hardware.broadcastradio.V2_0.ConfigFlag; import android.hardware.broadcastradio.V2_0.ITunerSession; import android.hardware.broadcastradio.V2_0.Result; import android.hardware.radio.ITuner; +import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.media.AudioSystem; @@ -184,10 +185,19 @@ class TunerSession extends ITuner.Stub { } @Override - public List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) { + public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException { synchronized (mLock) { checkNotClosedLocked(); - return null; + int halResult = mHwSession.startProgramListUpdates(Convert.programFilterToHal(filter)); + Convert.throwOnError("startProgramListUpdates", halResult); + } + } + + @Override + public void stopProgramListUpdates() throws RemoteException { + synchronized (mLock) { + checkNotClosedLocked(); + mHwSession.stopProgramListUpdates(); } } @@ -226,17 +236,12 @@ class TunerSession extends ITuner.Stub { } @Override - public void setConfigFlag(int flag, boolean value) { + public void setConfigFlag(int flag, boolean value) throws RemoteException { Slog.v(TAG, "setConfigFlag " + ConfigFlag.toString(flag) + " = " + value); synchronized (mLock) { checkNotClosedLocked(); - int halResult; - try { - halResult = mHwSession.setConfigFlag(flag, value); - } catch (RemoteException ex) { - throw new RuntimeException("Failed to set flag " + ConfigFlag.toString(flag), ex); - } + int halResult = mHwSession.setConfigFlag(flag, value); Convert.throwOnError("setConfigFlag", halResult); } } diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java index cbb1c0139bca..1e94e00a09a9 100644 --- a/services/core/java/com/android/server/display/BrightnessTracker.java +++ b/services/core/java/com/android/server/display/BrightnessTracker.java @@ -149,7 +149,7 @@ public class BrightnessTracker { /** * Start listening for brightness slider events * - * @param brightness the initial screen brightness + * @param initialBrightness the initial screen brightness */ public void start(float initialBrightness) { if (DEBUG) { @@ -219,8 +219,8 @@ public class BrightnessTracker { if (includePackage) { out.add(events[i]); } else { - BrightnessChangeEvent event = new BrightnessChangeEvent((events[i])); - event.packageName = null; + BrightnessChangeEvent event = new BrightnessChangeEvent((events[i]), + /* redactPackage */ true); out.add(event); } } @@ -246,7 +246,8 @@ public class BrightnessTracker { } private void handleBrightnessChanged(float brightness, boolean userInitiated) { - final BrightnessChangeEvent event; + BrightnessChangeEvent.Builder builder; + synchronized (mDataCollectionLock) { if (!mStarted) { // Not currently gathering brightness change information @@ -263,9 +264,9 @@ public class BrightnessTracker { return; } - - event = new BrightnessChangeEvent(); - event.timeStamp = mInjector.currentTimeMillis(); + builder = new BrightnessChangeEvent.Builder(); + builder.setBrightness(brightness); + builder.setTimeStamp(mInjector.currentTimeMillis()); final int readingCount = mLastSensorReadings.size(); if (readingCount == 0) { @@ -273,8 +274,8 @@ public class BrightnessTracker { return; } - event.luxValues = new float[readingCount]; - event.luxTimestamps = new long[readingCount]; + float[] luxValues = new float[readingCount]; + long[] luxTimestamps = new long[readingCount]; int pos = 0; @@ -282,33 +283,35 @@ public class BrightnessTracker { long currentTimeMillis = mInjector.currentTimeMillis(); long elapsedTimeNanos = mInjector.elapsedRealtimeNanos(); for (LightData reading : mLastSensorReadings) { - event.luxValues[pos] = reading.lux; - event.luxTimestamps[pos] = currentTimeMillis - + luxValues[pos] = reading.lux; + luxTimestamps[pos] = currentTimeMillis - TimeUnit.NANOSECONDS.toMillis(elapsedTimeNanos - reading.timestamp); ++pos; } + builder.setLuxValues(luxValues); + builder.setLuxTimestamps(luxTimestamps); - event.batteryLevel = mLastBatteryLevel; - event.lastBrightness = previousBrightness; + builder.setBatteryLevel(mLastBatteryLevel); + builder.setLastBrightness(previousBrightness); } - event.brightness = brightness; - try { final ActivityManager.StackInfo focusedStack = mInjector.getFocusedStack(); - event.userId = focusedStack.userId; - event.packageName = focusedStack.topActivity.getPackageName(); + builder.setUserId(focusedStack.userId); + builder.setPackageName(focusedStack.topActivity.getPackageName()); } catch (RemoteException e) { // Really shouldn't be possible. + return; } - event.nightMode = mInjector.getSecureIntForUser(mContentResolver, + builder.setNightMode(mInjector.getSecureIntForUser(mContentResolver, Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 0, UserHandle.USER_CURRENT) - == 1; - event.colorTemperature = mInjector.getSecureIntForUser(mContentResolver, + == 1); + builder.setColorTemperature(mInjector.getSecureIntForUser(mContentResolver, Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, - 0, UserHandle.USER_CURRENT); + 0, UserHandle.USER_CURRENT)); + BrightnessChangeEvent event = builder.build(); if (DEBUG) { Slog.d(TAG, "Event " + event.brightness + " " + event.packageName); } @@ -457,40 +460,43 @@ public class BrightnessTracker { } tag = parser.getName(); if (TAG_EVENT.equals(tag)) { - BrightnessChangeEvent event = new BrightnessChangeEvent(); + BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder(); String brightness = parser.getAttributeValue(null, ATTR_NITS); - event.brightness = Float.parseFloat(brightness); + builder.setBrightness(Float.parseFloat(brightness)); String timestamp = parser.getAttributeValue(null, ATTR_TIMESTAMP); - event.timeStamp = Long.parseLong(timestamp); - event.packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); + builder.setTimeStamp(Long.parseLong(timestamp)); + builder.setPackageName(parser.getAttributeValue(null, ATTR_PACKAGE_NAME)); String user = parser.getAttributeValue(null, ATTR_USER); - event.userId = mInjector.getUserId(mUserManager, Integer.parseInt(user)); + builder.setUserId(mInjector.getUserId(mUserManager, Integer.parseInt(user))); String batteryLevel = parser.getAttributeValue(null, ATTR_BATTERY_LEVEL); - event.batteryLevel = Float.parseFloat(batteryLevel); + builder.setBatteryLevel(Float.parseFloat(batteryLevel)); String nightMode = parser.getAttributeValue(null, ATTR_NIGHT_MODE); - event.nightMode = Boolean.parseBoolean(nightMode); + builder.setNightMode(Boolean.parseBoolean(nightMode)); String colorTemperature = parser.getAttributeValue(null, ATTR_COLOR_TEMPERATURE); - event.colorTemperature = Integer.parseInt(colorTemperature); + builder.setColorTemperature(Integer.parseInt(colorTemperature)); String lastBrightness = parser.getAttributeValue(null, ATTR_LAST_NITS); - event.lastBrightness = Float.parseFloat(lastBrightness); + builder.setLastBrightness(Float.parseFloat(lastBrightness)); String luxValue = parser.getAttributeValue(null, ATTR_LUX); String luxTimestamp = parser.getAttributeValue(null, ATTR_LUX_TIMESTAMPS); - String[] luxValues = luxValue.split(","); - String[] luxTimestamps = luxTimestamp.split(","); - if (luxValues.length != luxTimestamps.length) { + String[] luxValuesStrings = luxValue.split(","); + String[] luxTimestampsStrings = luxTimestamp.split(","); + if (luxValuesStrings.length != luxTimestampsStrings.length) { continue; } - event.luxValues = new float[luxValues.length]; - event.luxTimestamps = new long[luxValues.length]; + float[] luxValues = new float[luxValuesStrings.length]; + long[] luxTimestamps = new long[luxValuesStrings.length]; for (int i = 0; i < luxValues.length; ++i) { - event.luxValues[i] = Float.parseFloat(luxValues[i]); - event.luxTimestamps[i] = Long.parseLong(luxTimestamps[i]); + luxValues[i] = Float.parseFloat(luxValuesStrings[i]); + luxTimestamps[i] = Long.parseLong(luxTimestampsStrings[i]); } + builder.setLuxValues(luxValues); + builder.setLuxTimestamps(luxTimestamps); + BrightnessChangeEvent event = builder.build(); if (DEBUG) { Slog.i(TAG, "Read event " + event.brightness + " " + event.packageName); diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java index 85686ae90250..4f53ed49002b 100644 --- a/services/core/java/com/android/server/display/ColorFade.java +++ b/services/core/java/com/android/server/display/ColorFade.java @@ -99,7 +99,7 @@ final class ColorFade { private final float mProjMatrix[] = new float[16]; private final int[] mGLBuffers = new int[2]; private int mTexCoordLoc, mVertexLoc, mTexUnitLoc, mProjMatrixLoc, mTexMatrixLoc; - private int mOpacityLoc, mGammaLoc, mSaturationLoc; + private int mOpacityLoc, mGammaLoc; private int mProgram; // Vertex and corresponding texture coordinates. @@ -245,7 +245,6 @@ final class ColorFade { mOpacityLoc = GLES20.glGetUniformLocation(mProgram, "opacity"); mGammaLoc = GLES20.glGetUniformLocation(mProgram, "gamma"); - mSaturationLoc = GLES20.glGetUniformLocation(mProgram, "saturation"); mTexUnitLoc = GLES20.glGetUniformLocation(mProgram, "texUnit"); GLES20.glUseProgram(mProgram); @@ -393,9 +392,8 @@ final class ColorFade { double cos = Math.cos(Math.PI * one_minus_level); double sign = cos < 0 ? -1 : 1; float opacity = (float) -Math.pow(one_minus_level, 2) + 1; - float saturation = (float) Math.pow(level, 4); float gamma = (float) ((0.5d * sign * Math.pow(cos, 2) + 0.5d) * 0.9d + 0.1d); - drawFaded(opacity, 1.f / gamma, saturation); + drawFaded(opacity, 1.f / gamma); if (checkGlErrors("drawFrame")) { return false; } @@ -407,10 +405,9 @@ final class ColorFade { return showSurface(1.0f); } - private void drawFaded(float opacity, float gamma, float saturation) { + private void drawFaded(float opacity, float gamma) { if (DEBUG) { - Slog.d(TAG, "drawFaded: opacity=" + opacity + ", gamma=" + gamma + - ", saturation=" + saturation); + Slog.d(TAG, "drawFaded: opacity=" + opacity + ", gamma=" + gamma); } // Use shaders GLES20.glUseProgram(mProgram); @@ -420,7 +417,6 @@ final class ColorFade { GLES20.glUniformMatrix4fv(mTexMatrixLoc, 1, false, mTexMatrix, 0); GLES20.glUniform1f(mOpacityLoc, opacity); GLES20.glUniform1f(mGammaLoc, gamma); - GLES20.glUniform1f(mSaturationLoc, saturation); // Use textures GLES20.glActiveTexture(GLES20.GL_TEXTURE0); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 483b02c2bf65..23e4c9bf69cd 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -16,6 +16,7 @@ package com.android.server.display; +import android.app.ActivityThread; import android.content.res.Resources; import com.android.server.LocalServices; import com.android.server.lights.Light; @@ -392,7 +393,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { | DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS; } - final Resources res = getContext().getResources(); + final Resources res = getOverlayContext().getResources(); if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) { mInfo.name = res.getString( com.android.internal.R.string.display_manager_built_in_display_name); @@ -687,6 +688,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { } } + /** Supplies a context whose Resources apply runtime-overlays */ + Context getOverlayContext() { + return ActivityThread.currentActivityThread().getSystemUiContext(); + } + /** * Keeps track of a display configuration. */ diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 5a8005cc27bb..ee08c3874028 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -82,7 +82,7 @@ import android.security.keystore.UserNotAuthenticatedException; import android.security.keystore.EntryRecoveryData; import android.security.keystore.RecoveryData; import android.security.keystore.RecoveryMetadata; -import android.security.keystore.RecoveryManager.RecoveryManagerException; +import android.security.keystore.RecoveryManagerException; import android.service.gatekeeper.GateKeeperResponse; import android.service.gatekeeper.IGateKeeperService; import android.text.TextUtils; @@ -1982,8 +1982,8 @@ public class LockSettingsService extends ILockSettings.Stub { } @Override - public void setServerParameters(long serverParameters) throws RemoteException { - mRecoverableKeyStoreManager.setServerParameters(serverParameters); + public void setServerParams(byte[] serverParams) throws RemoteException { + mRecoverableKeyStoreManager.setServerParams(serverParams); } @Override diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java index 57d00410cfd7..5fe11b11fa5a 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java @@ -21,7 +21,7 @@ import static android.security.keystore.RecoveryMetadata.TYPE_LOCKSCREEN; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.security.keystore.KeyDerivationParameters; +import android.security.keystore.KeyDerivationParams; import android.security.keystore.EntryRecoveryData; import android.security.keystore.RecoveryData; import android.security.keystore.RecoveryMetadata; @@ -175,7 +175,7 @@ public class KeySyncTask implements Runnable { return; } - Long deviceId = mRecoverableKeyStoreDb.getServerParameters(mUserId, recoveryAgentUid); + byte[] deviceId = mRecoverableKeyStoreDb.getServerParams(mUserId, recoveryAgentUid); if (deviceId == null) { Log.w(TAG, "No device ID set for user " + mUserId); return; @@ -232,8 +232,8 @@ public class KeySyncTask implements Runnable { byte[] vaultParams = KeySyncUtils.packVaultParams( publicKey, counterId, - TRUSTED_HARDWARE_MAX_ATTEMPTS, - deviceId); + deviceId, + TRUSTED_HARDWARE_MAX_ATTEMPTS); byte[] encryptedRecoveryKey; try { @@ -254,7 +254,7 @@ public class KeySyncTask implements Runnable { RecoveryMetadata metadata = new RecoveryMetadata( /*userSecretType=*/ TYPE_LOCKSCREEN, /*lockScreenUiFormat=*/ getUiFormat(mCredentialType, mCredential), - /*keyDerivationParameters=*/ KeyDerivationParameters.createSha256Parameters(salt), + /*keyDerivationParams=*/ KeyDerivationParams.createSha256Params(salt), /*secret=*/ new byte[0]); ArrayList<RecoveryMetadata> metadataList = new ArrayList<>(); metadataList.add(metadata); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java index 0ab8dc5872f8..b4bef170e2bc 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java @@ -287,17 +287,17 @@ public class KeySyncUtils { * * @param thmPublicKey Public key of the trusted hardware module. * @param counterId ID referring to the specific counter in the hardware module. - * @param maxAttempts Maximum allowed guesses before trusted hardware wipes key. * @param deviceId ID of the device. + * @param maxAttempts Maximum allowed guesses before trusted hardware wipes key. * @return The binary vault params, ready for sync. */ public static byte[] packVaultParams( - PublicKey thmPublicKey, long counterId, int maxAttempts, long deviceId) { + PublicKey thmPublicKey, long counterId, byte[] deviceId, int maxAttempts) { return ByteBuffer.allocate(VAULT_PARAMS_LENGTH_BYTES) .order(ByteOrder.LITTLE_ENDIAN) .put(SecureBox.encodePublicKey(thmPublicKey)) .putLong(counterId) - .putLong(deviceId) + .putLong(0L) // TODO: replace with device Id. .putInt(maxAttempts) .array(); } 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 0e93f947c1bf..7658178d43da 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -16,13 +16,13 @@ package com.android.server.locksettings.recoverablekeystore; -import static android.security.keystore.RecoveryManager.ERROR_BAD_X509_CERTIFICATE; -import static android.security.keystore.RecoveryManager.ERROR_DATABASE_ERROR; -import static android.security.keystore.RecoveryManager.ERROR_DECRYPTION_FAILED; -import static android.security.keystore.RecoveryManager.ERROR_INSECURE_USER; -import static android.security.keystore.RecoveryManager.ERROR_KEYSTORE_INTERNAL_ERROR; -import static android.security.keystore.RecoveryManager.ERROR_NOT_YET_SUPPORTED; -import static android.security.keystore.RecoveryManager.ERROR_UNEXPECTED_MISSING_ALGORITHM; +import static android.security.keystore.RecoveryManagerException.ERROR_BAD_X509_CERTIFICATE; +import static android.security.keystore.RecoveryManagerException.ERROR_DATABASE_ERROR; +import static android.security.keystore.RecoveryManagerException.ERROR_DECRYPTION_FAILED; +import static android.security.keystore.RecoveryManagerException.ERROR_INSECURE_USER; +import static android.security.keystore.RecoveryManagerException.ERROR_KEYSTORE_INTERNAL_ERROR; +import static android.security.keystore.RecoveryManagerException.ERROR_UNEXPECTED_MISSING_ALGORITHM; +import static android.security.keystore.RecoveryManagerException.ERROR_NO_SNAPSHOT_PENDING; import android.annotation.NonNull; import android.annotation.Nullable; @@ -177,7 +177,7 @@ public class RecoverableKeyStoreManager { int uid = Binder.getCallingUid(); RecoveryData snapshot = mSnapshotStorage.get(uid); if (snapshot == null) { - throw new ServiceSpecificException(RecoveryManager.ERROR_NO_SNAPSHOT_PENDING); + throw new ServiceSpecificException(ERROR_NO_SNAPSHOT_PENDING); } return snapshot; } @@ -201,11 +201,11 @@ public class RecoverableKeyStoreManager { throw new UnsupportedOperationException(); } - public void setServerParameters(long serverParameters) throws RemoteException { + public void setServerParams(byte[] serverParams) throws RemoteException { checkRecoverKeyStorePermission(); int userId = UserHandle.getCallingUserId(); int uid = Binder.getCallingUid(); - long updatedRows = mDatabase.setServerParameters(userId, uid, serverParameters); + long updatedRows = mDatabase.setServerParams(userId, uid, serverParams); if (updatedRows > 0) { mDatabase.setShouldCreateSnapshot(userId, uid, true); } @@ -326,10 +326,7 @@ public class RecoverableKeyStoreManager { int uid = Binder.getCallingUid(); if (secrets.size() != 1) { - // TODO: support multiple secrets - throw new ServiceSpecificException( - ERROR_NOT_YET_SUPPORTED, - "Only a single RecoveryMetadata is supported"); + throw new UnsupportedOperationException("Only a single RecoveryMetadata is supported"); } PublicKey publicKey; 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 8674330364be..eb2da8077b36 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 @@ -336,17 +336,8 @@ public class RecoverableKeyStoreDb { * @hide */ public long setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey) { - SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); - ContentValues values = new ContentValues(); - values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY, publicKey.getEncoded()); - String selection = - RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " - + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; - String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; - - ensureRecoveryServiceMetadataEntryExists(userId, uid); - return db.update( - RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments); + return setBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY, + publicKey.getEncoded()); } /** @@ -393,56 +384,20 @@ public class RecoverableKeyStoreDb { */ @Nullable public PublicKey getRecoveryServicePublicKey(int userId, int uid) { - SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); - - String[] projection = { - RecoveryServiceMetadataEntry._ID, - RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, - RecoveryServiceMetadataEntry.COLUMN_NAME_UID, - RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY}; - String selection = - RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " - + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; - String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; - - try ( - Cursor cursor = db.query( - RecoveryServiceMetadataEntry.TABLE_NAME, - projection, - selection, - selectionArguments, - /*groupBy=*/ null, - /*having=*/ null, - /*orderBy=*/ null) - ) { - int count = cursor.getCount(); - if (count == 0) { - return null; - } - if (count > 1) { - Log.wtf(TAG, - String.format(Locale.US, - "%d PublicKey entries found for userId=%d uid=%d. " - + "Should only ever be 0 or 1.", count, userId, uid)); - return null; - } - cursor.moveToFirst(); - int idx = cursor.getColumnIndexOrThrow( - RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY); - if (cursor.isNull(idx)) { - return null; - } - byte[] keyBytes = cursor.getBlob(idx); - try { - return decodeX509Key(keyBytes); - } catch (InvalidKeySpecException e) { - Log.wtf(TAG, - String.format(Locale.US, - "Recovery service public key entry cannot be decoded for " - + "userId=%d uid=%d.", - userId, uid)); - return null; - } + byte[] keyBytes = + getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY); + if (keyBytes == null) { + return null; + } + try { + return decodeX509Key(keyBytes); + } catch (InvalidKeySpecException e) { + Log.wtf(TAG, + String.format(Locale.US, + "Recovery service public key entry cannot be decoded for " + + "userId=%d uid=%d.", + userId, uid)); + return null; } } @@ -617,14 +572,14 @@ public class RecoverableKeyStoreDb { * * @param userId The userId of the profile the application is running under. * @param uid The uid of the application. - * @param serverParameters The server parameters. + * @param serverParams The server parameters. * @return The primary key of the inserted row, or -1 if failed. * * @hide */ - public long setServerParameters(int userId, int uid, long serverParameters) { - return setLong(userId, uid, - RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS, serverParameters); + public long setServerParams(int userId, int uid, byte[] serverParams) { + return setBytes(userId, uid, + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS, serverParams); } /** @@ -638,9 +593,8 @@ public class RecoverableKeyStoreDb { * @hide */ @Nullable - public Long getServerParameters(int userId, int uid) { - return getLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS); - + public byte[] getServerParams(int userId, int uid) { + return getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS); } /** @@ -704,6 +658,7 @@ public class RecoverableKeyStoreDb { return res != null && res != 0L; } + /** * Returns given long value from the database. * @@ -785,6 +740,86 @@ public class RecoverableKeyStoreDb { } /** + * Returns given binary value from the database. + * + * @param userId The userId of the profile the application is running under. + * @param uid The uid of the application who initialized the local recovery components. + * @param key from {@code RecoveryServiceMetadataEntry} + * @return The value that were previously set, or null if there's none. + * + * @hide + */ + private byte[] getBytes(int userId, int uid, String key) { + SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); + + String[] projection = { + RecoveryServiceMetadataEntry._ID, + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, + RecoveryServiceMetadataEntry.COLUMN_NAME_UID, + key}; + String selection = + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " + + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; + String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; + + try ( + Cursor cursor = db.query( + RecoveryServiceMetadataEntry.TABLE_NAME, + projection, + selection, + selectionArguments, + /*groupBy=*/ null, + /*having=*/ null, + /*orderBy=*/ null) + ) { + int count = cursor.getCount(); + if (count == 0) { + return null; + } + if (count > 1) { + Log.wtf(TAG, + String.format(Locale.US, + "%d entries found for userId=%d uid=%d. " + + "Should only ever be 0 or 1.", count, userId, uid)); + return null; + } + cursor.moveToFirst(); + int idx = cursor.getColumnIndexOrThrow(key); + if (cursor.isNull(idx)) { + return null; + } else { + return cursor.getBlob(idx); + } + } + } + + /** + * Sets a binary value in the database. + * + * @param userId The userId of the profile the application is running under. + * @param uid The uid of the application who initialized the local recovery components. + * @param key defined in {@code RecoveryServiceMetadataEntry} + * @param value new value. + * @return The primary key of the inserted row, or -1 if failed. + * + * @hide + */ + + private long setBytes(int userId, int uid, String key, byte[] value) { + SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(key, value); + String selection = + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND " + + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?"; + String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)}; + + ensureRecoveryServiceMetadataEntryExists(userId, uid); + return db.update( + RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments); + } + + /** * Creates an empty row in the recovery service metadata table if such a row doesn't exist for * the given userId and uid, so db.update will succeed. */ 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 a6d9bd41d7be..4ee282b6115e 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 @@ -130,6 +130,6 @@ class RecoverableKeyStoreDbContract { /** * The server parameters of the recovery service. */ - static final String COLUMN_NAME_SERVER_PARAMETERS = "server_parameters"; + static final String COLUMN_NAME_SERVER_PARAMS = "server_params"; } } 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 6eb47ee44f24..d96671c5cd9d 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 @@ -61,7 +61,7 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB," + RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES + " TEXT," + RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID + " INTEGER," - + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS + " INTEGER," + + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS + " BLOB," + "UNIQUE(" + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + "," + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + "))"; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 575c44d15209..16b92573f222 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2917,12 +2917,34 @@ public class NotificationManagerService extends SystemService { } } + /** + * Sets the notification policy. Apps that target API levels below + * {@link android.os.Build.VERSION_CODES#P} cannot make DND silence + * {@link Policy#PRIORITY_CATEGORY_ALARMS} or + * {@link Policy#PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER} + */ @Override public void setNotificationPolicy(String pkg, Policy policy) { enforcePolicyAccess(pkg, "setNotificationPolicy"); final long identity = Binder.clearCallingIdentity(); try { + final ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(pkg, + 0, UserHandle.getUserId(MY_UID)); + + if (applicationInfo.targetSdkVersion <= Build.VERSION_CODES.O_MR1) { + Policy currPolicy = mZenModeHelper.getNotificationPolicy(); + + int priorityCategories = policy.priorityCategories + | (currPolicy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) + | (currPolicy.priorityCategories & + Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER); + policy = new Policy(priorityCategories, + policy.priorityCallSenders, policy.priorityMessageSenders, + policy.suppressedVisualEffects); + } + mZenModeHelper.setNotificationPolicy(policy); + } catch (RemoteException e) { } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index b6a094c8c04e..50690cb0f33e 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -117,7 +117,9 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_UNIFIED_PASSWORD, UserManager.DISALLOW_CONFIG_LOCATION_MODE, UserManager.DISALLOW_AIRPLANE_MODE, - UserManager.DISALLOW_CONFIG_BRIGHTNESS + UserManager.DISALLOW_CONFIG_BRIGHTNESS, + UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, + UserManager.DISALLOW_AMBIENT_DISPLAY }); /** diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 15fd7425ab64..d35255913d1c 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -161,6 +161,7 @@ import android.app.StatusBarManager; import android.app.UiModeManager; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; +import android.content.ComponentCallbacks; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -623,8 +624,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { PointerLocationView mPointerLocationView; - boolean mEmulateDisplayCutout = false; - // During layout, the layer at which the doc window is placed. int mDockLayer; // During layout, this is the layer of the status bar. @@ -983,9 +982,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { resolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.POLICY_CONTROL), false, this, UserHandle.USER_ALL); - resolver.registerContentObserver(Settings.Global.getUriFor( - Settings.Global.EMULATE_DISPLAY_CUTOUT), false, this, - UserHandle.USER_ALL); updateSettings(); } @@ -2411,10 +2407,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mImmersiveModeConfirmation != null) { mImmersiveModeConfirmation.loadSetting(mCurrentUserId); } - mEmulateDisplayCutout = Settings.Global.getInt(resolver, - Settings.Global.EMULATE_DISPLAY_CUTOUT, - Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF) - != Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF; } synchronized (mWindowManagerFuncs.getWindowManagerLock()) { PolicyControl.reloadFromSetting(mContext); @@ -2750,6 +2742,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override + public void onOverlayChangedLw() { + onConfigurationChanged(); + } + + @Override public void onConfigurationChanged() { // TODO(multi-display): Define policy for secondary displays. Context uiContext = getSystemUiContext(); @@ -4449,7 +4446,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** {@inheritDoc} */ @Override public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) { - displayFrames.onBeginLayout(mEmulateDisplayCutout, mStatusBarHeight); + displayFrames.onBeginLayout(); // TODO(multi-display): This doesn't seem right...Maybe only apply to default display? mSystemGestures.screenWidth = displayFrames.mUnrestricted.width(); mSystemGestures.screenHeight = displayFrames.mUnrestricted.height(); @@ -4892,8 +4889,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { final int adjust = sim & SOFT_INPUT_MASK_ADJUST; final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0 - || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0 - || (requestedSysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0; + || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0; final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN; final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR; @@ -7907,8 +7903,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { // If the top fullscreen-or-dimming window is also the top fullscreen, respect // its light flag. vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - vis |= PolicyControl.getSystemUiVisibility(statusColorWin, null) - & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + if (!statusColorWin.isLetterboxedForDisplayCutoutLw()) { + // Only allow white status bar if the window was not letterboxed. + vis |= PolicyControl.getSystemUiVisibility(statusColorWin, null) + & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } } else if (statusColorWin != null && statusColorWin.isDimming()) { // Otherwise if it's dimming, clear the light flag. vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 686155b40985..64a280c1cbe7 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -176,6 +176,11 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { void onKeyguardOccludedChangedLw(boolean occluded); /** + * Called when the resource overlays change. + */ + default void onOverlayChangedLw() {} + + /** * Interface to the Window Manager state associated with a particular * window. You can hold on to an instance of this interface from the call * to prepareAddWindow() until removeWindow(). @@ -446,6 +451,13 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { */ public boolean isDimming(); + /** + * Returns true if the window is letterboxed for the display cutout. + */ + default boolean isLetterboxedForDisplayCutoutLw() { + return false; + } + /** @return the current windowing mode of this window. */ int getWindowingMode(); diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index f2098dc3ce4f..a254ba2cab31 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -24,6 +24,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.SurfaceControl.HIDDEN; import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; @@ -245,6 +246,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree /** Whether this token should be boosted at the top of all app window tokens. */ private boolean mNeedsZBoost; + private Letterbox mLetterbox; private final Point mTmpPoint = new Point(); private final Rect mTmpRect = new Rect(); @@ -678,6 +680,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (destroyedSomething) { final DisplayContent dc = getDisplayContent(); dc.assignWindowLayers(true /*setLayoutNeeded*/); + updateLetterbox(null); } } @@ -944,6 +947,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree void removeChild(WindowState child) { super.removeChild(child); checkKeyguardFlagsChanged(); + updateLetterbox(child); } private boolean waitingForReplacement() { @@ -1409,6 +1413,33 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return isInterestingAndDrawn; } + void updateLetterbox(WindowState winHint) { + final WindowState w = findMainWindow(); + if (w != winHint && winHint != null && w != null) { + return; + } + final boolean needsLetterbox = w != null && w.isLetterboxedAppWindow() + && fillsParent() && w.hasDrawnLw(); + if (needsLetterbox) { + if (mLetterbox == null) { + mLetterbox = new Letterbox(() -> makeChildSurface(null)); + } + mLetterbox.setDimensions(mPendingTransaction, getParent().getBounds(), w.mFrame); + } else if (mLetterbox != null) { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + // Make sure we have a transaction here, in case we're called outside of a transaction. + // This does not use mPendingTransaction, because SurfaceAnimator uses a + // global transaction in onAnimationEnd. + SurfaceControl.openTransaction(); + try { + mLetterbox.hide(t); + } finally { + SurfaceControl.mergeToGlobalTransaction(t); + SurfaceControl.closeTransaction(); + } + } + } + @Override boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) { // For legacy reasons we process the TaskStack.mExitingAppTokens first in DisplayContent @@ -1635,6 +1666,8 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree // the status bar). In that case we need to use the final frame. if (freeform) { frame.set(win.mFrame); + } else if (win.isLetterboxedAppWindow()) { + frame.set(getTask().getBounds()); } else { frame.set(win.mContainingFrame); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 4c5520fac2cb..a8e00dd53526 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -697,6 +697,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final AppWindowToken atoken = w.mAppToken; if (atoken != null) { + atoken.updateLetterbox(w); final boolean updateAllDrawn = atoken.updateDrawnWindowStates(w); if (updateAllDrawn && !mTmpUpdateAllDrawn.contains(atoken)) { mTmpUpdateAllDrawn.add(atoken); diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java index bd06192b09fd..13d0c86959c7 100644 --- a/services/core/java/com/android/server/wm/DisplayFrames.java +++ b/services/core/java/com/android/server/wm/DisplayFrames.java @@ -101,7 +101,7 @@ public class DisplayFrames { /** During layout, the current screen borders along which input method windows are placed. */ public final Rect mDock = new Rect(); - /** The display cutout used for layout (after rotation and emulation) */ + /** The display cutout used for layout (after rotation) */ @NonNull public DisplayCutout mDisplayCutout = DisplayCutout.NO_CUTOUT; /** The cutout as supplied by display info */ @@ -134,7 +134,7 @@ public class DisplayFrames { ? info.displayCutout : DisplayCutout.NO_CUTOUT; } - public void onBeginLayout(boolean emulateDisplayCutout, int statusBarHeight) { + public void onBeginLayout() { switch (mRotation) { case ROTATION_90: mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.top; @@ -172,12 +172,8 @@ public class DisplayFrames { mStable.set(mUnrestricted); mStableFullscreen.set(mUnrestricted); mCurrent.set(mUnrestricted); - mDisplayCutout = mDisplayInfoCutout; - if (emulateDisplayCutout) { - setEmulatedDisplayCutout((int) (statusBarHeight * 0.8)); - } - mDisplayCutout = mDisplayCutout.calculateRelativeTo(mOverscan); + mDisplayCutout = mDisplayInfoCutout.calculateRelativeTo(mOverscan); mDisplayCutoutSafe.set(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); if (!mDisplayCutout.isEmpty()) { @@ -201,51 +197,6 @@ public class DisplayFrames { return mDock.bottom - mCurrent.bottom; } - private void setEmulatedDisplayCutout(int height) { - final boolean swappedDimensions = mRotation == ROTATION_90 || mRotation == ROTATION_270; - - final int screenWidth = swappedDimensions ? mDisplayHeight : mDisplayWidth; - final int screenHeight = swappedDimensions ? mDisplayWidth : mDisplayHeight; - - final int widthTop = (int) (screenWidth * 0.3); - final int widthBottom = widthTop - height; - - switch (mRotation) { - case ROTATION_90: - mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList( - new Point(0, (screenWidth - widthTop) / 2), - new Point(height, (screenWidth - widthBottom) / 2), - new Point(height, (screenWidth + widthBottom) / 2), - new Point(0, (screenWidth + widthTop) / 2) - )); - break; - case ROTATION_180: - mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList( - new Point((screenWidth - widthTop) / 2, screenHeight), - new Point((screenWidth - widthBottom) / 2, screenHeight - height), - new Point((screenWidth + widthBottom) / 2, screenHeight - height), - new Point((screenWidth + widthTop) / 2, screenHeight) - )); - break; - case ROTATION_270: - mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList( - new Point(screenHeight, (screenWidth - widthTop) / 2), - new Point(screenHeight - height, (screenWidth - widthBottom) / 2), - new Point(screenHeight - height, (screenWidth + widthBottom) / 2), - new Point(screenHeight, (screenWidth + widthTop) / 2) - )); - break; - default: - mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList( - new Point((screenWidth - widthTop) / 2, 0), - new Point((screenWidth - widthBottom) / 2, height), - new Point((screenWidth + widthBottom) / 2, height), - new Point((screenWidth + widthTop) / 2, 0) - )); - break; - } - } - public void writeToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); mStable.writeToProto(proto, STABLE_BOUNDS); diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java new file mode 100644 index 000000000000..8fa79ab5e189 --- /dev/null +++ b/services/core/java/com/android/server/wm/Letterbox.java @@ -0,0 +1,145 @@ +/* + * 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 com.android.server.wm; + +import static android.view.SurfaceControl.HIDDEN; + +import android.graphics.Rect; +import android.view.SurfaceControl; + +import java.util.function.Supplier; + +/** + * Manages a set of {@link SurfaceControl}s to draw a black letterbox between an + * outer rect and an inner rect. + */ +public class Letterbox { + + private static final Rect EMPTY_RECT = new Rect(); + + private final Supplier<SurfaceControl.Builder> mFactory; + private final Rect mOuter = new Rect(); + private final Rect mInner = new Rect(); + private final LetterboxSurface mTop = new LetterboxSurface("top"); + private final LetterboxSurface mLeft = new LetterboxSurface("left"); + private final LetterboxSurface mBottom = new LetterboxSurface("bottom"); + private final LetterboxSurface mRight = new LetterboxSurface("right"); + + /** + * Constructs a Letterbox. + * + * @param surfaceControlFactory a factory for creating the managed {@link SurfaceControl}s + */ + public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory) { + mFactory = surfaceControlFactory; + } + + /** + * Sets the dimensions of the the letterbox, such that the area between the outer and inner + * frames will be covered by black color surfaces. + * + * @param t a transaction in which to set the dimensions + * @param outer the outer frame of the letterbox (this frame will be black, except the area + * that intersects with the {code inner} frame). + * @param inner the inner frame of the letterbox (this frame will be clear) + */ + public void setDimensions(SurfaceControl.Transaction t, Rect outer, Rect inner) { + mOuter.set(outer); + mInner.set(inner); + + mTop.setRect(t, outer.left, outer.top, inner.right, inner.top); + mLeft.setRect(t, outer.left, inner.top, inner.left, outer.bottom); + mBottom.setRect(t, inner.left, inner.bottom, outer.right, outer.bottom); + mRight.setRect(t, inner.right, outer.top, outer.right, inner.bottom); + } + + /** + * Hides the letterbox. + * + * @param t a transaction in which to hide the letterbox + */ + public void hide(SurfaceControl.Transaction t) { + setDimensions(t, EMPTY_RECT, EMPTY_RECT); + } + + /** + * Destroys the managed {@link SurfaceControl}s. + */ + public void destroy() { + mOuter.setEmpty(); + mInner.setEmpty(); + + mTop.destroy(); + mLeft.destroy(); + mBottom.destroy(); + mRight.destroy(); + } + + private class LetterboxSurface { + + private final String mType; + private SurfaceControl mSurface; + + private int mLastLeft = 0; + private int mLastTop = 0; + private int mLastRight = 0; + private int mLastBottom = 0; + + public LetterboxSurface(String type) { + mType = type; + } + + public void setRect(SurfaceControl.Transaction t, + int left, int top, int right, int bottom) { + if (mLastLeft == left && mLastTop == top + && mLastRight == right && mLastBottom == bottom) { + // Nothing changed. + return; + } + + if (left < right && top < bottom) { + if (mSurface == null) { + createSurface(); + } + t.setPosition(mSurface, left, top); + t.setSize(mSurface, right - left, bottom - top); + t.show(mSurface); + } else if (mSurface != null) { + t.hide(mSurface); + } + + mLastLeft = left; + mLastTop = top; + mLastRight = right; + mLastBottom = bottom; + } + + private void createSurface() { + mSurface = mFactory.get().setName("Letterbox - " + mType) + .setFlags(HIDDEN).setColorLayer(true).build(); + mSurface.setLayer(-1); + mSurface.setColor(new float[]{0, 0, 0}); + } + + public void destroy() { + if (mSurface != null) { + mSurface.destroy(); + mSurface = null; + } + } + } +} diff --git a/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java b/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java index d2cbf88aac58..33e560f35dfb 100644 --- a/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java +++ b/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java @@ -33,7 +33,7 @@ import java.io.DataOutputStream; // the surface control. // // See cts/hostsidetests/../../SurfaceTraceReceiver.java for parsing side. -class RemoteSurfaceTrace extends SurfaceControlWithBackground { +class RemoteSurfaceTrace extends SurfaceControl { static final String TAG = "RemoteSurfaceTrace"; final FileDescriptor mWriteFd; @@ -42,7 +42,7 @@ class RemoteSurfaceTrace extends SurfaceControlWithBackground { final WindowManagerService mService; final WindowState mWindow; - RemoteSurfaceTrace(FileDescriptor fd, SurfaceControlWithBackground wrapped, + RemoteSurfaceTrace(FileDescriptor fd, SurfaceControl wrapped, WindowState window) { super(wrapped); diff --git a/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java b/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java deleted file mode 100644 index 7c5bd43a7e7f..000000000000 --- a/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; - -import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM; -import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT; -import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT; - -import android.graphics.Rect; -import android.graphics.Region; -import android.os.IBinder; -import android.os.Parcel; -import android.view.Surface; -import android.view.Surface.OutOfResourcesException; -import android.view.SurfaceControl; - -/** - * SurfaceControl extension that has black background behind navigation bar area for fullscreen - * letterboxed apps. - */ -class SurfaceControlWithBackground extends SurfaceControl { - // SurfaceControl that holds the background. - private SurfaceControl mBackgroundControl; - - // Flag that defines whether the background should be shown. - private boolean mVisible; - - // Way to communicate with corresponding window. - private WindowSurfaceController mWindowSurfaceController; - - // Rect to hold task bounds when computing metrics for background. - private Rect mTmpContainerRect = new Rect(); - - // Last metrics applied to the main SurfaceControl. - private float mLastWidth, mLastHeight; - private float mLastDsDx = 1, mLastDsDy = 1; - private float mLastX, mLastY; - - // SurfaceFlinger doesn't support crop rectangles where width or height is non-positive. - // If we just set an empty crop it will behave as if there is no crop at all. - // To fix this we explicitly hide the surface and won't let it to be shown. - private boolean mHiddenForCrop = false; - - public SurfaceControlWithBackground(SurfaceControlWithBackground other) { - super(other); - mBackgroundControl = other.mBackgroundControl; - mVisible = other.mVisible; - mWindowSurfaceController = other.mWindowSurfaceController; - } - - public SurfaceControlWithBackground(String name, SurfaceControl.Builder b, - int windowType, int w, int h, - WindowSurfaceController windowSurfaceController) throws OutOfResourcesException { - super(b.build()); - - // We should only show background behind app windows that are letterboxed in a task. - if ((windowType != TYPE_BASE_APPLICATION && windowType != TYPE_APPLICATION_STARTING) - || !windowSurfaceController.mAnimator.mWin.isLetterboxedAppWindow()) { - return; - } - mWindowSurfaceController = windowSurfaceController; - mLastWidth = w; - mLastHeight = h; - mWindowSurfaceController.getContainerRect(mTmpContainerRect); - mBackgroundControl = b.setName("Background for - " + name) - .setSize(mTmpContainerRect.width(), mTmpContainerRect.height()) - .setFormat(OPAQUE) - .setColorLayer(true) - .build(); - } - - @Override - public void setAlpha(float alpha) { - super.setAlpha(alpha); - - if (mBackgroundControl == null) { - return; - } - mBackgroundControl.setAlpha(alpha); - } - - @Override - public void setLayer(int zorder) { - super.setLayer(zorder); - - if (mBackgroundControl == null) { - return; - } - // TODO: Use setRelativeLayer(Integer.MIN_VALUE) when it's fixed. - mBackgroundControl.setLayer(zorder - 1); - } - - @Override - public void setPosition(float x, float y) { - super.setPosition(x, y); - - if (mBackgroundControl == null) { - return; - } - mLastX = x; - mLastY = y; - updateBgPosition(); - } - - private void updateBgPosition() { - mWindowSurfaceController.getContainerRect(mTmpContainerRect); - final Rect winFrame = mWindowSurfaceController.mAnimator.mWin.mFrame; - final float offsetX = (mTmpContainerRect.left - winFrame.left) * mLastDsDx; - final float offsetY = (mTmpContainerRect.top - winFrame.top) * mLastDsDy; - mBackgroundControl.setPosition(mLastX + offsetX, mLastY + offsetY); - } - - @Override - public void setSize(int w, int h) { - super.setSize(w, h); - - if (mBackgroundControl == null) { - return; - } - mLastWidth = w; - mLastHeight = h; - mWindowSurfaceController.getContainerRect(mTmpContainerRect); - mBackgroundControl.setSize(mTmpContainerRect.width(), mTmpContainerRect.height()); - } - - @Override - public void setWindowCrop(Rect crop) { - super.setWindowCrop(crop); - - if (mBackgroundControl == null) { - return; - } - calculateBgCrop(crop); - mBackgroundControl.setWindowCrop(mTmpContainerRect); - mHiddenForCrop = mTmpContainerRect.isEmpty(); - updateBackgroundVisibility(); - } - - @Override - public void setFinalCrop(Rect crop) { - super.setFinalCrop(crop); - - if (mBackgroundControl == null) { - return; - } - mWindowSurfaceController.getContainerRect(mTmpContainerRect); - mBackgroundControl.setFinalCrop(mTmpContainerRect); - } - - /** - * Compute background crop based on current animation progress for main surface control and - * update {@link #mTmpContainerRect} with new values. - */ - private void calculateBgCrop(Rect crop) { - // Track overall progress of animation by computing cropped portion of status bar. - final Rect contentInsets = mWindowSurfaceController.mAnimator.mWin.mContentInsets; - float d = contentInsets.top == 0 ? 0 : (float) crop.top / contentInsets.top; - if (d > 1.f) { - // We're running expand animation from launcher, won't compute custom bg crop here. - mTmpContainerRect.setEmpty(); - return; - } - - // Compute new scaled width and height for background that will depend on current animation - // progress. Those consist of current crop rect for the main surface + scaled areas outside - // of letterboxed area. - // TODO: Because the progress is computed with low precision we're getting smaller values - // for background width/height then screen size at the end of the animation. Will round when - // the value is smaller then some empiric epsilon. However, this should be fixed by - // computing correct frames for letterboxed windows in WindowState. - d = d < 0.025f ? 0 : d; - mWindowSurfaceController.getContainerRect(mTmpContainerRect); - int backgroundWidth = 0, backgroundHeight = 0; - // Compute additional offset for the background when app window is positioned not at (0,0). - // E.g. landscape with navigation bar on the left. - final Rect winFrame = mWindowSurfaceController.mAnimator.mWin.mFrame; - int offsetX = (int)((winFrame.left - mTmpContainerRect.left) * mLastDsDx), - offsetY = (int) ((winFrame.top - mTmpContainerRect.top) * mLastDsDy); - - // Position and size background. - final int bgPosition = mWindowSurfaceController.mAnimator.mService.getNavBarPosition(); - - switch (bgPosition) { - case NAV_BAR_LEFT: - backgroundWidth = (int) ((mTmpContainerRect.width() - mLastWidth) * (1 - d) + 0.5); - backgroundHeight = crop.height(); - offsetX += crop.left - backgroundWidth; - offsetY += crop.top; - break; - case NAV_BAR_RIGHT: - backgroundWidth = (int) ((mTmpContainerRect.width() - mLastWidth) * (1 - d) + 0.5); - backgroundHeight = crop.height(); - offsetX += crop.right; - offsetY += crop.top; - break; - case NAV_BAR_BOTTOM: - backgroundWidth = crop.width(); - backgroundHeight = (int) ((mTmpContainerRect.height() - mLastHeight) * (1 - d) - + 0.5); - offsetX += crop.left; - offsetY += crop.bottom; - break; - } - mTmpContainerRect.set(offsetX, offsetY, offsetX + backgroundWidth, - offsetY + backgroundHeight); - } - - @Override - public void setLayerStack(int layerStack) { - super.setLayerStack(layerStack); - - if (mBackgroundControl == null) { - return; - } - mBackgroundControl.setLayerStack(layerStack); - } - - @Override - public void setOpaque(boolean isOpaque) { - super.setOpaque(isOpaque); - updateBackgroundVisibility(); - } - - @Override - public void setSecure(boolean isSecure) { - super.setSecure(isSecure); - } - - @Override - public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) { - super.setMatrix(dsdx, dtdx, dtdy, dsdy); - - if (mBackgroundControl == null) { - return; - } - mBackgroundControl.setMatrix(dsdx, dtdx, dtdy, dsdy); - mLastDsDx = dsdx; - mLastDsDy = dsdy; - updateBgPosition(); - } - - @Override - public void hide() { - super.hide(); - mVisible = false; - updateBackgroundVisibility(); - } - - @Override - public void show() { - super.show(); - mVisible = true; - updateBackgroundVisibility(); - } - - @Override - public void destroy() { - super.destroy(); - - if (mBackgroundControl == null) { - return; - } - mBackgroundControl.destroy(); - } - - @Override - public void release() { - super.release(); - - if (mBackgroundControl == null) { - return; - } - mBackgroundControl.release(); - } - - @Override - public void setTransparentRegionHint(Region region) { - super.setTransparentRegionHint(region); - - if (mBackgroundControl == null) { - return; - } - mBackgroundControl.setTransparentRegionHint(region); - } - - @Override - public void deferTransactionUntil(IBinder handle, long frame) { - super.deferTransactionUntil(handle, frame); - - if (mBackgroundControl == null) { - return; - } - mBackgroundControl.deferTransactionUntil(handle, frame); - } - - @Override - public void deferTransactionUntil(Surface barrier, long frame) { - super.deferTransactionUntil(barrier, frame); - - if (mBackgroundControl == null) { - return; - } - mBackgroundControl.deferTransactionUntil(barrier, frame); - } - - private void updateBackgroundVisibility() { - if (mBackgroundControl == null) { - return; - } - final AppWindowToken appWindowToken = mWindowSurfaceController.mAnimator.mWin.mAppToken; - if (!mHiddenForCrop && mVisible && appWindowToken != null && appWindowToken.fillsParent()) { - mBackgroundControl.show(); - } else { - mBackgroundControl.hide(); - } - } -} diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index e4fe88858184..7b3353375914 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -6596,6 +6596,13 @@ public class WindowManagerService extends IWindowManager.Stub } } + public void onOverlayChanged() { + synchronized (mWindowMap) { + mPolicy.onOverlayChangedLw(); + requestTraversal(); + } + } + public void onDisplayChanged(int displayId) { synchronized (mWindowMap) { final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 97070bd91451..d5a1680bd045 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -20,16 +20,20 @@ import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.SurfaceControl.Transaction; +import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; +import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; +import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; @@ -198,7 +202,6 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Comparator; -import java.util.LinkedList; import java.util.function.Predicate; /** A window in the window manager. */ @@ -2977,7 +2980,29 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP /** @return true when the window is in fullscreen task, but has non-fullscreen bounds set. */ boolean isLetterboxedAppWindow() { - return !isInMultiWindowMode() && mAppToken != null && !mAppToken.matchParentBounds(); + return !isInMultiWindowMode() && mAppToken != null && !mAppToken.matchParentBounds() + || isLetterboxedForDisplayCutoutLw(); + } + + @Override + public boolean isLetterboxedForDisplayCutoutLw() { + if (mAppToken == null) { + // Only windows with an AppWindowToken are letterboxed. + return false; + } + if (getDisplayContent().getDisplayInfo().displayCutout == null) { + // No cutout, no letterbox. + return false; + } + if ((mAttrs.flags2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0) { + // Layout in cutout, no letterbox. + return false; + } + // TODO: handle dialogs and other non-filling windows + // Letterbox if any fullscreen mode is set. + final int fl = mAttrs.flags; + final int sysui = mSystemUiVisibility; + return (fl & FLAG_FULLSCREEN) != 0 || (sysui & (SYSTEM_UI_FLAG_FULLSCREEN)) != 0; } boolean isDragResizeChanged() { diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index e26a362b272d..2f38556efa7c 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -53,7 +53,7 @@ class WindowSurfaceController { final WindowStateAnimator mAnimator; - SurfaceControlWithBackground mSurfaceControl; + SurfaceControl mSurfaceControl; // Should only be set from within setShown(). private boolean mSurfaceShown = false; @@ -108,13 +108,11 @@ class WindowSurfaceController { .setFormat(format) .setFlags(flags) .setMetadata(windowType, ownerUid); - mSurfaceControl = new SurfaceControlWithBackground( - name, b, windowType, w, h, this); + mSurfaceControl = b.build(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (mService.mRoot.mSurfaceTraceEnabled) { - mSurfaceControl = new RemoteSurfaceTrace( - mService.mRoot.mSurfaceTraceFd.getFileDescriptor(), mSurfaceControl, win); + installRemoteTrace(mService.mRoot.mSurfaceTraceFd.getFileDescriptor()); } } @@ -123,7 +121,7 @@ class WindowSurfaceController { } void removeRemoteTrace() { - mSurfaceControl = new SurfaceControlWithBackground(mSurfaceControl); + mSurfaceControl = new SurfaceControl(mSurfaceControl); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 0fbb11f19e34..36de3d12a743 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -19,6 +19,7 @@ import android.annotation.UserIdInt; import android.app.admin.IDevicePolicyManager; import android.content.ComponentName; import android.os.PersistableBundle; +import android.os.UserHandle; import android.security.keymaster.KeymasterCertificateChain; import android.security.keystore.ParcelableKeyGenParameterSpec; @@ -103,4 +104,9 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { byte[] cert, byte[] chain, boolean isUserSelectable) { return false; } + + @Override + public boolean startUserInBackground(ComponentName who, UserHandle userHandle) { + return false; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index e112b5be1498..bf2b137f65b8 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -54,7 +54,6 @@ import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLE import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER; -import static android.app.admin.DevicePolicyManager.START_USER_IN_BACKGROUND; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; @@ -151,6 +150,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.os.UserManagerInternal; +import android.os.UserManagerInternal.UserRestrictionsListener; import android.os.storage.StorageManager; import android.provider.ContactsContract.QuickContact; import android.provider.ContactsInternal; @@ -703,6 +703,33 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } }; + protected static class RestrictionsListener implements UserRestrictionsListener { + private Context mContext; + + public RestrictionsListener(Context context) { + mContext = context; + } + + public void onUserRestrictionsChanged(int userId, Bundle newRestrictions, + Bundle prevRestrictions) { + final boolean newlyDisallowed = + newRestrictions.getBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE); + final boolean previouslyDisallowed = + prevRestrictions.getBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE); + final boolean restrictionChanged = (newlyDisallowed != previouslyDisallowed); + + if (restrictionChanged) { + // Notify ManagedProvisioning to update the built-in cross profile intent filters. + Intent intent = new Intent( + DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_CHANGED); + intent.setPackage(MANAGED_PROVISIONING_PKG); + intent.putExtra(Intent.EXTRA_USER_ID, userId); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM); + } + } + } + static class ActiveAdmin { private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features"; private static final String TAG_TEST_ONLY_ADMIN = "test-only-admin"; @@ -1699,6 +1726,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return ActivityManager.getService(); } + ActivityManagerInternal getActivityManagerInternal() { + return LocalServices.getService(ActivityManagerInternal.class); + } + IPackageManager getIPackageManager() { return AppGlobals.getPackageManager(); } @@ -1982,6 +2013,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { LocalServices.addService(DevicePolicyManagerInternal.class, mLocalService); mSetupContentObserver = new SetupContentObserver(mHandler); + + mUserManagerInternal.addUserRestrictionsListener(new RestrictionsListener(mContext)); } /** @@ -8611,14 +8644,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Settings.Secure.USER_SETUP_COMPLETE, 1, userHandle); } - if ((flags & START_USER_IN_BACKGROUND) != 0) { - try { - mInjector.getIActivityManager().startUserInBackground(userHandle); - } catch (RemoteException re) { - // Does not happen, same process - } - } - return user; } catch (Throwable re) { mUserManager.removeUser(userHandle); @@ -8631,6 +8656,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean removeUser(ComponentName who, UserHandle userHandle) { Preconditions.checkNotNull(who, "ComponentName is null"); + Preconditions.checkNotNull(userHandle, "UserHandle is null"); + synchronized (this) { getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); } @@ -8669,6 +8696,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean switchUser(ComponentName who, UserHandle userHandle) { Preconditions.checkNotNull(who, "ComponentName is null"); + synchronized (this) { getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); @@ -8689,8 +8717,40 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public boolean startUserInBackground(ComponentName who, UserHandle userHandle) { + Preconditions.checkNotNull(who, "ComponentName is null"); + Preconditions.checkNotNull(userHandle, "UserHandle is null"); + + synchronized (this) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + } + + final int userId = userHandle.getIdentifier(); + if (isManagedProfile(userId)) { + Log.w(LOG_TAG, "Managed profile cannot be started in background"); + return false; + } + + final long id = mInjector.binderClearCallingIdentity(); + try { + if (!mInjector.getActivityManagerInternal().canStartMoreUsers()) { + Log.w(LOG_TAG, "Cannot start more users in background"); + return false; + } + + return mInjector.getIActivityManager().startUserInBackground(userId); + } catch (RemoteException e) { + // Same process, should not happen. + return false; + } finally { + mInjector.binderRestoreCallingIdentity(id); + } + } + + @Override public boolean stopUser(ComponentName who, UserHandle userHandle) { Preconditions.checkNotNull(who, "ComponentName is null"); + Preconditions.checkNotNull(userHandle, "UserHandle is null"); synchronized (this) { getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java index f9ebd28418cd..bf5822463746 100644 --- a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java +++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java @@ -16,7 +16,11 @@ package com.android.server.backup; -import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAMES; +import static com.android.server.backup.testing.TransportData.backupTransport; +import static com.android.server.backup.testing.TransportData.d2dTransport; +import static com.android.server.backup.testing.TransportData.localTransport; +import static com.android.server.backup.testing.TransportTestUtils.setUpCurrentTransport; +import static com.android.server.backup.testing.TransportTestUtils.setUpTransports; import static com.google.common.truth.Truth.assertThat; @@ -24,6 +28,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; @@ -39,8 +44,8 @@ import android.platform.test.annotations.Presubmit; import android.provider.Settings; import com.android.server.backup.testing.ShadowAppBackupUtils; -import com.android.server.backup.testing.TransportTestUtils; -import com.android.server.backup.testing.TransportTestUtils.TransportData; +import com.android.server.backup.testing.TransportData; +import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; @@ -57,10 +62,10 @@ import org.robolectric.shadows.ShadowContextWrapper; import org.robolectric.shadows.ShadowLog; import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowSettings; +import org.robolectric.shadows.ShadowSystemClock; import java.io.File; import java.util.HashMap; -import java.util.List; import java.util.Map; @RunWith(FrameworkRobolectricTestRunner.class) @@ -73,22 +78,24 @@ import java.util.Map; @Presubmit public class BackupManagerServiceRoboTest { private static final String TAG = "BMSTest"; - private static final String TRANSPORT_NAME = - "com.google.android.gms/.backup.BackupTransportService"; @Mock private TransportManager mTransportManager; private HandlerThread mBackupThread; private ShadowLooper mShadowBackupLooper; private File mBaseStateDir; private File mDataDir; - private RefactoredBackupManagerService mBackupManagerService; private ShadowContextWrapper mShadowContext; private Context mContext; + private TransportData mTransport; + private String mTransportName; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mTransport = backupTransport(); + mTransportName = mTransport.transportName; + mBackupThread = new HandlerThread("backup-test"); mBackupThread.setUncaughtExceptionHandler( (t, e) -> ShadowLog.e(TAG, "Uncaught exception in test thread " + t.getName(), e)); @@ -102,15 +109,6 @@ public class BackupManagerServiceRoboTest { File cacheDir = mContext.getCacheDir(); mBaseStateDir = new File(cacheDir, "base_state_dir"); mDataDir = new File(cacheDir, "data_dir"); - - mBackupManagerService = - new RefactoredBackupManagerService( - mContext, - new Trampoline(mContext), - mBackupThread, - mBaseStateDir, - mDataDir, - mTransportManager); } @After @@ -124,10 +122,12 @@ public class BackupManagerServiceRoboTest { @Test public void testDestinationString() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME))) + when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName))) .thenReturn("destinationString"); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME); + String destination = backupManagerService.getDestinationString(mTransportName); assertThat(destination).isEqualTo("destinationString"); } @@ -135,10 +135,12 @@ public class BackupManagerServiceRoboTest { @Test public void testDestinationString_whenTransportNotRegistered() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME))) + when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName))) .thenThrow(TransportNotRegisteredException.class); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME); + String destination = backupManagerService.getDestinationString(mTransportName); assertThat(destination).isNull(); } @@ -146,12 +148,14 @@ public class BackupManagerServiceRoboTest { @Test public void testDestinationString_withoutPermission() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME))) + when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName))) .thenThrow(TransportNotRegisteredException.class); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); expectThrows( SecurityException.class, - () -> mBackupManagerService.getDestinationString(TRANSPORT_NAME)); + () -> backupManagerService.getDestinationString(mTransportName)); } /* Tests for app eligibility */ @@ -159,24 +163,28 @@ public class BackupManagerServiceRoboTest { @Test public void testIsAppEligibleForBackup_whenAppEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - TransportData transport = - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + TransportMock transportMock = setUpCurrentTransport(mTransportManager, backupTransport()); ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> true; + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - boolean result = mBackupManagerService.isAppEligibleForBackup("app.package"); + boolean result = backupManagerService.isAppEligibleForBackup("app.package"); assertThat(result).isTrue(); + verify(mTransportManager) - .disposeOfTransportClient(eq(transport.transportClientMock), any()); + .disposeOfTransportClient(eq(transportMock.transportClient), any()); } @Test public void testIsAppEligibleForBackup_whenAppNotEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + setUpCurrentTransport(mTransportManager, mTransport); ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false; + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - boolean result = mBackupManagerService.isAppEligibleForBackup("app.package"); + boolean result = backupManagerService.isAppEligibleForBackup("app.package"); assertThat(result).isFalse(); } @@ -184,38 +192,43 @@ public class BackupManagerServiceRoboTest { @Test public void testIsAppEligibleForBackup_withoutPermission() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + setUpCurrentTransport(mTransportManager, mTransport); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); expectThrows( SecurityException.class, - () -> mBackupManagerService.isAppEligibleForBackup("app.package")); + () -> backupManagerService.isAppEligibleForBackup("app.package")); } @Test public void testFilterAppsEligibleForBackup() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - TransportData transport = - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + TransportMock transportMock = setUpCurrentTransport(mTransportManager, mTransport); Map<String, Boolean> packagesMap = new HashMap<>(); packagesMap.put("package.a", true); packagesMap.put("package.b", false); ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = packagesMap::get; + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); String[] packages = packagesMap.keySet().toArray(new String[packagesMap.size()]); - String[] filtered = mBackupManagerService.filterAppsEligibleForBackup(packages); + String[] filtered = backupManagerService.filterAppsEligibleForBackup(packages); assertThat(filtered).asList().containsExactly("package.a"); verify(mTransportManager) - .disposeOfTransportClient(eq(transport.transportClientMock), any()); + .disposeOfTransportClient(eq(transportMock.transportClient), any()); } @Test public void testFilterAppsEligibleForBackup_whenNoneIsEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false; + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); String[] filtered = - mBackupManagerService.filterAppsEligibleForBackup( + backupManagerService.filterAppsEligibleForBackup( new String[] {"package.a", "package.b"}); assertThat(filtered).isEmpty(); @@ -224,12 +237,14 @@ public class BackupManagerServiceRoboTest { @Test public void testFilterAppsEligibleForBackup_withoutPermission() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + setUpCurrentTransport(mTransportManager, mTransport); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); expectThrows( SecurityException.class, () -> - mBackupManagerService.filterAppsEligibleForBackup( + backupManagerService.filterAppsEligibleForBackup( new String[] {"package.a", "package.b"})); } @@ -238,14 +253,12 @@ public class BackupManagerServiceRoboTest { private TransportData mNewTransport; private TransportData mOldTransport; private ComponentName mNewTransportComponent; - private ISelectBackupTransportCallback mCallback; private void setUpForSelectTransport() throws Exception { - List<TransportData> transports = - TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES); - mNewTransport = transports.get(0); - mNewTransportComponent = mNewTransport.transportClientMock.getTransportComponent(); - mOldTransport = transports.get(1); + mNewTransport = backupTransport(); + mNewTransportComponent = mNewTransport.getTransportComponent(); + mOldTransport = d2dTransport(); + setUpTransports(mTransportManager, mNewTransport, mOldTransport, localTransport()); when(mTransportManager.selectTransport(eq(mNewTransport.transportName))) .thenReturn(mOldTransport.transportName); } @@ -254,9 +267,11 @@ public class BackupManagerServiceRoboTest { public void testSelectBackupTransport() throws Exception { setUpForSelectTransport(); mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); String oldTransport = - mBackupManagerService.selectBackupTransport(mNewTransport.transportName); + backupManagerService.selectBackupTransport(mNewTransport.transportName); assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName); assertThat(oldTransport).isEqualTo(mOldTransport.transportName); @@ -266,10 +281,12 @@ public class BackupManagerServiceRoboTest { public void testSelectBackupTransport_withoutPermission() throws Exception { setUpForSelectTransport(); mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); expectThrows( SecurityException.class, - () -> mBackupManagerService.selectBackupTransport(mNewTransport.transportName)); + () -> backupManagerService.selectBackupTransport(mNewTransport.transportName)); } @Test @@ -278,9 +295,11 @@ public class BackupManagerServiceRoboTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent))) .thenReturn(BackupManager.SUCCESS); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); - mBackupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); + backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); mShadowBackupLooper.runToEndOfTasks(); assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName); @@ -293,9 +312,11 @@ public class BackupManagerServiceRoboTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent))) .thenReturn(BackupManager.ERROR_TRANSPORT_UNAVAILABLE); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); - mBackupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); + backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); mShadowBackupLooper.runToEndOfTasks(); assertThat(getSettingsTransport()).isNotEqualTo(mNewTransport.transportName); @@ -304,19 +325,19 @@ public class BackupManagerServiceRoboTest { @Test public void testSelectBackupTransportAsync_whenTransportGetsUnregistered() throws Exception { - TransportTestUtils.setUpTransports( - mTransportManager, new TransportData(TRANSPORT_NAME, null, null)); - ComponentName newTransportComponent = - TransportTestUtils.transportComponentName(TRANSPORT_NAME); + setUpTransports(mTransportManager, mTransport.unregistered()); + ComponentName newTransportComponent = mTransport.getTransportComponent(); mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.registerAndSelectTransport(eq(newTransportComponent))) .thenReturn(BackupManager.SUCCESS); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); - mBackupManagerService.selectBackupTransportAsync(newTransportComponent, callback); + backupManagerService.selectBackupTransportAsync(newTransportComponent, callback); mShadowBackupLooper.runToEndOfTasks(); - assertThat(getSettingsTransport()).isNotEqualTo(TRANSPORT_NAME); + assertThat(getSettingsTransport()).isNotEqualTo(mTransportName); verify(callback).onFailure(anyInt()); } @@ -324,13 +345,14 @@ public class BackupManagerServiceRoboTest { public void testSelectBackupTransportAsync_withoutPermission() throws Exception { setUpForSelectTransport(); mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - ComponentName newTransportComponent = - mNewTransport.transportClientMock.getTransportComponent(); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); + ComponentName newTransportComponent = mNewTransport.getTransportComponent(); expectThrows( SecurityException.class, () -> - mBackupManagerService.selectBackupTransportAsync( + backupManagerService.selectBackupTransportAsync( newTransportComponent, mock(ISelectBackupTransportCallback.class))); } @@ -338,4 +360,55 @@ public class BackupManagerServiceRoboTest { return ShadowSettings.ShadowSecure.getString( mContext.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT); } + + /* Miscellaneous tests */ + + @Test + public void testConstructor_postRegisterTransports() { + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + + createBackupManagerService(); + + mShadowBackupLooper.runToEndOfTasks(); + verify(mTransportManager).registerTransports(); + } + + @Test + public void testConstructor_doesNotRegisterTransportsSynchronously() { + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + + createBackupManagerService(); + + // Operations posted to mBackupThread only run with mShadowBackupLooper.runToEndOfTasks() + verify(mTransportManager, never()).registerTransports(); + } + + private RefactoredBackupManagerService createBackupManagerService() { + return new RefactoredBackupManagerService( + mContext, + new Trampoline(mContext), + mBackupThread, + mBaseStateDir, + mDataDir, + mTransportManager); + } + + private RefactoredBackupManagerService createInitializedBackupManagerService() { + RefactoredBackupManagerService backupManagerService = + new RefactoredBackupManagerService( + mContext, + new Trampoline(mContext), + mBackupThread, + mBaseStateDir, + mDataDir, + mTransportManager); + mShadowBackupLooper.runToEndOfTasks(); + // Handler instances have their own clock, so advancing looper (with runToEndOfTasks()) + // above does NOT advance the handlers' clock, hence whenever a handler post messages with + // specific time to the looper the time of those messages will be before the looper's time. + // To fix this we advance SystemClock as well since that is from where the handlers read + // time. + ShadowSystemClock.setCurrentTimeMillis(mShadowBackupLooper.getScheduler().getCurrentTime()); + return backupManagerService; + } } diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java index acd670f6748c..cf0bc235dc10 100644 --- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java +++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java @@ -16,108 +16,97 @@ package com.android.server.backup; +import static com.android.server.backup.testing.TransportData.genericTransport; +import static com.android.server.backup.testing.TransportTestUtils.mockTransport; +import static com.android.server.backup.testing.TransportTestUtils.setUpTransportsForTransportManager; + import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.shadow.api.Shadow.extract; import static org.testng.Assert.expectThrows; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static java.util.stream.Stream.concat; + import android.annotation.Nullable; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.os.IBinder; -import android.os.RemoteException; import android.platform.test.annotations.Presubmit; -import com.android.internal.backup.IBackupTransport; -import com.android.server.backup.testing.ShadowBackupTransportStub; import com.android.server.backup.testing.ShadowContextImplForBackup; -import com.android.server.backup.testing.ShadowPackageManagerForBackup; -import com.android.server.backup.testing.TransportBoundListenerStub; +import com.android.server.testing.shadows.FrameworkShadowPackageManager; +import com.android.server.backup.testing.TransportData; +import com.android.server.backup.testing.TransportTestUtils.TransportMock; +import com.android.server.backup.transport.OnTransportRegisteredListener; import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportClientManager; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; +import com.android.server.testing.shadows.FrameworkShadowContextImpl; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowLog; -import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowPackageManager; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; @RunWith(FrameworkRobolectricTestRunner.class) @Config( - manifest = Config.NONE, - sdk = 26, - shadows = { - ShadowContextImplForBackup.class, - ShadowBackupTransportStub.class, - ShadowPackageManagerForBackup.class - } + manifest = Config.NONE, + sdk = 26, + shadows = {FrameworkShadowPackageManager.class, FrameworkShadowContextImpl.class} ) @SystemLoaderClasses({TransportManager.class}) @Presubmit public class TransportManagerTest { - private static final String PACKAGE_NAME = "some.package.name"; - private static final String ANOTHER_PACKAGE_NAME = "another.package.name"; + private static final String PACKAGE_A = "some.package.a"; + private static final String PACKAGE_B = "some.package.b"; - private TransportInfo mTransport1; - private TransportInfo mTransport2; + @Mock private OnTransportRegisteredListener mListener; + @Mock private TransportClientManager mTransportClientManager; + private TransportData mTransportA1; + private TransportData mTransportA2; + private TransportData mTransportB1; - private ShadowPackageManager mPackageManagerShadow; - - private final TransportBoundListenerStub mTransportBoundListenerStub = - new TransportBoundListenerStub(true); + private ShadowPackageManager mShadowPackageManager; + private Context mContext; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - ShadowLog.stream = System.out; - - mPackageManagerShadow = - (ShadowPackageManagerForBackup) + mShadowPackageManager = + (FrameworkShadowPackageManager) extract(RuntimeEnvironment.application.getPackageManager()); + mContext = RuntimeEnvironment.application.getApplicationContext(); - mTransport1 = new TransportInfo( - PACKAGE_NAME, - "transport1.name", - new Intent(), - "currentDestinationString", - new Intent(), - "dataManagementLabel"); - mTransport2 = new TransportInfo( - PACKAGE_NAME, - "transport2.name", - new Intent(), - "currentDestinationString", - new Intent(), - "dataManagementLabel"); - - ShadowContextImplForBackup.sComponentBinderMap.put(mTransport1.componentName, - mTransport1.binder); - ShadowContextImplForBackup.sComponentBinderMap.put(mTransport2.componentName, - mTransport2.binder); - ShadowBackupTransportStub.sBinderTransportMap.put( - mTransport1.binder, mTransport1.binderInterface); - ShadowBackupTransportStub.sBinderTransportMap.put( - mTransport2.binder, mTransport2.binderInterface); + mTransportA1 = genericTransport(PACKAGE_A, "TransportFoo"); + mTransportA2 = genericTransport(PACKAGE_A, "TransportBar"); + mTransportB1 = genericTransport(PACKAGE_B, "TransportBaz"); } @After @@ -126,560 +115,441 @@ public class TransportManagerTest { } @Test - public void onPackageAdded_bindsToAllTransports() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Arrays.asList( - mTransport1.componentName, mTransport2.componentName)), - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isTrue(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } + public void testRegisterTransports() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpPackage(PACKAGE_B, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2, mTransportB1); + TransportManager transportManager = + createTransportManager(mTransportA1, mTransportA2, mTransportB1); - @Test - public void onPackageAdded_oneTransportUnavailable_bindsToOnlyOneTransport() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - ShadowContextImplForBackup.sUnbindableComponents.add(mTransport1.componentName); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Arrays.asList( - mTransport1.componentName, mTransport2.componentName)), - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Collections.singleton(mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Collections.singleton(mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); + transportManager.registerTransports(); + + assertRegisteredTransports( + transportManager, asList(mTransportA1, mTransportA2, mTransportB1)); + + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); + verify(mListener) + .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName); + verify(mListener) + .onTransportRegistered(mTransportB1.transportName, mTransportB1.transportDirName); } @Test - public void onPackageAdded_whitelistIsNull_doesNotBindToTransports() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - null /* whitelist */, - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).isEmpty(); - assertThat(transportManager.getBoundTransportNames()).isEmpty(); - assertThat(mTransportBoundListenerStub.isCalled()).isFalse(); + public void + testRegisterTransports_whenOneTransportUnavailable_doesNotRegisterUnavailableTransport() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + TransportData transport1 = mTransportA1.unavailable(); + TransportData transport2 = mTransportA2; + setUpTransports(transport1, transport2); + TransportManager transportManager = createTransportManager(transport1, transport2); + + transportManager.registerTransports(); + + assertRegisteredTransports(transportManager, singletonList(transport2)); + verify(mListener, never()) + .onTransportRegistered(transport1.transportName, transport1.transportDirName); + verify(mListener) + .onTransportRegistered(transport2.transportName, transport2.transportDirName); } @Test - public void onPackageAdded_onlyOneTransportWhitelisted_onlyConnectsToWhitelistedTransport() + public void testRegisterTransports_whenWhitelistIsEmpty_doesNotRegisterTransports() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Collections.singleton(mTransport2.componentName)), - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Collections.singleton(mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Collections.singleton(mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(null); - @Test - public void onPackageAdded_appIsNotPrivileged_doesNotBindToTransports() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), 0); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Arrays.asList( - mTransport1.componentName, mTransport2.componentName)), - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).isEmpty(); - assertThat(transportManager.getBoundTransportNames()).isEmpty(); - assertThat(mTransportBoundListenerStub.isCalled()).isFalse(); + transportManager.registerTransports(); + + assertRegisteredTransports(transportManager, emptyList()); + verify(mListener, never()).onTransportRegistered(any(), any()); } @Test - public void onPackageRemoved_transportsUnbound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void + testRegisterTransports_whenOnlyOneTransportWhitelisted_onlyRegistersWhitelistedTransport() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(null, mTransportA1); - transportManager.onPackageRemoved(PACKAGE_NAME); + transportManager.registerTransports(); - assertThat(transportManager.getAllTransportComponents()).isEmpty(); - assertThat(transportManager.getBoundTransportNames()).isEmpty(); + assertRegisteredTransports(transportManager, singletonList(mTransportA1)); + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); + verify(mListener, never()) + .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName); } @Test - public void onPackageRemoved_incorrectPackageName_nothingHappens() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testRegisterTransports_whenAppIsNotPrivileged_doesNotRegisterTransports() + throws Exception { + // Note ApplicationInfo.PRIVATE_FLAG_PRIVILEGED is missing from flags + setUpPackage(PACKAGE_A, 0); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = + createTransportManager(null, mTransportA1, mTransportA2); - transportManager.onPackageRemoved(ANOTHER_PACKAGE_NAME); + transportManager.registerTransports(); - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); + assertRegisteredTransports(transportManager, emptyList()); + verify(mListener, never()).onTransportRegistered(any(), any()); } @Test - public void onPackageChanged_oneComponentChanged_onlyOneTransportRebound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageChanged(PACKAGE_NAME, new String[]{mTransport2.name}); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } + public void testOnPackageAdded_registerTransports() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); - @Test - public void onPackageChanged_nothingChanged_noTransportsRebound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageChanged(PACKAGE_NAME, new String[0]); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isFalse(); - } + transportManager.onPackageAdded(PACKAGE_A); - @Test - public void onPackageChanged_unexpectedComponentChanged_noTransportsRebound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageChanged(PACKAGE_NAME, new String[]{"unexpected.component"}); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isFalse(); + assertRegisteredTransports(transportManager, asList(mTransportA1)); + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); } @Test - public void onPackageChanged_transportsRebound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageChanged(PACKAGE_NAME, new String[]{mTransport2.name}); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } + public void testOnPackageRemoved_unregisterTransports() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpPackage(PACKAGE_B, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportB1); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportB1); + transportManager.registerTransports(); - @Test - public void getTransportBinder_returnsCorrectBinder() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - assertThat(transportManager.getTransportBinder(mTransport1.name)).isEqualTo( - mTransport1.binderInterface); - assertThat(transportManager.getTransportBinder(mTransport2.name)).isEqualTo( - mTransport2.binderInterface); + transportManager.onPackageRemoved(PACKAGE_A); + + assertRegisteredTransports(transportManager, singletonList(mTransportB1)); } @Test - public void getTransportBinder_incorrectTransportName_returnsNull() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testOnPackageRemoved_whenUnknownPackage_nothingHappens() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); + + transportManager.onPackageRemoved(PACKAGE_A + "unknown"); - assertThat(transportManager.getTransportBinder("incorrect.transport")).isNull(); + assertRegisteredTransports(transportManager, singletonList(mTransportA1)); } @Test - public void getTransportBinder_oneTransportUnavailable_returnsCorrectBinder() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2), - Collections.singletonList(mTransport1), mTransport1.name); + public void testOnPackageChanged_whenOneComponentChanged_onlyOneTransportReRegistered() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); + // Reset listener to verify calls after registerTransports() above + reset(mListener); + + transportManager.onPackageChanged( + PACKAGE_A, mTransportA1.getTransportComponent().getClassName()); - assertThat(transportManager.getTransportBinder(mTransport1.name)).isNull(); - assertThat(transportManager.getTransportBinder(mTransport2.name)).isEqualTo( - mTransport2.binderInterface); + assertRegisteredTransports(transportManager, asList(mTransportA1, mTransportA2)); + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); + verify(mListener, never()) + .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName); } @Test - public void getCurrentTransport_selectTransportNotCalled_returnsDefaultTransport() + public void testOnPackageChanged_whenNoComponentsChanged_doesNotRegisterTransports() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); + reset(mListener); - assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport1.name); + transportManager.onPackageChanged(PACKAGE_A); + + assertRegisteredTransports(transportManager, singletonList(mTransportA1)); + verify(mListener, never()).onTransportRegistered(any(), any()); } @Test - public void getCurrentTransport_selectTransportCalled_returnsCorrectTransport() + public void testOnPackageChanged_whenUnknownComponentChanged_noTransportsRegistered() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport1.name); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); + reset(mListener); - transportManager.selectTransport(mTransport2.name); + transportManager.onPackageChanged(PACKAGE_A, PACKAGE_A + ".UnknownComponent"); - assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport2.name); + assertRegisteredTransports(transportManager, singletonList(mTransportA1)); + verify(mListener, never()).onTransportRegistered(any(), any()); } @Test - public void getCurrentTransportBinder_returnsCorrectBinder() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testOnPackageChanged_reRegisterTransports() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); + reset(mListener); - assertThat(transportManager.getCurrentTransportBinder()) - .isEqualTo(mTransport1.binderInterface); + transportManager.onPackageChanged( + PACKAGE_A, + mTransportA1.getTransportComponent().getClassName(), + mTransportA2.getTransportComponent().getClassName()); + + assertRegisteredTransports(transportManager, asList(mTransportA1, mTransportA2)); + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); + verify(mListener) + .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName); } @Test - public void getCurrentTransportBinder_transportNotBound_returnsNull() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2), - Collections.singletonList(mTransport1), mTransport2.name); + public void testGetCurrentTransportName_whenSelectTransportNotCalled_returnsDefaultTransport() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); - transportManager.selectTransport(mTransport1.name); + String currentTransportName = transportManager.getCurrentTransportName(); - assertThat(transportManager.getCurrentTransportBinder()).isNull(); + assertThat(currentTransportName).isEqualTo(mTransportA1.transportName); } @Test - public void getTransportName_returnsCorrectTransportName() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - assertThat(transportManager.getTransportName(mTransport1.binderInterface)) - .isEqualTo(mTransport1.name); - assertThat(transportManager.getTransportName(mTransport2.binderInterface)) - .isEqualTo(mTransport2.name); - } + public void testGetCurrentTransport_whenSelectTransportCalled_returnsSelectedTransport() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); + transportManager.selectTransport(mTransportA2.transportName); - @Test - public void getTransportName_transportNotBound_returnsNull() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2), - Collections.singletonList(mTransport1), mTransport1.name); + String currentTransportName = transportManager.getCurrentTransportName(); - assertThat(transportManager.getTransportName(mTransport1.binderInterface)).isNull(); - assertThat(transportManager.getTransportName(mTransport2.binderInterface)) - .isEqualTo(mTransport2.name); + assertThat(currentTransportName).isEqualTo(mTransportA2.transportName); } @Test - public void getTransportWhitelist_returnsCorrectWhiteList() throws Exception { - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Arrays.asList(mTransport1.componentName, mTransport2.componentName)), - mTransport1.name, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - - assertThat(transportManager.getTransportWhitelist()).containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - } + public void testGetTransportWhitelist() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); - @Test - public void getTransportWhitelist_whiteListIsNull_returnsEmptyArray() throws Exception { - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - null /* whitelist */, - mTransport1.name, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - - assertThat(transportManager.getTransportWhitelist()).isEmpty(); + Set<ComponentName> transportWhitelist = transportManager.getTransportWhitelist(); + + assertThat(transportWhitelist) + .containsExactlyElementsIn( + asList( + mTransportA1.getTransportComponent(), + mTransportA2.getTransportComponent())); } @Test - public void selectTransport_setsTransportCorrectlyAndReturnsPreviousTransport() - throws Exception { - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - null /* whitelist */, - mTransport1.name, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - - assertThat(transportManager.selectTransport(mTransport2.name)).isEqualTo(mTransport1.name); - assertThat(transportManager.selectTransport(mTransport1.name)).isEqualTo(mTransport2.name); + public void testSelectTransport() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = + createTransportManager(null, mTransportA1, mTransportA2); + + String transport1 = transportManager.selectTransport(mTransportA1.transportName); + String transport2 = transportManager.selectTransport(mTransportA2.transportName); + + assertThat(transport1).isNull(); + assertThat(transport2).isEqualTo(mTransportA1.transportName); } @Test - public void getTransportClient_forRegisteredTransport_returnCorrectly() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testGetTransportClient_forRegisteredTransport() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); TransportClient transportClient = - transportManager.getTransportClient(mTransport1.name, "caller"); + transportManager.getTransportClient(mTransportA1.transportName, "caller"); - assertThat(transportClient.getTransportComponent()).isEqualTo(mTransport1.componentName); + assertThat(transportClient.getTransportComponent()) + .isEqualTo(mTransportA1.getTransportComponent()); } @Test - public void getTransportClient_forOldNameOfTransportThatChangedName_returnsNull() + public void testGetTransportClient_forOldNameOfTransportThatChangedName_returnsNull() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); transportManager.updateTransportAttributes( - mTransport1.componentName, "newName", null, "destinationString", null, null); + mTransportA1.getTransportComponent(), + "newName", + null, + "destinationString", + null, + null); TransportClient transportClient = - transportManager.getTransportClient(mTransport1.name, "caller"); + transportManager.getTransportClient(mTransportA1.transportName, "caller"); assertThat(transportClient).isNull(); } @Test - public void getTransportClient_forNewNameOfTransportThatChangedName_returnsCorrectly() + public void testGetTransportClient_forNewNameOfTransportThatChangedName_returnsCorrectly() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); transportManager.updateTransportAttributes( - mTransport1.componentName, "newName", null, "destinationString", null, null); + mTransportA1.getTransportComponent(), + "newName", + null, + "destinationString", + null, + null); - TransportClient transportClient = - transportManager.getTransportClient("newName", "caller"); + TransportClient transportClient = transportManager.getTransportClient("newName", "caller"); - assertThat(transportClient.getTransportComponent()).isEqualTo(mTransport1.componentName); + assertThat(transportClient.getTransportComponent()) + .isEqualTo(mTransportA1.getTransportComponent()); } @Test - public void getTransportName_forTransportThatChangedName_returnsNewName() - throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testGetTransportName_forTransportThatChangedName_returnsNewName() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); transportManager.updateTransportAttributes( - mTransport1.componentName, "newName", null, "destinationString", null, null); + mTransportA1.getTransportComponent(), + "newName", + null, + "destinationString", + null, + null); - String transportName = transportManager.getTransportName(mTransport1.componentName); + String transportName = + transportManager.getTransportName(mTransportA1.getTransportComponent()); assertThat(transportName).isEqualTo("newName"); } @Test - public void isTransportRegistered_returnsCorrectly() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Collections.singletonList(mTransport1), - Collections.singletonList(mTransport2), - mTransport1.name); + public void testIsTransportRegistered() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); + + boolean isTransportA1Registered = + transportManager.isTransportRegistered(mTransportA1.transportName); + boolean isTransportA2Registered = + transportManager.isTransportRegistered(mTransportA2.transportName); - assertThat(transportManager.isTransportRegistered(mTransport1.name)).isTrue(); - assertThat(transportManager.isTransportRegistered(mTransport2.name)).isFalse(); + assertThat(isTransportA1Registered).isTrue(); + assertThat(isTransportA2Registered).isFalse(); } @Test - public void getTransportAttributes_forRegisteredTransport_returnsCorrectValues() + public void testGetTransportAttributes_forRegisteredTransport_returnsCorrectValues() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Collections.singletonList(mTransport1), - mTransport1.name); - - assertThat(transportManager.getTransportConfigurationIntent(mTransport1.name)) - .isEqualTo(mTransport1.binderInterface.configurationIntent()); - assertThat(transportManager.getTransportDataManagementIntent(mTransport1.name)) - .isEqualTo(mTransport1.binderInterface.dataManagementIntent()); - assertThat(transportManager.getTransportDataManagementLabel(mTransport1.name)) - .isEqualTo(mTransport1.binderInterface.dataManagementLabel()); - assertThat(transportManager.getTransportDirName(mTransport1.name)) - .isEqualTo(mTransport1.binderInterface.transportDirName()); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); + + Intent configurationIntent = + transportManager.getTransportConfigurationIntent(mTransportA1.transportName); + Intent dataManagementIntent = + transportManager.getTransportDataManagementIntent(mTransportA1.transportName); + String dataManagementLabel = + transportManager.getTransportDataManagementLabel(mTransportA1.transportName); + String transportDirName = transportManager.getTransportDirName(mTransportA1.transportName); + + assertThat(configurationIntent).isEqualTo(mTransportA1.configurationIntent); + assertThat(dataManagementIntent).isEqualTo(mTransportA1.dataManagementIntent); + assertThat(dataManagementLabel).isEqualTo(mTransportA1.dataManagementLabel); + assertThat(transportDirName).isEqualTo(mTransportA1.transportDirName); } @Test - public void getTransportAttributes_forUnregisteredTransport_throws() - throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Collections.singletonList(mTransport1), - Collections.singletonList(mTransport2), - mTransport1.name); + public void testGetTransportAttributes_forUnregisteredTransport_throws() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); expectThrows( TransportNotRegisteredException.class, - () -> transportManager.getTransportConfigurationIntent(mTransport2.name)); + () -> transportManager.getTransportConfigurationIntent(mTransportA2.transportName)); expectThrows( TransportNotRegisteredException.class, - () -> transportManager.getTransportDataManagementIntent( - mTransport2.name)); + () -> + transportManager.getTransportDataManagementIntent( + mTransportA2.transportName)); expectThrows( TransportNotRegisteredException.class, - () -> transportManager.getTransportDataManagementLabel(mTransport2.name)); + () -> transportManager.getTransportDataManagementLabel(mTransportA2.transportName)); expectThrows( TransportNotRegisteredException.class, - () -> transportManager.getTransportDirName(mTransport2.name)); + () -> transportManager.getTransportDirName(mTransportA2.transportName)); + } + + private List<TransportMock> setUpTransports(TransportData... transports) throws Exception { + setUpTransportsForTransportManager(mShadowPackageManager, transports); + List<TransportMock> transportMocks = new ArrayList<>(transports.length); + for (TransportData transport : transports) { + TransportMock transportMock = mockTransport(transport); + when(mTransportClientManager.getTransportClient( + eq(transport.getTransportComponent()), any())) + .thenReturn(transportMock.transportClient); + transportMocks.add(transportMock); + } + return transportMocks; } - private void setUpPackageWithTransports(String packageName, List<TransportInfo> transports, - int flags) throws Exception { + private void setUpPackage(String packageName, int flags) { PackageInfo packageInfo = new PackageInfo(); packageInfo.packageName = packageName; packageInfo.applicationInfo = new ApplicationInfo(); packageInfo.applicationInfo.privateFlags = flags; - - mPackageManagerShadow.addPackage(packageInfo); - - List<ResolveInfo> transportsInfo = new ArrayList<>(); - for (TransportInfo transport : transports) { - ResolveInfo info = new ResolveInfo(); - info.serviceInfo = new ServiceInfo(); - info.serviceInfo.packageName = packageName; - info.serviceInfo.name = transport.name; - transportsInfo.add(info); - } - - Intent intent = new Intent(TransportManager.SERVICE_ACTION_TRANSPORT_HOST); - intent.setPackage(packageName); - - mPackageManagerShadow.addResolveInfoForIntent(intent, transportsInfo); - } - - private TransportManager createTransportManagerAndSetUpTransports( - List<TransportInfo> availableTransports, String defaultTransportName) throws Exception { - return createTransportManagerAndSetUpTransports(availableTransports, - Collections.<TransportInfo>emptyList(), defaultTransportName); + mShadowPackageManager.addPackage(packageInfo); } - private TransportManager createTransportManagerAndSetUpTransports( - List<TransportInfo> availableTransports, List<TransportInfo> unavailableTransports, - String defaultTransportName) - throws Exception { - List<String> availableTransportsNames = new ArrayList<>(); - List<ComponentName> availableTransportsComponentNames = new ArrayList<>(); - for (TransportInfo transport : availableTransports) { - availableTransportsNames.add(transport.name); - availableTransportsComponentNames.add(transport.componentName); - } - - List<ComponentName> allTransportsComponentNames = new ArrayList<>(); - allTransportsComponentNames.addAll(availableTransportsComponentNames); - for (TransportInfo transport : unavailableTransports) { - allTransportsComponentNames.add(transport.componentName); - } - - for (TransportInfo transport : unavailableTransports) { - ShadowContextImplForBackup.sUnbindableComponents.add(transport.componentName); - } - - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(allTransportsComponentNames), - defaultTransportName, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - availableTransportsComponentNames); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - availableTransportsNames); - for (TransportInfo transport : availableTransports) { - assertThat(mTransportBoundListenerStub.isCalledForTransport(transport.binderInterface)) - .isTrue(); - } - for (TransportInfo transport : unavailableTransports) { - assertThat(mTransportBoundListenerStub.isCalledForTransport(transport.binderInterface)) - .isFalse(); - } - - mTransportBoundListenerStub.resetState(); - + private TransportManager createTransportManager( + @Nullable TransportData selectedTransport, TransportData... transports) { + Set<ComponentName> whitelist = + concat(Stream.of(selectedTransport), Stream.of(transports)) + .filter(Objects::nonNull) + .map(TransportData::getTransportComponent) + .collect(toSet()); + TransportManager transportManager = + new TransportManager( + mContext, + whitelist, + selectedTransport != null ? selectedTransport.transportName : null, + mTransportClientManager); + transportManager.setOnTransportRegisteredListener(mListener); return transportManager; } - private static class TransportInfo { - public final String packageName; - public final String name; - public final ComponentName componentName; - public final IBackupTransport binderInterface; - public final IBinder binder; - - TransportInfo( - String packageName, - String name, - @Nullable Intent configurationIntent, - String currentDestinationString, - @Nullable Intent dataManagementIntent, - String dataManagementLabel) { - this.packageName = packageName; - this.name = name; - this.componentName = new ComponentName(packageName, name); - this.binder = mock(IBinder.class); - IBackupTransport transport = mock(IBackupTransport.class); - try { - when(transport.name()).thenReturn(name); - when(transport.configurationIntent()).thenReturn(configurationIntent); - when(transport.currentDestinationString()).thenReturn(currentDestinationString); - when(transport.dataManagementIntent()).thenReturn(dataManagementIntent); - when(transport.dataManagementLabel()).thenReturn(dataManagementLabel); - } catch (RemoteException e) { - // Only here to mock methods that throw RemoteException - } - this.binderInterface = transport; - } + private void assertRegisteredTransports( + TransportManager transportManager, List<TransportData> transports) { + assertThat(transportManager.getRegisteredTransportComponents()) + .asList() + .containsExactlyElementsIn( + transports + .stream() + .map(TransportData::getTransportComponent) + .collect(toList())); + assertThat(transportManager.getRegisteredTransportNames()) + .asList() + .containsExactlyElementsIn( + transports.stream().map(t -> t.transportName).collect(toList())); } - } diff --git a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java index dfca9010130f..ace0441c8a4a 100644 --- a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java +++ b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java @@ -19,8 +19,10 @@ package com.android.server.backup.internal; import static android.app.backup.BackupTransport.TRANSPORT_ERROR; import static android.app.backup.BackupTransport.TRANSPORT_OK; -import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAME; -import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAMES; +import static com.android.server.backup.testing.TransportData.backupTransport; +import static com.android.server.backup.testing.TransportData.d2dTransport; +import static com.android.server.backup.testing.TransportData.localTransport; +import static com.android.server.backup.testing.TransportTestUtils.setUpTransports; import static com.google.common.truth.Truth.assertThat; @@ -28,7 +30,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -44,7 +45,8 @@ import com.android.internal.backup.IBackupTransport; import com.android.server.backup.RefactoredBackupManagerService; import com.android.server.backup.TransportManager; import com.android.server.backup.testing.TransportTestUtils; -import com.android.server.backup.testing.TransportTestUtils.TransportData; +import com.android.server.backup.testing.TransportData; +import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.TransportClient; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; @@ -58,7 +60,10 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import java.io.File; +import java.util.Arrays; +import java.util.Iterator; import java.util.List; +import java.util.stream.Stream; @RunWith(FrameworkRobolectricTestRunner.class) @Config(manifest = Config.NONE, sdk = 26) @@ -68,16 +73,21 @@ public class PerformInitializeTaskTest { @Mock private RefactoredBackupManagerService mBackupManagerService; @Mock private TransportManager mTransportManager; @Mock private OnTaskFinishedListener mListener; - @Mock private IBackupTransport mTransport; + @Mock private IBackupTransport mTransportBinder; @Mock private IBackupObserver mObserver; @Mock private AlarmManager mAlarmManager; @Mock private PendingIntent mRunInitIntent; private File mBaseStateDir; + private TransportData mTransport; + private String mTransportName; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mTransport = backupTransport(); + mTransportName = mTransport.transportName; + Application context = RuntimeEnvironment.application; mBaseStateDir = new File(context.getCacheDir(), "base_state_dir"); assertThat(mBaseStateDir.mkdir()).isTrue(); @@ -88,82 +98,76 @@ public class PerformInitializeTaskTest { @Test public void testRun_callsTransportCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransport).initializeDevice(); - verify(mTransport).finishBackup(); + verify(mTransportBinder).initializeDevice(); + verify(mTransportBinder).finishBackup(); } @Test public void testRun_callsBackupManagerCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); verify(mBackupManagerService) - .recordInitPending( - false, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME)); + .recordInitPending(false, mTransportName, mTransport.transportDirName); verify(mBackupManagerService) - .resetBackupState( - eq( - new File( - mBaseStateDir, - TransportTestUtils.transportDirName(TRANSPORT_NAME)))); + .resetBackupState(eq(new File(mBaseStateDir, mTransport.transportDirName))); } @Test public void testRun_callsObserverAndListenerCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_OK)); + verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_OK)); verify(mObserver).backupFinished(eq(TRANSPORT_OK)); verify(mListener).onFinished(any()); } @Test public void testRun_whenInitializeDeviceFails() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_ERROR, 0); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransport).initializeDevice(); - verify(mTransport, never()).finishBackup(); + verify(mTransportBinder).initializeDevice(); + verify(mTransportBinder, never()).finishBackup(); verify(mBackupManagerService) - .recordInitPending( - true, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME)); + .recordInitPending(true, mTransportName, mTransport.transportDirName); } @Test public void testRun_whenInitializeDeviceFails_callsObserverAndListenerCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_ERROR, 0); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR)); + verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_ERROR)); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); verify(mListener).onFinished(any()); } @Test public void testRun_whenInitializeDeviceFails_schedulesAlarm() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_ERROR, 0); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); @@ -172,37 +176,36 @@ public class PerformInitializeTaskTest { @Test public void testRun_whenFinishBackupFails() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransport).initializeDevice(); - verify(mTransport).finishBackup(); + verify(mTransportBinder).initializeDevice(); + verify(mTransportBinder).finishBackup(); verify(mBackupManagerService) - .recordInitPending( - true, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME)); + .recordInitPending(true, mTransportName, mTransport.transportDirName); } @Test public void testRun_whenFinishBackupFails_callsObserverAndListenerCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR)); + verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_ERROR)); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); verify(mListener).onFinished(any()); } @Test public void testRun_whenFinishBackupFails_schedulesAlarm() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); @@ -211,64 +214,76 @@ public class PerformInitializeTaskTest { @Test public void testRun_whenOnlyOneTransportFails() throws Exception { - List<TransportData> transports = - TransportTestUtils.setUpTransports( - mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); - configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0); - configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK); + TransportData transport1 = backupTransport(); + TransportData transport2 = d2dTransport(); + List<TransportMock> transportMocks = + setUpTransports(mTransportManager, transport1, transport2); + configureTransport(transportMocks.get(0).transport, TRANSPORT_ERROR, 0); + configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK); PerformInitializeTask performInitializeTask = - createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + createPerformInitializeTask(transport1.transportName, transport2.transportName); performInitializeTask.run(); - verify(transports.get(1).transportMock).initializeDevice(); - verify(mObserver).onResult(eq(TRANSPORT_NAMES[0]), eq(TRANSPORT_ERROR)); - verify(mObserver).onResult(eq(TRANSPORT_NAMES[1]), eq(TRANSPORT_OK)); + verify(transportMocks.get(1).transport).initializeDevice(); + verify(mObserver).onResult(eq(transport1.transportName), eq(TRANSPORT_ERROR)); + verify(mObserver).onResult(eq(transport2.transportName), eq(TRANSPORT_OK)); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); } @Test public void testRun_withMultipleTransports() throws Exception { - List<TransportData> transports = - TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES); - configureTransport(transports.get(0).transportMock, TRANSPORT_OK, TRANSPORT_OK); - configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK); - configureTransport(transports.get(2).transportMock, TRANSPORT_OK, TRANSPORT_OK); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAMES); + List<TransportMock> transportMocks = + setUpTransports( + mTransportManager, backupTransport(), d2dTransport(), localTransport()); + configureTransport(transportMocks.get(0).transport, TRANSPORT_OK, TRANSPORT_OK); + configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK); + configureTransport(transportMocks.get(2).transport, TRANSPORT_OK, TRANSPORT_OK); + String[] transportNames = + Stream.of(new TransportData[] {backupTransport(), d2dTransport(), localTransport()}) + .map(t -> t.transportName) + .toArray(String[]::new); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(transportNames); performInitializeTask.run(); - for (TransportData transport : transports) { + Iterator<TransportData> transportsIterator = + Arrays.asList( + new TransportData[] { + backupTransport(), d2dTransport(), localTransport() + }) + .iterator(); + for (TransportMock transportMock : transportMocks) { + TransportData transport = transportsIterator.next(); verify(mTransportManager).getTransportClient(eq(transport.transportName), any()); verify(mTransportManager) - .disposeOfTransportClient(eq(transport.transportClientMock), any()); + .disposeOfTransportClient(eq(transportMock.transportClient), any()); } } @Test public void testRun_whenOnlyOneTransportFails_disposesAllTransports() throws Exception { - List<TransportData> transports = - TransportTestUtils.setUpTransports( - mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); - configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0); - configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK); + TransportData transport1 = backupTransport(); + TransportData transport2 = d2dTransport(); + List<TransportMock> transportMocks = + setUpTransports(mTransportManager, transport1, transport2); + configureTransport(transportMocks.get(0).transport, TRANSPORT_ERROR, 0); + configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK); PerformInitializeTask performInitializeTask = - createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + createPerformInitializeTask(transport1.transportName, transport2.transportName); performInitializeTask.run(); verify(mTransportManager) - .disposeOfTransportClient(eq(transports.get(0).transportClientMock), any()); + .disposeOfTransportClient(eq(transportMocks.get(0).transportClient), any()); verify(mTransportManager) - .disposeOfTransportClient(eq(transports.get(1).transportClientMock), any()); + .disposeOfTransportClient(eq(transportMocks.get(1).transportClient), any()); } @Test public void testRun_whenTransportNotRegistered() throws Exception { - TransportTestUtils.setUpTransports( - mTransportManager, new TransportData(TRANSPORT_NAME, null, null)); - - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransports(mTransportManager, mTransport.unregistered()); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); @@ -279,16 +294,15 @@ public class PerformInitializeTaskTest { @Test public void testRun_whenOnlyOneTransportNotRegistered() throws Exception { - List<TransportData> transports = - TransportTestUtils.setUpTransports( - mTransportManager, - new TransportData(TRANSPORT_NAMES[0], null, null), - new TransportData(TRANSPORT_NAMES[1])); - String registeredTransportName = transports.get(1).transportName; - IBackupTransport registeredTransport = transports.get(1).transportMock; - TransportClient registeredTransportClient = transports.get(1).transportClientMock; + TransportData transport1 = backupTransport().unregistered(); + TransportData transport2 = d2dTransport(); + List<TransportMock> transportMocks = + setUpTransports(mTransportManager, transport1, transport2); + String registeredTransportName = transport2.transportName; + IBackupTransport registeredTransport = transportMocks.get(1).transport; + TransportClient registeredTransportClient = transportMocks.get(1).transportClient; PerformInitializeTask performInitializeTask = - createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + createPerformInitializeTask(transport1.transportName, transport2.transportName); performInitializeTask.run(); @@ -299,25 +313,24 @@ public class PerformInitializeTaskTest { @Test public void testRun_whenTransportNotAvailable() throws Exception { - TransportClient transportClient = mock(TransportClient.class); - TransportTestUtils.setUpTransports( - mTransportManager, new TransportData(TRANSPORT_NAME, null, transportClient)); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + TransportMock transportMock = setUpTransport(mTransport.unavailable()); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransportManager).disposeOfTransportClient(eq(transportClient), any()); + verify(mTransportManager) + .disposeOfTransportClient(eq(transportMock.transportClient), any()); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); verify(mListener).onFinished(any()); } @Test public void testRun_whenTransportThrowsDeadObjectException() throws Exception { - TransportClient transportClient = mock(TransportClient.class); - TransportTestUtils.setUpTransports( - mTransportManager, new TransportData(TRANSPORT_NAME, mTransport, transportClient)); - when(mTransport.initializeDevice()).thenThrow(DeadObjectException.class); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + TransportMock transportMock = setUpTransport(mTransport); + IBackupTransport transport = transportMock.transport; + TransportClient transportClient = transportMock.transportClient; + when(transport.initializeDevice()).thenThrow(DeadObjectException.class); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); @@ -343,9 +356,10 @@ public class PerformInitializeTaskTest { when(transportMock.finishBackup()).thenReturn(finishBackupStatus); } - private void setUpTransport(String transportName) throws Exception { - TransportTestUtils.setUpTransport( - mTransportManager, - new TransportData(transportName, mTransport, mock(TransportClient.class))); + private TransportMock setUpTransport(TransportData transport) throws Exception { + TransportMock transportMock = + TransportTestUtils.setUpTransport(mTransportManager, transport); + mTransportBinder = transportMock.transport; + return transportMock; } } diff --git a/services/robotests/src/com/android/server/backup/testing/TestUtils.java b/services/robotests/src/com/android/server/backup/testing/TestUtils.java new file mode 100644 index 000000000000..1be298d2c8c5 --- /dev/null +++ b/services/robotests/src/com/android/server/backup/testing/TestUtils.java @@ -0,0 +1,68 @@ +/* + * 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 com.android.server.backup.testing; + +import com.android.internal.util.FunctionalUtils.ThrowingRunnable; + +import java.util.concurrent.Callable; + +public class TestUtils { + /** + * Calls {@link Runnable#run()} and returns if no exception is thrown. Otherwise, if the + * exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException} and + * throw. + * + * <p><b>Warning:</b>DON'T use this outside tests. A wrapped checked exception is just a failure + * in a test. + */ + public static void uncheck(ThrowingRunnable runnable) { + try { + runnable.runOrThrow(); + } catch (Exception e) { + throw wrapIfChecked(e); + } + } + + /** + * Calls {@link Callable#call()} and returns the value if no exception is thrown. Otherwise, if + * the exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException} + * and throw. + * + * <p><b>Warning:</b>DON'T use this outside tests. A wrapped checked exception is just a failure + * in a test. + */ + public static <T> T uncheck(Callable<T> callable) { + try { + return callable.call(); + } catch (Exception e) { + throw wrapIfChecked(e); + } + } + + /** + * Wrap {@code e} in a {@link RuntimeException} only if it's not one already, in which case it's + * returned. + */ + public static RuntimeException wrapIfChecked(Exception e) { + if (e instanceof RuntimeException) { + return (RuntimeException) e; + } + return new RuntimeException(e); + } + + private TestUtils() {} +} diff --git a/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java b/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java deleted file mode 100644 index 84ac2c212854..000000000000 --- a/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.backup.testing; - -import com.android.internal.backup.IBackupTransport; -import com.android.server.backup.TransportManager; - -import java.util.HashSet; -import java.util.Set; - -/** - * Stub implementation of TransportBoundListener, which returns given result and can tell whether - * it was called for given transport. - */ -public class TransportBoundListenerStub implements - TransportManager.TransportBoundListener { - private boolean mAlwaysReturnSuccess; - private Set<IBackupTransport> mTransportsCalledFor = new HashSet<>(); - - public TransportBoundListenerStub(boolean alwaysReturnSuccess) { - this.mAlwaysReturnSuccess = alwaysReturnSuccess; - } - - @Override - public boolean onTransportBound(IBackupTransport binder) { - mTransportsCalledFor.add(binder); - return mAlwaysReturnSuccess; - } - - /** - * Returns whether the listener was called for the specified transport at least once. - */ - public boolean isCalledForTransport(IBackupTransport binder) { - return mTransportsCalledFor.contains(binder); - } - - /** - * Returns whether the listener was called at least once. - */ - public boolean isCalled() { - return !mTransportsCalledFor.isEmpty(); - } - - /** - * Resets listener calls. - */ - public void resetState() { - mTransportsCalledFor.clear(); - } -} diff --git a/services/robotests/src/com/android/server/backup/testing/TransportData.java b/services/robotests/src/com/android/server/backup/testing/TransportData.java new file mode 100644 index 000000000000..9feaa8efcf3d --- /dev/null +++ b/services/robotests/src/com/android/server/backup/testing/TransportData.java @@ -0,0 +1,149 @@ +/* + * 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 com.android.server.backup.testing; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Intent; + +public class TransportData { + // No constants since new Intent() can't be called in static context because of Robolectric + public static TransportData backupTransport() { + return new TransportData( + "com.google.android.gms/.backup.BackupTransportService", + "com.google.android.gms/.backup.BackupTransportService", + "com.google.android.gms.backup.BackupTransportService", + new Intent(), + "user@gmail.com", + new Intent(), + "Google Account"); + } + + public static TransportData d2dTransport() { + return new TransportData( + "com.google.android.gms/.backup.migrate.service.D2dTransport", + "com.google.android.gms/.backup.component.D2dTransportService", + "d2dMigrateTransport", + null, + "Moving data to new device", + null, + ""); + } + + public static TransportData localTransport() { + return new TransportData( + "android/com.android.internal.backup.LocalTransport", + "android/com.android.internal.backup.LocalTransportService", + "com.android.internal.backup.LocalTransport", + null, + "Backing up to debug-only private cache", + null, + ""); + } + + public static TransportData genericTransport(String packageName, String className) { + return new TransportData( + packageName + "/." + className, + packageName + "/." + className + "Service", + packageName + "." + className, + new Intent(), + "currentDestinationString", + new Intent(), + "dataManagementLabel"); + } + + @TransportTestUtils.TransportStatus + public int transportStatus; + public final String transportName; + private final String transportComponentShort; + @Nullable + public String transportDirName; + @Nullable public Intent configurationIntent; + @Nullable public String currentDestinationString; + @Nullable public Intent dataManagementIntent; + @Nullable public String dataManagementLabel; + + private TransportData( + @TransportTestUtils.TransportStatus int transportStatus, + String transportName, + String transportComponentShort, + String transportDirName, + Intent configurationIntent, + String currentDestinationString, + Intent dataManagementIntent, + String dataManagementLabel) { + this.transportStatus = transportStatus; + this.transportName = transportName; + this.transportComponentShort = transportComponentShort; + this.transportDirName = transportDirName; + this.configurationIntent = configurationIntent; + this.currentDestinationString = currentDestinationString; + this.dataManagementIntent = dataManagementIntent; + this.dataManagementLabel = dataManagementLabel; + } + + public TransportData( + String transportName, + String transportComponentShort, + String transportDirName, + Intent configurationIntent, + String currentDestinationString, + Intent dataManagementIntent, + String dataManagementLabel) { + this( + TransportTestUtils.TransportStatus.REGISTERED_AVAILABLE, + transportName, + transportComponentShort, + transportDirName, + configurationIntent, + currentDestinationString, + dataManagementIntent, + dataManagementLabel); + } + + /** + * Not field because otherwise we'd have to call ComponentName::new in static context and + * Robolectric does not like this. + */ + public ComponentName getTransportComponent() { + return ComponentName.unflattenFromString(transportComponentShort); + } + + public TransportData unavailable() { + return new TransportData( + TransportTestUtils.TransportStatus.REGISTERED_UNAVAILABLE, + transportName, + transportComponentShort, + transportDirName, + configurationIntent, + currentDestinationString, + dataManagementIntent, + dataManagementLabel); + } + + public TransportData unregistered() { + return new TransportData( + TransportTestUtils.TransportStatus.UNREGISTERED, + transportName, + transportComponentShort, + transportDirName, + configurationIntent, + currentDestinationString, + dataManagementIntent, + dataManagementLabel); + } +} diff --git a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java index 9770e407ec72..e1dc7b5e151e 100644 --- a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java +++ b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java @@ -16,13 +16,23 @@ package com.android.server.backup.testing; +import static com.android.server.backup.testing.TestUtils.uncheck; + +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static java.util.stream.Collectors.toList; + import android.annotation.Nullable; import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.RemoteException; +import android.support.annotation.IntDef; import com.android.internal.backup.IBackupTransport; import com.android.server.backup.TransportManager; @@ -30,85 +40,82 @@ import com.android.server.backup.transport.TransportClient; import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.transport.TransportNotRegisteredException; -import java.util.Arrays; +import org.robolectric.shadows.ShadowPackageManager; + import java.util.List; +import java.util.stream.Stream; public class TransportTestUtils { - public static final String[] TRANSPORT_NAMES = { - "android/com.android.internal.backup.LocalTransport", - "com.google.android.gms/.backup.migrate.service.D2dTransport", - "com.google.android.gms/.backup.BackupTransportService" - }; + /** + * Differently from {@link #setUpTransports(TransportManager, TransportData...)}, which + * configures {@link TransportManager}, this is meant to mock the environment for a real + * TransportManager. + */ + public static void setUpTransportsForTransportManager( + ShadowPackageManager shadowPackageManager, TransportData... transports) + throws Exception { + for (TransportData transport : transports) { + ComponentName transportComponent = transport.getTransportComponent(); + String packageName = transportComponent.getPackageName(); + ResolveInfo resolveInfo = resolveInfo(transportComponent); + shadowPackageManager.addResolveInfoForIntent(transportIntent(), resolveInfo); + shadowPackageManager.addResolveInfoForIntent( + transportIntent().setPackage(packageName), resolveInfo); + } + } - public static final String TRANSPORT_NAME = TRANSPORT_NAMES[0]; + private static Intent transportIntent() { + return new Intent(TransportManager.SERVICE_ACTION_TRANSPORT_HOST); + } - /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */ - public static TransportData setUpCurrentTransport( - TransportManager transportManager, String transportName) throws Exception { - TransportData transport = setUpTransports(transportManager, transportName).get(0); - when(transportManager.getCurrentTransportClient(any())) - .thenReturn(transport.transportClientMock); - return transport; + private static ResolveInfo resolveInfo(ComponentName transportComponent) { + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.serviceInfo = new ServiceInfo(); + resolveInfo.serviceInfo.packageName = transportComponent.getPackageName(); + resolveInfo.serviceInfo.name = transportComponent.getClassName(); + return resolveInfo; } /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */ - public static List<TransportData> setUpTransports( - TransportManager transportManager, String... transportNames) throws Exception { - return setUpTransports( - transportManager, - Arrays.stream(transportNames) - .map(TransportData::new) - .toArray(TransportData[]::new)); + public static TransportMock setUpCurrentTransport( + TransportManager transportManager, TransportData transport) throws Exception { + TransportMock transportMock = setUpTransports(transportManager, transport).get(0); + if (transportMock.transportClient != null) { + when(transportManager.getCurrentTransportClient(any())) + .thenReturn(transportMock.transportClient); + } + return transportMock; } /** @see #setUpTransport(TransportManager, TransportData) */ - public static List<TransportData> setUpTransports( + public static List<TransportMock> setUpTransports( TransportManager transportManager, TransportData... transports) throws Exception { - for (TransportData transport : transports) { - setUpTransport(transportManager, transport); - } - return Arrays.asList(transports); + return Stream.of(transports) + .map(transport -> uncheck(() -> setUpTransport(transportManager, transport))) + .collect(toList()); } - /** - * Configures transport according to {@link TransportData}: - * - * <ul> - * <li>{@link TransportData#transportMock} {@code null} means transport not available. - * <li>{@link TransportData#transportClientMock} {@code null} means transport not registered. - * </ul> - */ - public static void setUpTransport(TransportManager transportManager, TransportData transport) - throws Exception { + public static TransportMock setUpTransport( + TransportManager transportManager, TransportData transport) throws Exception { + int status = transport.transportStatus; String transportName = transport.transportName; - String transportDirName = transportDirName(transportName); - ComponentName transportComponent = transportComponentName(transportName); - IBackupTransport transportMock = transport.transportMock; - TransportClient transportClientMock = transport.transportClientMock; + ComponentName transportComponent = transport.getTransportComponent(); + String transportDirName = transport.transportDirName; - if (transportClientMock != null) { + TransportMock transportMock = mockTransport(transport); + if (status == TransportStatus.REGISTERED_AVAILABLE + || status == TransportStatus.REGISTERED_UNAVAILABLE) { // Transport registered when(transportManager.getTransportClient(eq(transportName), any())) - .thenReturn(transportClientMock); + .thenReturn(transportMock.transportClient); when(transportManager.getTransportClientOrThrow(eq(transportName), any())) - .thenReturn(transportClientMock); + .thenReturn(transportMock.transportClient); when(transportManager.getTransportName(transportComponent)).thenReturn(transportName); when(transportManager.getTransportDirName(eq(transportName))) .thenReturn(transportDirName); when(transportManager.getTransportDirName(eq(transportComponent))) .thenReturn(transportDirName); - when(transportClientMock.getTransportComponent()).thenReturn(transportComponent); - - if (transportMock != null) { - // Transport registered and available - when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock); - when(transportMock.name()).thenReturn(transportName); - when(transportMock.transportDirName()).thenReturn(transportDirName); - } else { - // Transport registered but unavailable - when(transportClientMock.connectOrThrow(any())) - .thenThrow(TransportNotAvailableException.class); - } + // TODO: Mock rest of description methods } else { // Transport not registered when(transportManager.getTransportClient(eq(transportName), any())).thenReturn(null); @@ -121,35 +128,74 @@ public class TransportTestUtils { when(transportManager.getTransportDirName(eq(transportComponent))) .thenThrow(TransportNotRegisteredException.class); } + return transportMock; } - /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */ - public static ComponentName transportComponentName(String transportName) { - return ComponentName.unflattenFromString(transportName); - } + public static TransportMock mockTransport(TransportData transport) throws Exception { + final TransportClient transportClientMock; + int status = transport.transportStatus; + ComponentName transportComponent = transport.getTransportComponent(); + if (status == TransportStatus.REGISTERED_AVAILABLE + || status == TransportStatus.REGISTERED_UNAVAILABLE) { + // Transport registered + transportClientMock = mock(TransportClient.class); + when(transportClientMock.getTransportComponent()).thenReturn(transportComponent); + if (status == TransportStatus.REGISTERED_AVAILABLE) { + // Transport registered and available + IBackupTransport transportMock = mockTransportBinder(transport); + when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock); + + return new TransportMock(transportClientMock, transportMock); + } else { + // Transport registered but unavailable + when(transportClientMock.connectOrThrow(any())) + .thenThrow(TransportNotAvailableException.class); - public static String transportDirName(String transportName) { - return transportName + "_dir_name"; + return new TransportMock(transportClientMock, null); + } + } else { + // Transport not registered + return new TransportMock(null, null); + } } - public static class TransportData { - public final String transportName; - @Nullable public final IBackupTransport transportMock; - @Nullable public final TransportClient transportClientMock; - - public TransportData( - String transportName, - @Nullable IBackupTransport transportMock, - @Nullable TransportClient transportClientMock) { - this.transportName = transportName; - this.transportMock = transportMock; - this.transportClientMock = transportClientMock; + private static IBackupTransport mockTransportBinder(TransportData transport) throws Exception { + IBackupTransport transportBinder = mock(IBackupTransport.class); + try { + when(transportBinder.name()).thenReturn(transport.transportName); + when(transportBinder.transportDirName()).thenReturn(transport.transportDirName); + when(transportBinder.configurationIntent()).thenReturn(transport.configurationIntent); + when(transportBinder.currentDestinationString()) + .thenReturn(transport.currentDestinationString); + when(transportBinder.dataManagementIntent()).thenReturn(transport.dataManagementIntent); + when(transportBinder.dataManagementLabel()).thenReturn(transport.dataManagementLabel); + } catch (RemoteException e) { + fail("RemoteException?"); } + return transportBinder; + } + + public static class TransportMock { + @Nullable public final TransportClient transportClient; + @Nullable public final IBackupTransport transport; - public TransportData(String transportName) { - this(transportName, mock(IBackupTransport.class), mock(TransportClient.class)); + private TransportMock( + @Nullable TransportClient transportClient, @Nullable IBackupTransport transport) { + this.transportClient = transportClient; + this.transport = transport; } } + @IntDef({ + TransportStatus.REGISTERED_AVAILABLE, + TransportStatus.REGISTERED_UNAVAILABLE, + TransportStatus.UNREGISTERED + }) + public @interface TransportStatus { + int REGISTERED_AVAILABLE = 0; + int REGISTERED_UNAVAILABLE = 1; + int UNREGISTERED = 2; + } + private TransportTestUtils() {} } diff --git a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java index 3bc0b30813a5..9b4dec6019f4 100644 --- a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java +++ b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java @@ -17,9 +17,7 @@ package com.android.server.backup.transport; import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST; - import static com.google.common.truth.Truth.assertThat; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -36,12 +34,10 @@ import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; - import com.android.internal.backup.IBackupTransport; import com.android.server.backup.TransportManager; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -78,11 +74,7 @@ public class TransportClientTest { mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(mTransportComponent); mTransportClient = new TransportClient( - mContext, - mBindIntent, - mTransportComponent, - "1", - new Handler(mainLooper)); + mContext, mBindIntent, mTransportComponent, "1", new Handler(mainLooper)); when(mContext.bindServiceAsUser( eq(mBindIntent), @@ -213,32 +205,34 @@ public class TransportClientTest { .onTransportConnectionResult(isNull(), eq(mTransportClient)); } - // TODO(b/69153972): Support SDK 26 API (ServiceConnection.inBindingDied) for transport tests - /*@Test + @Test public void testConnectAsync_callsListenerIfBindingDies() throws Exception { - mTransportClient.connectAsync(mTransportListener, "caller"); + mTransportClient.connectAsync(mTransportConnectionListener, "caller"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onBindingDied(mTransportComponent); mShadowLooper.runToEndOfTasks(); - verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient)); + verify(mTransportConnectionListener) + .onTransportConnectionResult(isNull(), eq(mTransportClient)); } @Test public void testConnectAsync_whenPendingConnection_callsListenersIfBindingDies() throws Exception { - mTransportClient.connectAsync(mTransportListener, "caller1"); + mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); - mTransportClient.connectAsync(mTransportListener2, "caller2"); + mTransportClient.connectAsync(mTransportConnectionListener2, "caller2"); connection.onBindingDied(mTransportComponent); mShadowLooper.runToEndOfTasks(); - verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient)); - verify(mTransportListener2).onTransportBound(isNull(), eq(mTransportClient)); - }*/ + verify(mTransportConnectionListener) + .onTransportConnectionResult(isNull(), eq(mTransportClient)); + verify(mTransportConnectionListener2) + .onTransportConnectionResult(isNull(), eq(mTransportClient)); + } private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) { ArgumentCaptor<ServiceConnection> connectionCaptor = diff --git a/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java new file mode 100644 index 000000000000..6d220737f2fb --- /dev/null +++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java @@ -0,0 +1,37 @@ +/* + * 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 com.android.server.testing.shadows; + +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.UserHandle; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowContextImpl; + +@Implements(className = ShadowContextImpl.CLASS_NAME, inheritImplementationMethods = true) +public class FrameworkShadowContextImpl extends ShadowContextImpl { + @Implementation + public boolean bindServiceAsUser( + Intent service, + ServiceConnection connection, + int flags, + UserHandle user) { + return bindService(service, connection, flags); + } +} diff --git a/services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java index b64b59d238d1..5cdbe7ff09ba 100644 --- a/services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java +++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java @@ -14,22 +14,18 @@ * limitations under the License */ -package com.android.server.backup.testing; +package com.android.server.testing.shadows; import android.app.ApplicationPackageManager; import android.content.Intent; import android.content.pm.ResolveInfo; - +import java.util.List; import org.robolectric.annotation.Implements; import org.robolectric.shadows.ShadowApplicationPackageManager; -import java.util.List; - -/** - * Implementation of PackageManager for Robolectric which handles queryIntentServicesAsUser(). - */ +/** Extension of ShadowApplicationPackageManager */ @Implements(value = ApplicationPackageManager.class, inheritImplementationMethods = true) -public class ShadowPackageManagerForBackup extends ShadowApplicationPackageManager { +public class FrameworkShadowPackageManager extends ShadowApplicationPackageManager { @Override public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) { return queryIntentServices(intent, flags); diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java index 766d30d9f9a5..7b4441ae30e6 100644 --- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -96,7 +96,7 @@ public class BackupManagerServiceTest { createBackupManagerService(); verify(mTransportManager) - .setTransportBoundListener(any(TransportManager.TransportBoundListener.class)); + .setOnTransportRegisteredListener(any()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index f5894e07303c..5134f523a8ff 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -15,6 +15,7 @@ */ package com.android.server.devicepolicy; +import android.app.ActivityManagerInternal; import android.app.AlarmManager; import android.app.IActivityManager; import android.app.NotificationManager; @@ -188,6 +189,11 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @Override + ActivityManagerInternal getActivityManagerInternal() { + return services.activityManagerInternal; + } + + @Override IPackageManager getIPackageManager() { return services.ipackageManager; } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 57030e433fa9..1df0ff23f847 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -87,6 +87,7 @@ import com.android.internal.R; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.devicepolicy.DevicePolicyManagerService.RestrictionsListener; import com.android.server.pm.UserRestrictionsUtils; import org.hamcrest.BaseMatcher; @@ -4020,6 +4021,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.setPasswordMinimumNumeric(admin1, 1); dpm.setPasswordMinimumSymbols(admin1, 0); + reset(mContext.spiedContext); + PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics( DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9, 8, 2, @@ -4069,9 +4072,16 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.setActivePasswordState(passwordMetrics, userHandle); dpm.reportPasswordChanged(userHandle); + // Drain ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED broadcasts as part of + // reportPasswordChanged() + verify(mContext.spiedContext, times(3)).sendBroadcastAsUser( + MockUtils.checkIntentAction( + DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), + MockUtils.checkUserHandle(userHandle)); + final Intent intent = new Intent(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED); intent.setComponent(admin1); - intent.putExtra(Intent.EXTRA_USER, UserHandle.of(mContext.binder.callingUid)); + intent.putExtra(Intent.EXTRA_USER, UserHandle.of(userHandle)); verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( MockUtils.checkIntent(intent), @@ -4482,6 +4492,45 @@ public class DevicePolicyManagerTest extends DpmTestBase { verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(null, caller); } + public void testDisallowSharingIntoProfileSetRestriction() { + Bundle restriction = new Bundle(); + restriction.putBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, true); + + mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; + RestrictionsListener listener = new RestrictionsListener(mContext); + listener.onUserRestrictionsChanged(DpmMockContext.CALLER_USER_HANDLE, restriction, + new Bundle()); + verifyDataSharingChangedBroadcast(); + } + + public void testDisallowSharingIntoProfileClearRestriction() { + Bundle restriction = new Bundle(); + restriction.putBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, true); + + mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; + RestrictionsListener listener = new RestrictionsListener(mContext); + listener.onUserRestrictionsChanged(DpmMockContext.CALLER_USER_HANDLE, new Bundle(), + restriction); + verifyDataSharingChangedBroadcast(); + } + + public void testDisallowSharingIntoProfileUnchanged() { + RestrictionsListener listener = new RestrictionsListener(mContext); + listener.onUserRestrictionsChanged(DpmMockContext.CALLER_USER_HANDLE, new Bundle(), + new Bundle()); + verify(mContext.spiedContext, never()).sendBroadcastAsUser(any(), any()); + } + + private void verifyDataSharingChangedBroadcast() { + Intent expectedIntent = new Intent( + DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_CHANGED); + expectedIntent.setPackage("com.android.managedprovisioning"); + expectedIntent.putExtra(Intent.EXTRA_USER_ID, DpmMockContext.CALLER_USER_HANDLE); + verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( + MockUtils.checkIntent(expectedIntent), + MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)); + } + private void verifyCanGetOwnerInstalledCaCerts( final ComponentName caller, final DpmMockContext callerContext) throws Exception { final String alias = "cert"; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java index 11e32f8326d9..268d424734e2 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.when; import android.accounts.Account; import android.accounts.AccountManager; +import android.app.ActivityManagerInternal; import android.app.AlarmManager; import android.app.IActivityManager; import android.app.NotificationManager; @@ -84,6 +85,7 @@ public class MockSystemServices { public final IIpConnectivityMetrics iipConnectivityMetrics; public final IWindowManager iwindowManager; public final IActivityManager iactivityManager; + public ActivityManagerInternal activityManagerInternal; public final IPackageManager ipackageManager; public final IBackupManager ibackupManager; public final IAudioService iaudioService; @@ -120,6 +122,7 @@ public class MockSystemServices { iipConnectivityMetrics = mock(IIpConnectivityMetrics.class); iwindowManager = mock(IWindowManager.class); iactivityManager = mock(IActivityManager.class); + activityManagerInternal = mock(ActivityManagerInternal.class); ipackageManager = mock(IPackageManager.class); ibackupManager = mock(IBackupManager.class); iaudioService = mock(IAudioService.class); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java index 288a8bedf713..dec962eeefeb 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java @@ -91,7 +91,8 @@ public class MockUtils { @Override public boolean matches(Object item) { if (item == null) return false; - return intent.filterEquals((Intent) item); + if (!intent.filterEquals((Intent) item)) return false; + return intent.getExtras().kindofEquals(((Intent) item).getExtras()); } @Override public void describeTo(Description description) { diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java index 08edd52c7112..edc7d74b47cb 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java @@ -447,21 +447,24 @@ public class BrightnessTrackerTest { @Test public void testParcelUnParcel() { Parcel parcel = Parcel.obtain(); - BrightnessChangeEvent event = new BrightnessChangeEvent(); - event.brightness = 23f; - event.timeStamp = 345L; - event.packageName = "com.example"; - event.userId = 12; - event.luxValues = new float[2]; - event.luxValues[0] = 3000.0f; - event.luxValues[1] = 4000.0f; - event.luxTimestamps = new long[2]; - event.luxTimestamps[0] = 325L; - event.luxTimestamps[1] = 315L; - event.batteryLevel = 0.7f; - event.nightMode = false; - event.colorTemperature = 345; - event.lastBrightness = 50f; + BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder(); + builder.setBrightness(23f); + builder.setTimeStamp(345L); + builder.setPackageName("com.example"); + builder.setUserId(12); + float[] luxValues = new float[2]; + luxValues[0] = 3000.0f; + luxValues[1] = 4000.0f; + builder.setLuxValues(luxValues); + long[] luxTimestamps = new long[2]; + luxTimestamps[0] = 325L; + luxTimestamps[1] = 315L; + builder.setLuxTimestamps(luxTimestamps); + builder.setBatteryLevel(0.7f); + builder.setNightMode(false); + builder.setColorTemperature(345); + builder.setLastBrightness(50f); + BrightnessChangeEvent event = builder.build(); event.writeToParcel(parcel, 0); byte[] parceled = parcel.marshall(); @@ -485,7 +488,8 @@ public class BrightnessTrackerTest { assertEquals(event.lastBrightness, event2.lastBrightness, FLOAT_DELTA); parcel = Parcel.obtain(); - event.batteryLevel = Float.NaN; + builder.setBatteryLevel(Float.NaN); + event = builder.build(); event.writeToParcel(parcel, 0); parceled = parcel.marshall(); parcel.recycle(); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java index 75380795b829..9eb42e9db425 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java @@ -40,7 +40,7 @@ import android.content.Context; import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; -import android.security.keystore.KeyDerivationParameters; +import android.security.keystore.KeyDerivationParams; import android.security.keystore.EntryRecoveryData; import android.security.keystore.RecoveryData; import android.support.test.InstrumentationRegistry; @@ -77,7 +77,8 @@ public class KeySyncTaskTest { private static final int TEST_USER_ID = 1000; private static final int TEST_RECOVERY_AGENT_UID = 10009; private static final int TEST_RECOVERY_AGENT_UID2 = 10010; - private static final long TEST_DEVICE_ID = 13295035643L; + private static final byte[] TEST_DEVICE_ID = + new byte[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2}; private static final String TEST_APP_KEY_ALIAS = "rcleaver"; private static final int TEST_GENERATION_ID = 2; private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PASSWORD; @@ -239,7 +240,7 @@ public class KeySyncTaskTest { @Test public void run_doesNotSendAnythingIfNoRecoveryAgentPendingIntentRegistered() throws Exception { SecretKey applicationKey = generateKey(); - mRecoverableKeyStoreDb.setServerParameters( + mRecoverableKeyStoreDb.setServerParams( TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_DEVICE_ID); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID); mRecoverableKeyStoreDb.insertKey( @@ -283,13 +284,13 @@ public class KeySyncTaskTest { mKeySyncTask.run(); RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID); - KeyDerivationParameters keyDerivationParameters = - recoveryData.getRecoveryMetadata().get(0).getKeyDerivationParameters(); - assertThat(keyDerivationParameters.getAlgorithm()).isEqualTo( - KeyDerivationParameters.ALGORITHM_SHA256); + KeyDerivationParams KeyDerivationParams = + recoveryData.getRecoveryMetadata().get(0).getKeyDerivationParams(); + assertThat(KeyDerivationParams.getAlgorithm()).isEqualTo( + KeyDerivationParams.ALGORITHM_SHA256); verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID); byte[] lockScreenHash = KeySyncTask.hashCredentials( - keyDerivationParameters.getSalt(), + KeyDerivationParams.getSalt(), TEST_CREDENTIAL); Long counterId = mRecoverableKeyStoreDb.getCounterId(TEST_USER_ID, TEST_RECOVERY_AGENT_UID); assertThat(counterId).isNotNull(); @@ -299,8 +300,8 @@ public class KeySyncTaskTest { /*vaultParams=*/ KeySyncUtils.packVaultParams( mKeyPair.getPublic(), counterId, - /*maxAttempts=*/ 10, - TEST_DEVICE_ID)); + TEST_DEVICE_ID, + /*maxAttempts=*/ 10)); List<EntryRecoveryData> applicationKeys = recoveryData.getEntryRecoveryData(); assertThat(applicationKeys).hasSize(1); EntryRecoveryData keyData = applicationKeys.get(0); @@ -469,7 +470,7 @@ public class KeySyncTaskTest { private SecretKey addApplicationKey(int userId, int recoveryAgentUid, String alias) throws Exception{ SecretKey applicationKey = generateKey(); - mRecoverableKeyStoreDb.setServerParameters( + mRecoverableKeyStoreDb.setServerParams( userId, recoveryAgentUid, TEST_DEVICE_ID); mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, TEST_GENERATION_ID); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java index 114da1aaebb5..3a9ff85520ff 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java @@ -50,6 +50,8 @@ public class KeySyncUtilsTest { private static final int RECOVERY_KEY_LENGTH_BITS = 256; private static final int THM_KF_HASH_SIZE = 256; private static final int KEY_CLAIMANT_LENGTH_BYTES = 16; + private static final byte[] TEST_DEVICE_ID = + new byte[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2}; private static final String SHA_256_ALGORITHM = "SHA-256"; private static final String APPLICATION_KEY_ALGORITHM = "AES"; private static final byte[] LOCK_SCREEN_HASH_1 = @@ -348,8 +350,8 @@ public class KeySyncUtilsTest { byte[] packedForm = KeySyncUtils.packVaultParams( thmPublicKey, /*counterId=*/ 1001L, - /*maxAttempts=*/ 10, - /*deviceId=*/ 1L); + TEST_DEVICE_ID, + /*maxAttempts=*/ 10); assertEquals(VAULT_PARAMS_LENGTH_BYTES, packedForm.length); } @@ -361,8 +363,8 @@ public class KeySyncUtilsTest { byte[] packedForm = KeySyncUtils.packVaultParams( thmPublicKey, /*counterId=*/ 1001L, - /*maxAttempts=*/ 10, - /*deviceId=*/ 1L); + TEST_DEVICE_ID, + /*maxAttempts=*/ 10); assertArrayEquals( SecureBox.encodePublicKey(thmPublicKey), @@ -376,8 +378,8 @@ public class KeySyncUtilsTest { byte[] packedForm = KeySyncUtils.packVaultParams( SecureBox.genKeyPair().getPublic(), counterId, - /*maxAttempts=*/ 10, - /*deviceId=*/ 1L); + TEST_DEVICE_ID, + /*maxAttempts=*/ 10); ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm) .order(ByteOrder.LITTLE_ENDIAN); @@ -387,18 +389,17 @@ public class KeySyncUtilsTest { @Test public void packVaultParams_encodesDeviceIdAsThirdParam() throws Exception { - long deviceId = 102942158152L; byte[] packedForm = KeySyncUtils.packVaultParams( SecureBox.genKeyPair().getPublic(), /*counterId=*/ 10021L, - /*maxAttempts=*/ 10, - deviceId); + TEST_DEVICE_ID, + /*maxAttempts=*/ 10); ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm) .order(ByteOrder.LITTLE_ENDIAN); byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES); - assertEquals(deviceId, byteBuffer.getLong()); + assertEquals(/* default value*/0, byteBuffer.getLong()); } @Test @@ -408,11 +409,12 @@ public class KeySyncUtilsTest { byte[] packedForm = KeySyncUtils.packVaultParams( SecureBox.genKeyPair().getPublic(), /*counterId=*/ 1001L, - maxAttempts, - /*deviceId=*/ 1L); + TEST_DEVICE_ID, + maxAttempts); ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm) .order(ByteOrder.LITTLE_ENDIAN); + // TODO: update position. byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + 2 * Long.BYTES); assertEquals(maxAttempts, byteBuffer.getInt()); } 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 0eddcf30f3f2..1bdcf478ce68 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 @@ -42,7 +42,7 @@ import android.os.UserHandle; import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; -import android.security.keystore.KeyDerivationParameters; +import android.security.keystore.KeyDerivationParams; import android.security.keystore.EntryRecoveryData; import android.security.keystore.RecoveryMetadata; import android.security.keystore.RecoveryManager; @@ -254,7 +254,7 @@ public class RecoverableKeyStoreManagerTest { new RecoveryMetadata( TYPE_LOCKSCREEN, TYPE_PASSWORD, - KeyDerivationParameters.createSha256Parameters(TEST_SALT), + KeyDerivationParams.createSha256Params(TEST_SALT), TEST_SECRET))); verify(mMockContext, times(1)) @@ -273,7 +273,7 @@ public class RecoverableKeyStoreManagerTest { new RecoveryMetadata( TYPE_LOCKSCREEN, TYPE_PASSWORD, - KeyDerivationParameters.createSha256Parameters(TEST_SALT), + KeyDerivationParams.createSha256Params(TEST_SALT), TEST_SECRET))); assertEquals(1, mRecoverySessionStorage.size()); @@ -311,7 +311,7 @@ public class RecoverableKeyStoreManagerTest { new RecoveryMetadata( TYPE_LOCKSCREEN, TYPE_PASSWORD, - KeyDerivationParameters.createSha256Parameters(TEST_SALT), + KeyDerivationParams.createSha256Params(TEST_SALT), TEST_SECRET))); fail("should have thrown"); } catch (ServiceSpecificException e) { @@ -333,7 +333,7 @@ public class RecoverableKeyStoreManagerTest { new RecoveryMetadata( TYPE_LOCKSCREEN, TYPE_PASSWORD, - KeyDerivationParameters.createSha256Parameters(TEST_SALT), + KeyDerivationParams.createSha256Params(TEST_SALT), TEST_SECRET))); fail("should have thrown"); } catch (ServiceSpecificException e) { @@ -366,7 +366,7 @@ public class RecoverableKeyStoreManagerTest { ImmutableList.of(new RecoveryMetadata( TYPE_LOCKSCREEN, TYPE_PASSWORD, - KeyDerivationParameters.createSha256Parameters(TEST_SALT), + KeyDerivationParams.createSha256Params(TEST_SALT), TEST_SECRET))); try { @@ -390,7 +390,7 @@ public class RecoverableKeyStoreManagerTest { ImmutableList.of(new RecoveryMetadata( TYPE_LOCKSCREEN, TYPE_PASSWORD, - KeyDerivationParameters.createSha256Parameters(TEST_SALT), + KeyDerivationParams.createSha256Params(TEST_SALT), TEST_SECRET))); byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID) .getKeyClaimant(); @@ -422,7 +422,7 @@ public class RecoverableKeyStoreManagerTest { ImmutableList.of(new RecoveryMetadata( TYPE_LOCKSCREEN, TYPE_PASSWORD, - KeyDerivationParameters.createSha256Parameters(TEST_SALT), + KeyDerivationParams.createSha256Params(TEST_SALT), TEST_SECRET))); byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID) .getKeyClaimant(); 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 6d5e7408904d..5cb7b677dbbd 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 @@ -51,6 +51,12 @@ public class RecoverableKeyStoreDbTest { private RecoverableKeyStoreDb mRecoverableKeyStoreDb; private File mDatabaseFile; + private static final byte[] SERVER_PARAMS = + new byte[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2}; + + private static final byte[] SERVER_PARAMS2 = + new byte[]{1, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4}; + @Before public void setUp() { Context context = InstrumentationRegistry.getTargetContext(); @@ -328,8 +334,7 @@ public class RecoverableKeyStoreDbTest { int uid = 10009; assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull(); - long serverParams = 123456L; - mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams); + mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS); assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull(); } @@ -437,32 +442,30 @@ public class RecoverableKeyStoreDbTest { PublicKey pubkey1 = genRandomPublicKey(); int[] types1 = new int[]{1}; - long serverParams1 = 111L; PublicKey pubkey2 = genRandomPublicKey(); int[] types2 = new int[]{2}; - long serverParams2 = 222L; mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1); mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types1); - mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams1); + mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS); assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( types1); - assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo( - serverParams1); + assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo( + SERVER_PARAMS); assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo( pubkey1); // Check that the methods don't interfere with each other. mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2); mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types2); - mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams2); + mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS2); assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo( types2); - assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo( - serverParams2); + assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo( + SERVER_PARAMS2); assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo( pubkey2); } @@ -479,35 +482,33 @@ public class RecoverableKeyStoreDbTest { } @Test - public void setServerParameters_replaceOldValue() throws Exception { + public void setServerParams_replaceOldValue() throws Exception { int userId = 12; int uid = 10009; - long serverParams1 = 111L; - long serverParams2 = 222L; - mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams1); - mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams2); - assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo( - serverParams2); + + mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS); + mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS2); + assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo( + SERVER_PARAMS2); } @Test - public void getServerParameters_returnsNullIfNoValue() throws Exception { + public void getServerParams_returnsNullIfNoValue() throws Exception { int userId = 12; int uid = 10009; - assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull(); + assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isNull(); PublicKey pubkey = genRandomPublicKey(); mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey); - assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull(); + assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isNull(); } @Test - public void getServerParameters_returnsInsertedValue() throws Exception { + public void getServerParams_returnsInsertedValue() throws Exception { int userId = 12; int uid = 10009; - long serverParams = 123456L; - mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams); - assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams); + mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS); + assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(SERVER_PARAMS); } @Test @@ -590,22 +591,21 @@ public class RecoverableKeyStoreDbTest { int uid = 10009; PublicKey pubkey1 = genRandomPublicKey(); PublicKey pubkey2 = genRandomPublicKey(); - long serverParams = 123456L; mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1); assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo( pubkey1); - assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull(); + assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isNull(); - mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams); + mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS); assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo( pubkey1); - assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams); + assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(SERVER_PARAMS); mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2); assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo( pubkey2); - assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams); + assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(SERVER_PARAMS); } private static byte[] getUtf8Bytes(String s) { diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java index 9a6da0e791b5..1ad73cf55d3f 100644 --- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java @@ -19,6 +19,7 @@ package com.android.server.policy; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; @@ -128,7 +129,7 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { } @Test - public void layoutWindowLw_withDisplayCutout_fullscreen() { + public void layoutWindowLw_withDisplayCutout_layoutFullscreen() { addDisplayCutout(); mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; @@ -137,6 +138,22 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); + } + + @Test + public void layoutWindowLw_withDisplayCutout_fullscreen() { + addDisplayCutout(); + + mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0); assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); @@ -147,7 +164,7 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { public void layoutWindowLw_withDisplayCutout_fullscreenInCutout() { addDisplayCutout(); - mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; mPolicy.addWindow(mAppWindow); diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java index e7e9abad5bbe..ad8995325a6a 100644 --- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java +++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java @@ -24,6 +24,8 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; +import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates; + import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; @@ -31,6 +33,8 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.graphics.Matrix; +import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.IBinder; @@ -38,6 +42,7 @@ import android.os.UserHandle; import android.support.test.InstrumentationRegistry; import android.testing.TestableResources; import android.view.Display; +import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.Gravity; import android.view.View; @@ -65,6 +70,9 @@ public class PhoneWindowManagerTestBase { FakeWindowState mStatusBar; FakeWindowState mNavigationBar; + private boolean mHasDisplayCutout; + private int mRotation = ROTATION_0; + private final Matrix mTmpMatrix = new Matrix(); @Before public void setUpBase() throws Exception { @@ -80,16 +88,32 @@ public class PhoneWindowManagerTestBase { mPolicy = TestablePhoneWindowManager.create(mContext); - setRotation(ROTATION_0); + updateDisplayFrames(); } public void setRotation(int rotation) { + mRotation = rotation; + updateDisplayFrames(); + } + + private void updateDisplayFrames() { DisplayInfo info = new DisplayInfo(); - final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270; + final boolean flippedDimensions = mRotation == ROTATION_90 || mRotation == ROTATION_270; info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH; info.logicalHeight = flippedDimensions ? DISPLAY_WIDTH : DISPLAY_HEIGHT; - info.rotation = rotation; + info.rotation = mRotation; + if (mHasDisplayCutout) { + Path p = new Path(); + p.addRect(DISPLAY_WIDTH / 4, 0, DISPLAY_WIDTH * 3 / 4, DISPLAY_CUTOUT_HEIGHT, + Path.Direction.CCW); + transformPhysicalToLogicalCoordinates( + mRotation, DISPLAY_WIDTH, DISPLAY_HEIGHT, mTmpMatrix); + p.transform(mTmpMatrix); + info.displayCutout = DisplayCutout.fromBounds(p); + } else { + info.displayCutout = null; + } mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info); } @@ -116,7 +140,8 @@ public class PhoneWindowManagerTestBase { } public void addDisplayCutout() { - mPolicy.mEmulateDisplayCutout = true; + mHasDisplayCutout = true; + updateDisplayFrames(); } /** Asserts that {@code actual} is inset by the given amounts from the full display rect. */ diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 20911012e6ba..8c7d6b30ecdc 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -1408,7 +1408,7 @@ public final class Call { * @param extras Bundle containing extra information associated with the event. */ public void sendCallEvent(String event, Bundle extras) { - mInCallAdapter.sendCallEvent(mTelecomCallId, event, extras); + mInCallAdapter.sendCallEvent(mTelecomCallId, event, mTargetSdkVersion, extras); } /** diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java index 4bc2a9b149f2..658685fe2907 100644 --- a/telecomm/java/android/telecom/InCallAdapter.java +++ b/telecomm/java/android/telecom/InCallAdapter.java @@ -286,11 +286,12 @@ public final class InCallAdapter { * * @param callId The callId to send the event for. * @param event The event. + * @param targetSdkVer Target sdk version of the app calling this api * @param extras Extras associated with the event. */ - public void sendCallEvent(String callId, String event, Bundle extras) { + public void sendCallEvent(String callId, String event, int targetSdkVer, Bundle extras) { try { - mAdapter.sendCallEvent(callId, event, extras); + mAdapter.sendCallEvent(callId, event, targetSdkVer, extras); } catch (RemoteException ignored) { } } diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index bddf3eacd85a..d292db3efc82 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -24,6 +24,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; @@ -1440,6 +1441,13 @@ public class TelecomManager { public void addNewIncomingCall(PhoneAccountHandle phoneAccount, Bundle extras) { try { if (isServiceConnected()) { + if (extras != null && extras.getBoolean(EXTRA_IS_HANDOVER) && + mContext.getApplicationContext().getApplicationInfo().targetSdkVersion > + Build.VERSION_CODES.O_MR1) { + Log.e("TAG", "addNewIncomingCall failed. Use public api " + + "acceptHandover for API > O-MR1"); + // TODO add "return" after DUO team adds support for new handover API + } getTelecomService().addNewIncomingCall( phoneAccount, extras == null ? new Bundle() : extras); } diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl index 23ac940edfa8..87ccd3ed4369 100644 --- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl @@ -64,7 +64,7 @@ oneway interface IInCallAdapter { void pullExternalCall(String callId); - void sendCallEvent(String callId, String event, in Bundle extras); + void sendCallEvent(String callId, String event, int targetSdkVer, in Bundle extras); void putExtras(String callId, in Bundle extras); diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index c7e51310e106..98ea45158ba1 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -244,6 +244,13 @@ public class PhoneStateListener { */ public static final int LISTEN_DATA_ACTIVATION_STATE = 0x00040000; + /** + * Listen for changes to the user mobile data state + * + * @see #onUserMobileDataStateChanged + */ + public static final int LISTEN_USER_MOBILE_DATA_STATE = 0x00080000; + /* * Subscription used to listen to the phone state changes * @hide @@ -349,6 +356,9 @@ public class PhoneStateListener { case LISTEN_DATA_ACTIVATION_STATE: PhoneStateListener.this.onDataActivationStateChanged((int)msg.obj); break; + case LISTEN_USER_MOBILE_DATA_STATE: + PhoneStateListener.this.onUserMobileDataStateChanged((boolean)msg.obj); + break; case LISTEN_CARRIER_NETWORK_CHANGE: PhoneStateListener.this.onCarrierNetworkChange((boolean)msg.obj); break; @@ -543,6 +553,14 @@ public class PhoneStateListener { } /** + * Callback invoked when the user mobile data state has changed + * @param enabled indicates whether the current user mobile data state is enabled or disabled. + */ + public void onUserMobileDataStateChanged(boolean enabled) { + // default implementation empty + } + + /** * Callback invoked when telephony has received notice from a carrier * app that a network action that could result in connectivity loss * has been requested by an app using @@ -654,6 +672,10 @@ public class PhoneStateListener { send(LISTEN_DATA_ACTIVATION_STATE, 0, 0, activationState); } + public void onUserMobileDataStateChanged(boolean enabled) { + send(LISTEN_USER_MOBILE_DATA_STATE, 0, 0, enabled); + } + public void onCarrierNetworkChange(boolean active) { send(LISTEN_CARRIER_NETWORK_CHANGE, 0, 0, active); } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f411ef71c94b..1945e5d328c2 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -53,6 +53,7 @@ import android.util.Log; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsRcsFeature; +import com.android.ims.internal.IImsRegistration; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telecom.ITelecomService; @@ -4932,6 +4933,25 @@ public class TelephonyManager { } /** + * @return the {@IImsRegistration} interface that corresponds with the slot index and feature. + * @param slotIndex The SIM slot corresponding to the ImsService ImsRegistration is active for. + * @param feature An integer indicating the feature that we wish to get the ImsRegistration for. + * Corresponds to features defined in ImsFeature. + * @hide + */ + public @Nullable IImsRegistration getImsRegistration(int slotIndex, int feature) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getImsRegistration(slotIndex, feature); + } + } catch (RemoteException e) { + Rlog.e(TAG, "getImsRegistration, RemoteException: " + e.getMessage()); + } + return null; + } + + /** * Set IMS registration state * * @param Registration state diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java index 8230eafc2e4d..aaa0f08594d1 100644 --- a/telephony/java/android/telephony/ims/ImsService.java +++ b/telephony/java/android/telephony/ims/ImsService.java @@ -26,12 +26,14 @@ import android.telephony.CarrierConfigManager; import android.telephony.ims.feature.ImsFeature; import android.telephony.ims.feature.MMTelFeature; import android.telephony.ims.feature.RcsFeature; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.Log; import android.util.SparseArray; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsRcsFeature; +import com.android.ims.internal.IImsRegistration; import com.android.ims.internal.IImsServiceController; import com.android.internal.annotations.VisibleForTesting; @@ -113,6 +115,12 @@ public class ImsService extends Service { throws RemoteException { ImsService.this.removeImsFeature(slotId, featureType, c); } + + @Override + public IImsRegistration getRegistration(int slotId) throws RemoteException { + ImsRegistrationImplBase r = ImsService.this.getRegistration(slotId); + return r != null ? r.getBinder() : null; + } }; /** @@ -174,6 +182,8 @@ public class ImsService extends Service { f.setSlotId(slotId); f.addImsFeatureStatusCallback(c); addImsFeature(slotId, featureType, f); + // TODO: Remove once new onFeatureReady AIDL is merged in. + f.onFeatureReady(); } private void addImsFeature(int slotId, int featureType, ImsFeature f) { @@ -236,4 +246,13 @@ public class ImsService extends Service { public @Nullable RcsFeature onCreateRcsFeature(int slotId) { return null; } + + /** + * @param slotId The slot that is associated with the IMS Registration. + * @return the ImsRegistration implementation associated with the slot. + * @hide + */ + public ImsRegistrationImplBase getRegistration(int slotId) { + return new ImsRegistrationImplBase(); + } } diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java index ca4a210e30cc..d47cea3097f3 100644 --- a/telephony/java/android/telephony/ims/feature/ImsFeature.java +++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java @@ -96,7 +96,7 @@ public abstract class ImsFeature { new WeakHashMap<IImsFeatureStatusCallback, Boolean>()); private @ImsState int mState = STATE_NOT_AVAILABLE; private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; - private Context mContext; + protected Context mContext; public void setContext(Context context) { mContext = context; diff --git a/telephony/java/android/telephony/ims/internal/ImsService.java b/telephony/java/android/telephony/ims/internal/ImsService.java index b7c8ca0f9799..afaf33294d8a 100644 --- a/telephony/java/android/telephony/ims/internal/ImsService.java +++ b/telephony/java/android/telephony/ims/internal/ImsService.java @@ -24,7 +24,6 @@ import android.telephony.CarrierConfigManager; import android.telephony.ims.internal.aidl.IImsConfig; import android.telephony.ims.internal.aidl.IImsMmTelFeature; import android.telephony.ims.internal.aidl.IImsRcsFeature; -import android.telephony.ims.internal.aidl.IImsRegistration; import android.telephony.ims.internal.aidl.IImsServiceController; import android.telephony.ims.internal.aidl.IImsServiceControllerListener; import android.telephony.ims.internal.feature.ImsFeature; @@ -32,11 +31,12 @@ import android.telephony.ims.internal.feature.MmTelFeature; import android.telephony.ims.internal.feature.RcsFeature; import android.telephony.ims.internal.stub.ImsConfigImplBase; import android.telephony.ims.internal.stub.ImsFeatureConfiguration; -import android.telephony.ims.internal.stub.ImsRegistrationImplBase; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.Log; import android.util.SparseArray; import com.android.ims.internal.IImsFeatureStatusCallback; +import com.android.ims.internal.IImsRegistration; import com.android.internal.annotations.VisibleForTesting; /** diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl index 8332bc024e37..43f5098af3ca 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl +++ b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl @@ -24,4 +24,5 @@ import com.android.ims.internal.IImsCallSession; */ oneway interface IImsMmTelListener { void onIncomingCall(IImsCallSession c); + void onVoiceMessageCountUpdate(int count); }
\ No newline at end of file diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl index 8afb95588b01..82a85254bbca 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl +++ b/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl @@ -18,12 +18,12 @@ package android.telephony.ims.internal.aidl; import android.telephony.ims.internal.aidl.IImsMmTelFeature; import android.telephony.ims.internal.aidl.IImsRcsFeature; -import android.telephony.ims.internal.aidl.IImsRegistration; import android.telephony.ims.internal.aidl.IImsConfig; import android.telephony.ims.internal.aidl.IImsServiceControllerListener; import android.telephony.ims.internal.stub.ImsFeatureConfiguration; import com.android.ims.internal.IImsFeatureStatusCallback; +import com.android.ims.internal.IImsRegistration; /** * See ImsService and MmTelFeature for more information. diff --git a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java index 4d188734c10e..5dbf077ee7c5 100644 --- a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java +++ b/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java @@ -18,7 +18,7 @@ package android.telephony.ims.internal.feature; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.ims.internal.stub.ImsRegistrationImplBase; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.ArraySet; import java.util.ArrayList; diff --git a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java index 238411ee4f8f..8d888c2bcb28 100644 --- a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java @@ -28,8 +28,8 @@ import android.telephony.ims.internal.aidl.IImsCallSessionListener; import android.telephony.ims.internal.aidl.IImsCapabilityCallback; import android.telephony.ims.internal.aidl.IImsMmTelFeature; import android.telephony.ims.internal.aidl.IImsMmTelListener; -import android.telephony.ims.internal.stub.ImsRegistrationImplBase; import android.telephony.ims.internal.aidl.IImsSmsListener; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.telephony.ims.stub.ImsEcbmImplBase; import android.telephony.ims.stub.ImsMultiEndpointImplBase; import android.telephony.ims.stub.ImsUtImplBase; @@ -262,6 +262,15 @@ public class MmTelFeature extends ImsFeature { } /** + * Updates the Listener when the voice message count for IMS has changed. + * @param count an integer representing the new message count. + */ + @Override + public void onVoiceMessageCountUpdate(int count) { + + } + + /** * Called when the IMS provider receives an incoming call. * @param c The {@link ImsCallSession} associated with the new call. */ diff --git a/telephony/java/android/telephony/ims/internal/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java index 558b009ab4c2..42af08365f61 100644 --- a/telephony/java/android/telephony/ims/internal/stub/ImsRegistrationImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java @@ -14,16 +14,19 @@ * limitations under the License */ -package android.telephony.ims.internal.stub; +package android.telephony.ims.stub; import android.annotation.IntDef; +import android.net.Uri; +import android.os.IBinder; import android.os.RemoteCallbackList; import android.os.RemoteException; -import android.telephony.ims.internal.aidl.IImsRegistration; -import android.telephony.ims.internal.aidl.IImsRegistrationCallback; import android.util.Log; import com.android.ims.ImsReasonInfo; +import com.android.ims.internal.IImsRegistration; +import com.android.ims.internal.IImsRegistrationCallback; +import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -62,23 +65,25 @@ public class ImsRegistrationImplBase { // Registration states, used to notify new ImsRegistrationImplBase#Callbacks of the current // state. + // The unknown state is set as the initialization state. This is so that we do not call back + // with NOT_REGISTERED in the case where the ImsService has not updated the registration state + // yet. + private static final int REGISTRATION_STATE_UNKNOWN = -1; private static final int REGISTRATION_STATE_NOT_REGISTERED = 0; private static final int REGISTRATION_STATE_REGISTERING = 1; private static final int REGISTRATION_STATE_REGISTERED = 2; - /** * Callback class for receiving Registration callback events. + * @hide */ - public static class Callback extends IImsRegistrationCallback.Stub { - + public static class Callback { /** * Notifies the framework when the IMS Provider is connected to the IMS network. * * @param imsRadioTech the radio access technology. Valid values are defined in * {@link ImsRegistrationTech}. */ - @Override public void onRegistered(@ImsRegistrationTech int imsRadioTech) { } @@ -88,7 +93,6 @@ public class ImsRegistrationImplBase { * @param imsRadioTech the radio access technology. Valid values are defined in * {@link ImsRegistrationTech}. */ - @Override public void onRegistering(@ImsRegistrationTech int imsRadioTech) { } @@ -97,7 +101,6 @@ public class ImsRegistrationImplBase { * * @param info the {@link ImsReasonInfo} associated with why registration was disconnected. */ - @Override public void onDeregistered(ImsReasonInfo info) { } @@ -108,10 +111,19 @@ public class ImsRegistrationImplBase { * @param imsRadioTech The {@link ImsRegistrationTech} type that has failed * @param info A {@link ImsReasonInfo} that identifies the reason for failure. */ - @Override public void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech, ImsReasonInfo info) { } + + /** + * Returns a list of subscriber {@link Uri}s associated with this IMS subscription when + * it changes. + * @param uris new array of subscriber {@link Uri}s that are associated with this IMS + * subscription. + */ + public void onSubscriberAssociatedUriChanged(Uri[] uris) { + + } } private final IImsRegistration mBinder = new IImsRegistration.Stub() { @@ -139,9 +151,9 @@ public class ImsRegistrationImplBase { private @ImsRegistrationTech int mConnectionType = REGISTRATION_TECH_NONE; // Locked on mLock - private int mRegistrationState = REGISTRATION_STATE_NOT_REGISTERED; - // Locked on mLock - private ImsReasonInfo mLastDisconnectCause; + private int mRegistrationState = REGISTRATION_STATE_UNKNOWN; + // Locked on mLock, create unspecified disconnect cause. + private ImsReasonInfo mLastDisconnectCause = new ImsReasonInfo(); public final IImsRegistration getBinder() { return mBinder; @@ -221,6 +233,17 @@ public class ImsRegistrationImplBase { }); } + public final void onSubscriberAssociatedUriChanged(Uri[] uris) { + mCallbacks.broadcast((c) -> { + try { + c.onSubscriberAssociatedUriChanged(uris); + } catch (RemoteException e) { + Log.w(LOG_TAG, e + " " + "onSubscriberAssociatedUriChanged() - Skipping " + + "callback."); + } + }); + } + private void updateToState(@ImsRegistrationTech int connType, int newState) { synchronized (mLock) { mConnectionType = connType; @@ -241,7 +264,8 @@ public class ImsRegistrationImplBase { } } - private @ImsRegistrationTech int getConnectionType() { + @VisibleForTesting + public final @ImsRegistrationTech int getConnectionType() { synchronized (mLock) { return mConnectionType; } @@ -271,6 +295,10 @@ public class ImsRegistrationImplBase { c.onRegistered(getConnectionType()); break; } + case REGISTRATION_STATE_UNKNOWN: { + // Do not callback if the state has not been updated yet by the ImsService. + break; + } } } } diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl b/telephony/java/com/android/ims/internal/IImsRegistration.aidl index 687b7ca408d5..6de264ec90fb 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl +++ b/telephony/java/com/android/ims/internal/IImsRegistration.aidl @@ -15,10 +15,9 @@ */ -package android.telephony.ims.internal.aidl; +package com.android.ims.internal; -import android.telephony.ims.internal.aidl.IImsRegistrationCallback; -import android.telephony.ims.internal.stub.ImsFeatureConfiguration; +import com.android.ims.internal.IImsRegistrationCallback; /** * See ImsRegistration for more information. diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl b/telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl index a50575b96865..5f21167422dc 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl +++ b/telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl @@ -15,8 +15,9 @@ */ -package android.telephony.ims.internal.aidl; +package com.android.ims.internal; +import android.net.Uri; import android.telephony.ims.internal.stub.ImsFeatureConfiguration; import com.android.ims.ImsReasonInfo; @@ -31,4 +32,5 @@ oneway interface IImsRegistrationCallback { void onRegistering(int imsRadioTech); void onDeregistered(in ImsReasonInfo info); void onTechnologyChangeFailed(int imsRadioTech, in ImsReasonInfo info); + void onSubscriberAssociatedUriChanged(in Uri[] uris); }
\ No newline at end of file diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl index 857089fac33a..7ac25ac13fbe 100644 --- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl +++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl @@ -18,6 +18,7 @@ package com.android.ims.internal; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.ims.internal.IImsMMTelFeature; +import com.android.ims.internal.IImsRegistration; import com.android.ims.internal.IImsRcsFeature; /** @@ -29,4 +30,5 @@ interface IImsServiceController { IImsMMTelFeature createMMTelFeature(int slotId, in IImsFeatureStatusCallback c); IImsRcsFeature createRcsFeature(int slotId, in IImsFeatureStatusCallback c); void removeImsFeature(int slotId, int featureType, in IImsFeatureStatusCallback c); + IImsRegistration getRegistration(int slotId); } diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl index ac161397af04..8e3f4c070d2e 100644 --- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -46,5 +46,6 @@ oneway interface IPhoneStateListener { void onVoiceActivationStateChanged(int activationState); void onDataActivationStateChanged(int activationState); void onCarrierNetworkChange(in boolean active); + void onUserMobileDataStateChanged(in boolean enabled); } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 416146fcb98e..fba82ee17f77 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -40,6 +40,7 @@ import android.telephony.TelephonyHistogram; import android.telephony.VisualVoicemailSmsFilterSettings; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsRcsFeature; +import com.android.ims.internal.IImsRegistration; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.telephony.CellNetworkScanResult; import com.android.internal.telephony.OperatorInfo; @@ -808,6 +809,11 @@ interface ITelephony { IImsRcsFeature getRcsFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback); /** + * Returns the IImsRegistration associated with the slot and feature specified. + */ + IImsRegistration getImsRegistration(int slotId, int feature); + + /** * Set the network selection mode to automatic. * * @param subId the id of the subscription to update. diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl index 75d8f3fcb9b6..188167cc14f6 100644 --- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -69,4 +69,5 @@ interface ITelephonyRegistry { int activationState, int activationType); void notifySubscriptionInfoChanged(); void notifyCarrierNetworkChange(in boolean active); + void notifyUserMobileDataStateChangedForPhoneId(in int phoneId, in int subId, in boolean state); } diff --git a/test-base/Android.bp b/test-base/Android.bp index 0088962b8a0c..ccf57b00a379 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -49,7 +49,8 @@ java_library { // Build the repackaged.android.test.base library // ============================================== -// This contains repackaged versions of the classes from legacy-test. +// This contains repackaged versions of the classes from +// android.test.base. java_library_static { name: "repackaged.android.test.base", @@ -97,7 +98,7 @@ java_library_static { ], static_libs: [ - "android.test.runner", + "android.test.runner-minus-junit", "android.test.mock", ], diff --git a/test-mock/Android.bp b/test-mock/Android.bp index 8eddec48611b..b1ae40e17b9d 100644 --- a/test-mock/Android.bp +++ b/test-mock/Android.bp @@ -24,7 +24,6 @@ java_library { no_framework_libs: true, libs: [ "framework", - "legacy-test", ], } diff --git a/test-runner/Android.bp b/test-runner/Android.bp index 104ae8236368..dfaeed5e271e 100644 --- a/test-runner/Android.bp +++ b/test-runner/Android.bp @@ -24,11 +24,28 @@ java_library { no_framework_libs: true, libs: [ "framework", - "legacy-test", + "android.test.base", "android.test.mock", ], } +// Build the android.test.runner-minus-junit library +// ================================================= +// This is provided solely for use by the legacy-android-test module. +java_library { + name: "android.test.runner-minus-junit", + + srcs: ["src/android/**/*.java"], + + no_framework_libs: true, + libs: [ + "framework", + "android.test.base", + "android.test.mock", + "junit", + ], +} + // Build the repackaged.android.test.runner library // ================================================ java_library_static { diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java index 473dc538f09d..9aad413c354b 100644 --- a/tests/net/java/android/net/MacAddressTest.java +++ b/tests/net/java/android/net/MacAddressTest.java @@ -67,7 +67,7 @@ public class MacAddressTest { assertEquals(msg, t.expectedType, got); if (got != MacAddress.TYPE_UNKNOWN) { - assertEquals(got, MacAddress.fromBytes(t.addr).addressType()); + assertEquals(got, MacAddress.fromBytes(t.addr).getAddressType()); } } } @@ -191,7 +191,7 @@ public class MacAddressTest { assertTrue(stringRepr + " expected to be a locally assigned address", mac.isLocallyAssigned()); - assertEquals(MacAddress.TYPE_UNICAST, mac.addressType()); + assertEquals(MacAddress.TYPE_UNICAST, mac.getAddressType()); assertTrue(stringRepr + " expected to begin with " + expectedLocalOui, stringRepr.startsWith(expectedLocalOui)); } diff --git a/tests/net/java/android/net/NetworkTest.java b/tests/net/java/android/net/NetworkTest.java index bacf986b3627..94d01e91d03b 100644 --- a/tests/net/java/android/net/NetworkTest.java +++ b/tests/net/java/android/net/NetworkTest.java @@ -147,9 +147,9 @@ public class NetworkTest { // Adjust as necessary to test an implementation's specific constants. // When running with runtest, "adb logcat -s TestRunner" can be useful. - assertEquals(4311403230L, one.getNetworkHandle()); - assertEquals(8606370526L, two.getNetworkHandle()); - assertEquals(12901337822L, three.getNetworkHandle()); + assertEquals(7700664333L, one.getNetworkHandle()); + assertEquals(11995631629L, two.getNetworkHandle()); + assertEquals(16290598925L, three.getNetworkHandle()); } private static <T> void assertNotEqual(T t1, T t2) { diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 2b0349c6fa83..b8e37f3a10ea 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -1392,39 +1392,75 @@ public class ConnectivityServiceTest { return null; } - void expectAvailableCallbacks( - MockNetworkAgent agent, boolean expectSuspended, int timeoutMs) { + // Expects onAvailable and the callbacks that follow it. These are: + // - onSuspended, iff the network was suspended when the callbacks fire. + // - onCapabilitiesChanged. + // - onLinkPropertiesChanged. + // + // @param agent the network to expect the callbacks on. + // @param expectSuspended whether to expect a SUSPENDED callback. + // @param expectValidated the expected value of the VALIDATED capability in the + // onCapabilitiesChanged callback. + // @param timeoutMs how long to wait for the callbacks. + void expectAvailableCallbacks(MockNetworkAgent agent, boolean expectSuspended, + boolean expectValidated, int timeoutMs) { expectCallback(CallbackState.AVAILABLE, agent, timeoutMs); if (expectSuspended) { expectCallback(CallbackState.SUSPENDED, agent, timeoutMs); } - expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs); + if (expectValidated) { + expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); + } else { + expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, agent); + } expectCallback(CallbackState.LINK_PROPERTIES, agent, timeoutMs); } - void expectAvailableCallbacks(MockNetworkAgent agent) { - expectAvailableCallbacks(agent, false, TIMEOUT_MS); + // Expects the available callbacks (validated), plus onSuspended. + void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent, boolean expectValidated) { + expectAvailableCallbacks(agent, true, expectValidated, TIMEOUT_MS); + } + + void expectAvailableCallbacksValidated(MockNetworkAgent agent) { + expectAvailableCallbacks(agent, false, true, TIMEOUT_MS); + } + + void expectAvailableCallbacksUnvalidated(MockNetworkAgent agent) { + expectAvailableCallbacks(agent, false, false, TIMEOUT_MS); } - void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent) { - expectAvailableCallbacks(agent, true, TIMEOUT_MS); + // Expects the available callbacks (where the onCapabilitiesChanged must contain the + // VALIDATED capability), plus another onCapabilitiesChanged which is identical to the + // one we just sent. + // TODO: this is likely a bug. Fix it and remove this method. + void expectAvailableDoubleValidatedCallbacks(MockNetworkAgent agent) { + expectCallback(CallbackState.AVAILABLE, agent, TIMEOUT_MS); + NetworkCapabilities nc1 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); + expectCallback(CallbackState.LINK_PROPERTIES, agent, TIMEOUT_MS); + NetworkCapabilities nc2 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); + assertEquals(nc1, nc2); } - void expectAvailableAndValidatedCallbacks(MockNetworkAgent agent) { - expectAvailableCallbacks(agent, false, TIMEOUT_MS); + // Expects the available callbacks where the onCapabilitiesChanged must not have validated, + // then expects another onCapabilitiesChanged that has the validated bit set. This is used + // when a network connects and satisfies a callback, and then immediately validates. + void expectAvailableThenValidatedCallbacks(MockNetworkAgent agent) { + expectAvailableCallbacksUnvalidated(agent); expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); } - void expectCapabilitiesWith(int capability, MockNetworkAgent agent) { + NetworkCapabilities expectCapabilitiesWith(int capability, MockNetworkAgent agent) { CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent); NetworkCapabilities nc = (NetworkCapabilities) cbi.arg; assertTrue(nc.hasCapability(capability)); + return nc; } - void expectCapabilitiesWithout(int capability, MockNetworkAgent agent) { + NetworkCapabilities expectCapabilitiesWithout(int capability, MockNetworkAgent agent) { CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent); NetworkCapabilities nc = (NetworkCapabilities) cbi.arg; assertFalse(nc.hasCapability(capability)); + return nc; } void assertNoCallback() { @@ -1461,8 +1497,8 @@ public class ConnectivityServiceTest { ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); - genericNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent); - cellNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent); + genericNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + cellNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); waitFor(cv); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); @@ -1476,8 +1512,8 @@ public class ConnectivityServiceTest { cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent); - wifiNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); waitFor(cv); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); @@ -1500,8 +1536,8 @@ public class ConnectivityServiceTest { // Test validated networks mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - genericNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + genericNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); @@ -1513,10 +1549,10 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); genericNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); genericNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); - wifiNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + wifiNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); cellNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); @@ -1552,32 +1588,32 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); mCellNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mWiFiNetworkAgent.connect(true); // We get AVAILABLE on wifi when wifi connects and satisfies our unmetered request. // We then get LOSING when wifi validates and cell is outscored. - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // TODO: Investigate sending validated before losing. callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mEthernetNetworkAgent.connect(true); - callback.expectAvailableCallbacks(mEthernetNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent); // TODO: Investigate sending validated before losing. callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mEthernetNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); for (int i = 0; i < 4; i++) { MockNetworkAgent oldNetwork, newNetwork; @@ -1594,7 +1630,7 @@ public class ConnectivityServiceTest { callback.expectCallback(CallbackState.LOSING, oldNetwork); // TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no // longer lingering? - defaultCallback.expectAvailableCallbacks(newNetwork); + defaultCallback.expectAvailableCallbacksValidated(newNetwork); assertEquals(newNetwork.getNetwork(), mCm.getActiveNetwork()); } assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); @@ -1614,7 +1650,7 @@ public class ConnectivityServiceTest { // Disconnect our test networks. mWiFiNetworkAgent.disconnect(); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); mCellNetworkAgent.disconnect(); defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); @@ -1630,22 +1666,22 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); // Score: 10 - callback.expectAvailableCallbacks(mCellNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); // Bring up wifi with a score of 20. // Cell stays up because it would satisfy the default request if it validated. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); // Score: 20 - callback.expectAvailableCallbacks(mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); // Bring up wifi with a score of 70. @@ -1653,33 +1689,33 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.adjustScore(50); mWiFiNetworkAgent.connect(false); // Score: 70 - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); // Tear down wifi. mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); // Bring up wifi, then validate it. Previous versions would immediately tear down cell, but // it's arguably correct to linger it, since it was the default network before it validated. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // TODO: Investigate sending validated before losing. callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); mCellNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mCellNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); @@ -1687,12 +1723,12 @@ public class ConnectivityServiceTest { // If a network is lingering, and we add and remove a request from it, resume lingering. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // TODO: Investigate sending validated before losing. callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); @@ -1711,7 +1747,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); // Cell is now the default network. Pin it with a cell-specific request. noopCallback = new NetworkCallback(); // Can't reuse NetworkCallbacks. http://b/20701525 @@ -1720,8 +1756,8 @@ public class ConnectivityServiceTest { // Now connect wifi, and expect it to become the default network. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); // The default request is lingering on cell, but nothing happens to cell, and we send no // callbacks for it, because it's kept up by cellRequest. callback.assertNoCallback(); @@ -1737,14 +1773,14 @@ public class ConnectivityServiceTest { // Register a TRACK_DEFAULT request and check that it does not affect lingering. TestNetworkCallback trackDefaultCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(trackDefaultCallback); - trackDefaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + trackDefaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET); mEthernetNetworkAgent.connect(true); - callback.expectAvailableCallbacks(mEthernetNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent); callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent); - trackDefaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent); + trackDefaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); // Let linger run its course. callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent, lingerTimeoutMs); @@ -1771,13 +1807,13 @@ public class ConnectivityServiceTest { // Bring up validated cell. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); // Bring up unvalidated wifi with explicitlySelected=true. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.explicitlySelected(false); mWiFiNetworkAgent.connect(false); - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // Cell Remains the default. assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); @@ -1800,7 +1836,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.explicitlySelected(false); mWiFiNetworkAgent.connect(false); - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the // network to disconnect. @@ -1811,7 +1847,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.explicitlySelected(false); mWiFiNetworkAgent.connect(true); - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); @@ -1820,7 +1856,7 @@ public class ConnectivityServiceTest { // TODO: fix this. mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET); mEthernetNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork()); callback.assertNoCallback(); @@ -1993,7 +2029,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); mCellNetworkAgent.connectWithoutInternet(); - networkCallback.expectAvailableCallbacks(mCellNetworkAgent); + networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); verifyActiveNetwork(TRANSPORT_WIFI); // Test releasing NetworkRequest disconnects cellular with MMS @@ -2022,7 +2058,7 @@ public class ConnectivityServiceTest { MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS); mmsNetworkAgent.connectWithoutInternet(); - networkCallback.expectAvailableCallbacks(mmsNetworkAgent); + networkCallback.expectAvailableCallbacksUnvalidated(mmsNetworkAgent); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent @@ -2049,7 +2085,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); String firstRedirectUrl = "http://example.com/firstPath"; mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl); - captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl); // Take down network. @@ -2062,7 +2098,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); String secondRedirectUrl = "http://example.com/secondPath"; mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl); - captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl); // Make captive portal disappear then revalidate. @@ -2072,9 +2108,7 @@ public class ConnectivityServiceTest { captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); // Expect NET_CAPABILITY_VALIDATED onAvailable callback. - validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent); - // TODO: Investigate only sending available callbacks. - validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); // Break network connectivity. // Expect NET_CAPABILITY_VALIDATED onLost callback. @@ -2098,7 +2132,7 @@ public class ConnectivityServiceTest { // Bring up wifi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - validatedCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); // Check that calling startCaptivePortalApp does nothing. @@ -2109,7 +2143,7 @@ public class ConnectivityServiceTest { // Turn into a captive portal. mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 302; mCm.reportNetworkConnectivity(wifiNetwork, false); - captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); // Check that startCaptivePortalApp sends the expected intent. @@ -2122,7 +2156,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204; CaptivePortal c = (CaptivePortal) intent.getExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL); c.reportCaptivePortalDismissed(); - validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); mCm.unregisterNetworkCallback(validatedCallback); @@ -2165,7 +2199,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl); // Expect NET_CAPABILITY_VALIDATED onAvailable callback. - validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); // But there should be no CaptivePortal callback. captivePortalCallback.assertNoCallback(); } @@ -2203,14 +2237,14 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - cEmpty1.expectAvailableCallbacks(mWiFiNetworkAgent); - cEmpty2.expectAvailableCallbacks(mWiFiNetworkAgent); - cEmpty3.expectAvailableCallbacks(mWiFiNetworkAgent); - cEmpty4.expectAvailableCallbacks(mWiFiNetworkAgent); + cEmpty1.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + cEmpty2.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + cEmpty3.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + cEmpty4.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertNoCallbacks(cFoo, cBar); mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("foo")); - cFoo.expectAvailableCallbacks(mWiFiNetworkAgent); + cFoo.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); for (TestNetworkCallback c: emptyCallbacks) { c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent); } @@ -2219,7 +2253,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("bar")); cFoo.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - cBar.expectAvailableCallbacks(mWiFiNetworkAgent); + cBar.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); for (TestNetworkCallback c: emptyCallbacks) { c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent); } @@ -2348,14 +2382,14 @@ public class ConnectivityServiceTest { // Bring up cell and expect CALLBACK_AVAILABLE. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); // Bring up wifi and expect CALLBACK_AVAILABLE. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); cellNetworkCallback.assertNoCallback(); - defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); // Bring down cell. Expect no default network callback, since it wasn't the default. mCellNetworkAgent.disconnect(); @@ -2365,7 +2399,7 @@ public class ConnectivityServiceTest { // Bring up cell. Expect no default network callback, since it won't be the default. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); defaultNetworkCallback.assertNoCallback(); // Bring down wifi. Expect the default network callback to notified of LOST wifi @@ -2373,7 +2407,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.disconnect(); cellNetworkCallback.assertNoCallback(); defaultNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); mCellNetworkAgent.disconnect(); cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); @@ -2394,7 +2428,7 @@ public class ConnectivityServiceTest { // We should get onAvailable(), onCapabilitiesChanged(), and // onLinkPropertiesChanged() in rapid succession. Additionally, we // should get onCapabilitiesChanged() when the mobile network validates. - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); // Update LinkProperties. @@ -2415,7 +2449,7 @@ public class ConnectivityServiceTest { mCm.registerDefaultNetworkCallback(dfltNetworkCallback); // We should get onAvailable(), onCapabilitiesChanged(), onLinkPropertiesChanged(), // as well as onNetworkSuspended() in rapid succession. - dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent); + dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent, true); dfltNetworkCallback.assertNoCallback(); mCm.unregisterNetworkCallback(dfltNetworkCallback); @@ -2455,18 +2489,18 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - fgCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + fgCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); assertTrue(isForegroundNetwork(mCellNetworkAgent)); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); // When wifi connects, cell lingers. - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); - fgCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + fgCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); fgCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); fgCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); assertTrue(isForegroundNetwork(mCellNetworkAgent)); @@ -2490,8 +2524,8 @@ public class ConnectivityServiceTest { // is currently delivered before the onAvailable() callbacks. // TODO: Fix this. cellCallback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); - cellCallback.expectAvailableCallbacks(mCellNetworkAgent); - fgCallback.expectAvailableCallbacks(mCellNetworkAgent); + cellCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); // Expect a network capabilities update with FOREGROUND, because the most recent // request causes its state to change. callback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); @@ -2511,7 +2545,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - fgCallback.expectAvailableCallbacks(mCellNetworkAgent); + fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertTrue(isForegroundNetwork(mCellNetworkAgent)); mCm.unregisterNetworkCallback(callback); @@ -2651,7 +2685,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); testFactory.expectAddRequests(2); // Because the cell request changes score twice. mCellNetworkAgent.connect(true); - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); testFactory.waitForNetworkRequests(2); assertFalse(testFactory.getMyStartRequested()); // Because the cell network outscores us. @@ -2742,16 +2776,15 @@ public class ConnectivityServiceTest { // Bring up validated cell. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); Network cellNetwork = mCellNetworkAgent.getNetwork(); // Bring up validated wifi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); - validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent); - validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); // Fail validation on wifi. @@ -2772,18 +2805,18 @@ public class ConnectivityServiceTest { // that we switch back to cell. tracker.configRestrictsAvoidBadWifi = false; tracker.reevaluate(); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertEquals(mCm.getActiveNetwork(), cellNetwork); // Switch back to a restrictive carrier. tracker.configRestrictsAvoidBadWifi = true; tracker.reevaluate(); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mCm.getActiveNetwork(), wifiNetwork); // Simulate the user selecting "switch" on the dialog, and check that we switch to cell. mCm.setAvoidUnvalidated(wifiNetwork); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability( NET_CAPABILITY_VALIDATED)); assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability( @@ -2794,9 +2827,8 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.disconnect(); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); - validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent); - validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); wifiNetwork = mWiFiNetworkAgent.getNetwork(); // Fail validation on wifi and expect the dialog to appear. @@ -2810,7 +2842,7 @@ public class ConnectivityServiceTest { tracker.reevaluate(); // We now switch to cell. - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability( NET_CAPABILITY_VALIDATED)); assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability( @@ -2821,17 +2853,17 @@ public class ConnectivityServiceTest { // We switch to wifi and then to cell. Settings.Global.putString(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, null); tracker.reevaluate(); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mCm.getActiveNetwork(), wifiNetwork); Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1); tracker.reevaluate(); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertEquals(mCm.getActiveNetwork(), cellNetwork); // If cell goes down, we switch to wifi. mCellNetworkAgent.disconnect(); defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); validatedWifiCallback.assertNoCallback(); mCm.unregisterNetworkCallback(cellNetworkCallback); @@ -2873,7 +2905,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, timeoutMs); + networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, timeoutMs); // pass timeout and validate that UNAVAILABLE is not called networkCallback.assertNoCallback(); @@ -2894,7 +2926,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); final int assertTimeoutMs = 100; - networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, assertTimeoutMs); + networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, assertTimeoutMs); mWiFiNetworkAgent.disconnect(); networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); @@ -3381,7 +3413,7 @@ public class ConnectivityServiceTest { // Bring up wifi aware network. wifiAware.connect(false, false); - callback.expectAvailableCallbacks(wifiAware); + callback.expectAvailableCallbacksUnvalidated(wifiAware); assertNull(mCm.getActiveNetworkInfo()); assertNull(mCm.getActiveNetwork()); diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp index 80853b13a7cc..0e57f7ff0ed2 100644 --- a/tools/stats_log_api_gen/Collation.cpp +++ b/tools/stats_log_api_gen/Collation.cpp @@ -15,6 +15,7 @@ */ #include "Collation.h" +#include "frameworks/base/cmds/statsd/src/atoms.pb.h" #include <stdio.h> #include <map> @@ -137,6 +138,16 @@ java_type(const FieldDescriptor* field) } /** + * Gather the enums info. + */ +void collate_enums(const EnumDescriptor &enumDescriptor, AtomField *atomField) { + for (int i = 0; i < enumDescriptor.value_count(); i++) { + atomField->enumValues[enumDescriptor.value(i)->number()] = + enumDescriptor.value(i)->name().c_str(); + } +} + +/** * Gather the info about an atom proto. */ int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, @@ -221,11 +232,7 @@ int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, if (javaType == JAVA_TYPE_ENUM) { // All enums are treated as ints when it comes to function signatures. signature->push_back(JAVA_TYPE_INT); - const EnumDescriptor *enumDescriptor = field->enum_type(); - for (int i = 0; i < enumDescriptor->value_count(); i++) { - atField.enumValues[enumDescriptor->value(i)->number()] = - enumDescriptor->value(i)->name().c_str(); - } + collate_enums(*field->enum_type(), &atField); } else { signature->push_back(javaType); } @@ -235,6 +242,53 @@ int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, return errorCount; } +// This function flattens the fields of the AttributionNode proto in an Atom proto and generates +// the corresponding atom decl and signature. +bool get_non_chained_node(const Descriptor *atom, AtomDecl *atomDecl, + vector<java_type_t> *signature) { + // Build a sorted list of the fields. Descriptor has them in source file + // order. + map<int, const FieldDescriptor *> fields; + for (int j = 0; j < atom->field_count(); j++) { + const FieldDescriptor *field = atom->field(j); + fields[field->number()] = field; + } + + AtomDecl attributionDecl; + vector<java_type_t> attributionSignature; + collate_atom(android::os::statsd::AttributionNode::descriptor(), + &attributionDecl, &attributionSignature); + + // Build the type signature and the atom data. + bool has_attribution_node = false; + for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin(); + it != fields.end(); it++) { + const FieldDescriptor *field = it->second; + java_type_t javaType = java_type(field); + if (javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { + atomDecl->fields.insert( + atomDecl->fields.end(), + attributionDecl.fields.begin(), attributionDecl.fields.end()); + signature->insert( + signature->end(), + attributionSignature.begin(), attributionSignature.end()); + has_attribution_node = true; + + } else { + AtomField atField(field->name(), javaType); + if (javaType == JAVA_TYPE_ENUM) { + // All enums are treated as ints when it comes to function signatures. + signature->push_back(JAVA_TYPE_INT); + collate_enums(*field->enum_type(), &atField); + } else { + signature->push_back(javaType); + } + atomDecl->fields.push_back(atField); + } + } + return has_attribution_node; +} + /** * Gather the info about the atoms. */ @@ -266,6 +320,13 @@ int collate_atoms(const Descriptor *descriptor, Atoms *atoms) { errorCount += collate_atom(atom, &atomDecl, &signature); atoms->signatures.insert(signature); atoms->decls.insert(atomDecl); + + AtomDecl nonChainedAtomDecl(atomField->number(), atomField->name(), atom->name()); + vector<java_type_t> nonChainedSignature; + if (get_non_chained_node(atom, &nonChainedAtomDecl, &nonChainedSignature)) { + atoms->non_chained_signatures.insert(nonChainedSignature); + atoms->non_chained_decls.insert(nonChainedAtomDecl); + } } if (dbg) { diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h index cd0625c3a61d..0455eca62da8 100644 --- a/tools/stats_log_api_gen/Collation.h +++ b/tools/stats_log_api_gen/Collation.h @@ -32,6 +32,7 @@ using std::set; using std::string; using std::vector; using google::protobuf::Descriptor; +using google::protobuf::FieldDescriptor; /** * The types for atom parameters. @@ -93,14 +94,15 @@ struct AtomDecl { struct Atoms { set<vector<java_type_t>> signatures; set<AtomDecl> decls; + set<AtomDecl> non_chained_decls; + set<vector<java_type_t>> non_chained_signatures; }; /** * Gather the information about the atoms. Returns the number of errors. */ int collate_atoms(const Descriptor* descriptor, Atoms* atoms); -int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, - vector<java_type_t> *signature); +int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, vector<java_type_t> *signature); } // namespace stats_log_api_gen } // namespace android diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp index bbe6d63073c1..e0e6b5883e32 100644 --- a/tools/stats_log_api_gen/main.cpp +++ b/tools/stats_log_api_gen/main.cpp @@ -195,6 +195,47 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, fprintf(out, "\n"); } + for (set<vector<java_type_t>>::const_iterator signature = atoms.non_chained_signatures.begin(); + signature != atoms.non_chained_signatures.end(); signature++) { + int argIndex; + + fprintf(out, "void\n"); + fprintf(out, "stats_write_non_chained(int32_t code"); + argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); + argIndex++; + } + fprintf(out, ")\n"); + + fprintf(out, "{\n"); + argIndex = 1; + fprintf(out, " android_log_event_list event(kStatsEventTag);\n"); + fprintf(out, " event << code;\n\n"); + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + if (argIndex == 1) { + fprintf(out, " event.begin();\n\n"); + fprintf(out, " event.begin();\n"); + } + if (*arg == JAVA_TYPE_STRING) { + fprintf(out, " if (arg%d == NULL) {\n", argIndex); + fprintf(out, " arg%d = \"\";\n", argIndex); + fprintf(out, " }\n"); + } + fprintf(out, " event << arg%d;\n", argIndex); + if (argIndex == 2) { + fprintf(out, " event.end();\n\n"); + fprintf(out, " event.end();\n\n"); + } + argIndex++; + } + + fprintf(out, " event.write(LOG_ID_STATS);\n"); + fprintf(out, "}\n"); + fprintf(out, "\n"); + } // Print footer fprintf(out, "\n"); fprintf(out, "} // namespace util\n"); @@ -203,6 +244,68 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, return 0; } +void build_non_chained_decl_map(const Atoms& atoms, + std::map<int, set<AtomDecl>::const_iterator>* decl_map){ + for (set<AtomDecl>::const_iterator atom = atoms.non_chained_decls.begin(); + atom != atoms.non_chained_decls.end(); atom++) { + decl_map->insert(std::make_pair(atom->code, atom)); + } +} + +static void write_cpp_usage( + FILE* out, const string& method_name, const string& atom_code_name, + const AtomDecl& atom, const AtomDecl &attributionDecl) { + fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(), atom_code_name.c_str()); + for (vector<AtomField>::const_iterator field = atom.fields.begin(); + field != atom.fields.end(); field++) { + if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { + for (auto chainField : attributionDecl.fields) { + if (chainField.javaType == JAVA_TYPE_STRING) { + fprintf(out, ", const std::vector<%s>& %s", + cpp_type_name(chainField.javaType), + chainField.name.c_str()); + } else { + fprintf(out, ", const %s* %s, size_t %s_length", + cpp_type_name(chainField.javaType), + chainField.name.c_str(), chainField.name.c_str()); + } + } + } else { + fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str()); + } + } + fprintf(out, ");\n"); +} + +static void write_cpp_method_header( + FILE* out, const string& method_name, const set<vector<java_type_t>>& signatures, + const AtomDecl &attributionDecl) { + for (set<vector<java_type_t>>::const_iterator signature = signatures.begin(); + signature != signatures.end(); signature++) { + fprintf(out, "void %s(int32_t code ", method_name.c_str()); + int argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { + for (auto chainField : attributionDecl.fields) { + if (chainField.javaType == JAVA_TYPE_STRING) { + fprintf(out, ", const std::vector<%s>& %s", + cpp_type_name(chainField.javaType), chainField.name.c_str()); + } else { + fprintf(out, ", const %s* %s, size_t %s_length", + cpp_type_name(chainField.javaType), + chainField.name.c_str(), chainField.name.c_str()); + } + } + } else { + fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); + } + argIndex++; + } + fprintf(out, ");\n"); + + } +} static int write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl) @@ -228,6 +331,9 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio fprintf(out, " */\n"); fprintf(out, "enum {\n"); + std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map; + build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map); + size_t i = 0; // Print constants for (set<AtomDecl>::const_iterator atom = atoms.decls.begin(); @@ -236,26 +342,13 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio fprintf(out, "\n"); fprintf(out, " /**\n"); fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str()); - fprintf(out, " * Usage: stats_write(StatsLog.%s", constant.c_str()); - for (vector<AtomField>::const_iterator field = atom->fields.begin(); - field != atom->fields.end(); field++) { - if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { - for (auto chainField : attributionDecl.fields) { - if (chainField.javaType == JAVA_TYPE_STRING) { - fprintf(out, ", const std::vector<%s>& %s", - cpp_type_name(chainField.javaType), - chainField.name.c_str()); - } else { - fprintf(out, ", const %s* %s, size_t %s_length", - cpp_type_name(chainField.javaType), - chainField.name.c_str(), chainField.name.c_str()); - } - } - } else { - fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str()); - } + write_cpp_usage(out, "stats_write", constant, *atom, attributionDecl); + + auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code); + if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) { + write_cpp_usage(out, "stats_write_non_chained", constant, *non_chained_decl->second, + attributionDecl); } - fprintf(out, ");\n"); fprintf(out, " */\n"); char const* const comma = (i == atoms.decls.size() - 1) ? "" : ","; fprintf(out, " %s = %d%s\n", constant.c_str(), atom->code, comma); @@ -274,38 +367,64 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio fprintf(out, "//\n"); fprintf(out, "// Write methods\n"); fprintf(out, "//\n"); - for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); - signature != atoms.signatures.end(); signature++) { - fprintf(out, "void stats_write(int32_t code "); + write_cpp_method_header(out, "stats_write", atoms.signatures, attributionDecl); + + fprintf(out, "//\n"); + fprintf(out, "// Write flattened methods\n"); + fprintf(out, "//\n"); + write_cpp_method_header(out, "stats_write_non_chained", atoms.non_chained_signatures, + attributionDecl); + + fprintf(out, "\n"); + fprintf(out, "} // namespace util\n"); + fprintf(out, "} // namespace android\n"); + + return 0; +} + +static void write_java_usage( + FILE* out, const string& method_name, const string& atom_code_name, + const AtomDecl& atom, const AtomDecl &attributionDecl) { + fprintf(out, " * Usage: StatsLog.%s(StatsLog.%s", + method_name.c_str(), atom_code_name.c_str()); + for (vector<AtomField>::const_iterator field = atom.fields.begin(); + field != atom.fields.end(); field++) { + if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { + for (auto chainField : attributionDecl.fields) { + fprintf(out, ", %s[] %s", + java_type_name(chainField.javaType), chainField.name.c_str()); + } + } else { + fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str()); + } + } + fprintf(out, ");\n"); +} + +static void write_java_method( + FILE* out, const string& method_name, const set<vector<java_type_t>>& signatures, + const AtomDecl &attributionDecl) { + for (set<vector<java_type_t>>::const_iterator signature = signatures.begin(); + signature != signatures.end(); signature++) { + fprintf(out, " public static native void %s(int code", method_name.c_str()); int argIndex = 1; for (vector<java_type_t>::const_iterator arg = signature->begin(); arg != signature->end(); arg++) { if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { for (auto chainField : attributionDecl.fields) { - if (chainField.javaType == JAVA_TYPE_STRING) { - fprintf(out, ", const std::vector<%s>& %s", - cpp_type_name(chainField.javaType), chainField.name.c_str()); - } else { - fprintf(out, ", const %s* %s, size_t %s_length", - cpp_type_name(chainField.javaType), - chainField.name.c_str(), chainField.name.c_str()); - } + fprintf(out, ", %s[] %s", + java_type_name(chainField.javaType), chainField.name.c_str()); } } else { - fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); + fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex); } argIndex++; } fprintf(out, ");\n"); } - - fprintf(out, "\n"); - fprintf(out, "} // namespace util\n"); - fprintf(out, "} // namespace android\n"); - - return 0; } + static int write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl) { @@ -322,6 +441,9 @@ write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionD fprintf(out, "public class StatsLogInternal {\n"); fprintf(out, " // Constants for atom codes.\n"); + std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map; + build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map); + // Print constants for the atom codes. for (set<AtomDecl>::const_iterator atom = atoms.decls.begin(); atom != atoms.decls.end(); atom++) { @@ -329,19 +451,12 @@ write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionD fprintf(out, "\n"); fprintf(out, " /**\n"); fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str()); - fprintf(out, " * Usage: StatsLog.write(StatsLog.%s", constant.c_str()); - for (vector<AtomField>::const_iterator field = atom->fields.begin(); - field != atom->fields.end(); field++) { - if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { - for (auto chainField : attributionDecl.fields) { - fprintf(out, ", %s[] %s", - java_type_name(chainField.javaType), chainField.name.c_str()); - } - } else { - fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str()); - } + write_java_usage(out, "write", constant, *atom, attributionDecl); + auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code); + if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) { + write_java_usage(out, "write_non_chained", constant, *non_chained_decl->second, + attributionDecl); } - fprintf(out, ");\n"); fprintf(out, " */\n"); fprintf(out, " public static final int %s = %d;\n", constant.c_str(), atom->code); } @@ -371,24 +486,8 @@ write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionD // Print write methods fprintf(out, " // Write methods\n"); - for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); - signature != atoms.signatures.end(); signature++) { - fprintf(out, " public static native void write(int code"); - int argIndex = 1; - for (vector<java_type_t>::const_iterator arg = signature->begin(); - arg != signature->end(); arg++) { - if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { - for (auto chainField : attributionDecl.fields) { - fprintf(out, ", %s[] %s", - java_type_name(chainField.javaType), chainField.name.c_str()); - } - } else { - fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex); - } - argIndex++; - } - fprintf(out, ");\n"); - } + write_java_method(out, "write", atoms.signatures, attributionDecl); + write_java_method(out, "write_non_chained", atoms.non_chained_signatures, attributionDecl); fprintf(out, "}\n"); @@ -431,9 +530,9 @@ jni_array_type_name(java_type_t type) } static string -jni_function_name(const vector<java_type_t>& signature) +jni_function_name(const string& method_name, const vector<java_type_t>& signature) { - string result("StatsLog_write"); + string result("StatsLog_" + method_name); for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end(); arg++) { switch (*arg) { @@ -509,34 +608,17 @@ jni_function_signature(const vector<java_type_t>& signature, const AtomDecl &att } static int -write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl) +write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp_method_name, + const set<vector<java_type_t>>& signatures, const AtomDecl &attributionDecl) { - // Print prelude - fprintf(out, "// This file is autogenerated\n"); - fprintf(out, "\n"); - - fprintf(out, "#include <statslog.h>\n"); - fprintf(out, "\n"); - fprintf(out, "#include <nativehelper/JNIHelp.h>\n"); - fprintf(out, "#include <nativehelper/ScopedUtfChars.h>\n"); - fprintf(out, "#include <utils/Vector.h>\n"); - fprintf(out, "#include \"core_jni_helpers.h\"\n"); - fprintf(out, "#include \"jni.h\"\n"); - fprintf(out, "\n"); - fprintf(out, "#define UNUSED __attribute__((__unused__))\n"); - fprintf(out, "\n"); - - fprintf(out, "namespace android {\n"); - fprintf(out, "\n"); - // Print write methods - for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); - signature != atoms.signatures.end(); signature++) { + for (set<vector<java_type_t>>::const_iterator signature = signatures.begin(); + signature != signatures.end(); signature++) { int argIndex; fprintf(out, "static void\n"); fprintf(out, "%s(JNIEnv* env, jobject clazz UNUSED, jint code", - jni_function_name(*signature).c_str()); + jni_function_name(java_method_name, *signature).c_str()); argIndex = 1; for (vector<java_type_t>::const_iterator arg = signature->begin(); arg != signature->end(); arg++) { @@ -624,7 +706,7 @@ write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDe // stats_write call argIndex = 1; - fprintf(out, " android::util::stats_write(code"); + fprintf(out, " android::util::%s(code", cpp_method_name.c_str()); for (vector<java_type_t>::const_iterator arg = signature->begin(); arg != signature->end(); arg++) { if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { @@ -675,17 +757,53 @@ write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDe fprintf(out, "\n"); } + + return 0; +} + +void write_jni_registration(FILE* out, const string& java_method_name, + const set<vector<java_type_t>>& signatures, const AtomDecl &attributionDecl) { + for (set<vector<java_type_t>>::const_iterator signature = signatures.begin(); + signature != signatures.end(); signature++) { + fprintf(out, " { \"%s\", \"%s\", (void*)%s },\n", + java_method_name.c_str(), + jni_function_signature(*signature, attributionDecl).c_str(), + jni_function_name(java_method_name, *signature).c_str()); + } +} + +static int +write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl) +{ + // Print prelude + fprintf(out, "// This file is autogenerated\n"); + fprintf(out, "\n"); + + fprintf(out, "#include <statslog.h>\n"); + fprintf(out, "\n"); + fprintf(out, "#include <nativehelper/JNIHelp.h>\n"); + fprintf(out, "#include <nativehelper/ScopedUtfChars.h>\n"); + fprintf(out, "#include <utils/Vector.h>\n"); + fprintf(out, "#include \"core_jni_helpers.h\"\n"); + fprintf(out, "#include \"jni.h\"\n"); + fprintf(out, "\n"); + fprintf(out, "#define UNUSED __attribute__((__unused__))\n"); + fprintf(out, "\n"); + + fprintf(out, "namespace android {\n"); + fprintf(out, "\n"); + + write_stats_log_jni(out, "write", "stats_write", atoms.signatures, attributionDecl); + write_stats_log_jni(out, "write_non_chained", "stats_write_non_chained", + atoms.non_chained_signatures, attributionDecl); + // Print registration function table fprintf(out, "/*\n"); fprintf(out, " * JNI registration.\n"); fprintf(out, " */\n"); fprintf(out, "static const JNINativeMethod gRegisterMethods[] = {\n"); - for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); - signature != atoms.signatures.end(); signature++) { - fprintf(out, " { \"write\", \"%s\", (void*)%s },\n", - jni_function_signature(*signature, attributionDecl).c_str(), - jni_function_name(*signature).c_str()); - } + write_jni_registration(out, "write", atoms.signatures, attributionDecl); + write_jni_registration(out, "write_non_chained", atoms.non_chained_signatures, attributionDecl); fprintf(out, "};\n"); fprintf(out, "\n"); @@ -699,11 +817,9 @@ write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDe fprintf(out, "\n"); fprintf(out, "} // namespace android\n"); - return 0; } - static void print_usage() { |