diff options
330 files changed, 14685 insertions, 4020 deletions
diff --git a/Android.mk b/Android.mk index 2539c3dfbda6..fbcea9003df3 100644 --- a/Android.mk +++ b/Android.mk @@ -107,6 +107,7 @@ LOCAL_SRC_FILES += \ core/java/android/app/backup/IFullBackupRestoreObserver.aidl \ core/java/android/app/backup/IRestoreObserver.aidl \ core/java/android/app/backup/IRestoreSession.aidl \ + core/java/android/app/backup/ISelectBackupTransportCallback.aidl \ core/java/android/app/usage/IStorageStatsManager.aidl \ core/java/android/app/usage/IUsageStatsManager.aidl \ core/java/android/bluetooth/IBluetooth.aidl \ @@ -424,10 +425,12 @@ LOCAL_SRC_FILES += \ media/java/android/media/projection/IMediaProjectionManager.aidl \ media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl \ media/java/android/media/session/IActiveSessionsListener.aidl \ - media/java/android/media/session/ISessionController.aidl \ - media/java/android/media/session/ISessionControllerCallback.aidl \ + media/java/android/media/session/IOnMediaKeyListener.aidl \ + media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl \ media/java/android/media/session/ISession.aidl \ media/java/android/media/session/ISessionCallback.aidl \ + media/java/android/media/session/ISessionController.aidl \ + media/java/android/media/session/ISessionControllerCallback.aidl \ media/java/android/media/session/ISessionManager.aidl \ media/java/android/media/tv/ITvInputClient.aidl \ media/java/android/media/tv/ITvInputHardware.aidl \ @@ -536,6 +539,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ framework-protos \ android.hardware.thermal@1.0-java-constants \ android.hardware.health@1.0-java-constants \ + android.hardware.usb@1.0-java-constants \ LOCAL_PROTOC_OPTIMIZE_TYPE := stream LOCAL_PROTOC_FLAGS := \ diff --git a/api/current.txt b/api/current.txt index cd66ed67da02..c39bce828fb1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -408,6 +408,7 @@ package android { field public static final int colorForeground = 16842800; // 0x1010030 field public static final int colorForegroundInverse = 16843270; // 0x1010206 field public static final int colorLongPressedHighlight = 16843662; // 0x101038e + field public static final int colorMode = 16844108; // 0x101054c field public static final int colorMultiSelectHighlight = 16843665; // 0x1010391 field public static final int colorPressedHighlight = 16843661; // 0x101038d field public static final int colorPrimary = 16843827; // 0x1010433 @@ -1169,6 +1170,7 @@ package android { field public static final int spinnerStyle = 16842881; // 0x1010081 field public static final int spinnersShown = 16843595; // 0x101034b field public static final int splitMotionEvents = 16843503; // 0x10102ef + field public static final int splitName = 16844107; // 0x101054b field public static final int splitTrack = 16843852; // 0x101044c field public static final int spotShadowAlpha = 16843967; // 0x10104bf field public static final int src = 16843033; // 0x1010119 @@ -1817,6 +1819,7 @@ package android { field public static final int tabs = 16908307; // 0x1020013 field public static final int text1 = 16908308; // 0x1020014 field public static final int text2 = 16908309; // 0x1020015 + field public static final int textAssist = 16908353; // 0x1020041 field public static final int title = 16908310; // 0x1020016 field public static final int toggle = 16908311; // 0x1020017 field public static final int undo = 16908338; // 0x1020032 @@ -3536,7 +3539,6 @@ package android.app { method public int getRequestedOrientation(); method public final android.view.SearchEvent getSearchEvent(); method public int getTaskId(); - method public android.text.TextAssistant getTextAssistant(); method public final java.lang.CharSequence getTitle(); method public final int getTitleColor(); method public android.app.VoiceInteractor getVoiceInteractor(); @@ -3686,7 +3688,6 @@ package android.app { method public final void setResult(int, android.content.Intent); method public final deprecated void setSecondaryProgress(int); method public void setTaskDescription(android.app.ActivityManager.TaskDescription); - method public void setTextAssistant(android.text.TextAssistant); method public void setTitle(java.lang.CharSequence); method public void setTitle(int); method public deprecated void setTitleColor(int); @@ -8861,6 +8862,7 @@ package android.content { field public static final java.lang.String ACTION_CALL_BUTTON = "android.intent.action.CALL_BUTTON"; field public static final java.lang.String ACTION_CAMERA_BUTTON = "android.intent.action.CAMERA_BUTTON"; field public static final java.lang.String ACTION_CHOOSER = "android.intent.action.CHOOSER"; + field public static final java.lang.String ACTION_CLEAR_PACKAGE = "android.intent.action.CLEAR_PACKAGE"; field public static final java.lang.String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS"; field public static final java.lang.String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED"; field public static final java.lang.String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT"; @@ -9575,7 +9577,10 @@ package android.content.pm { method public int describeContents(); method public void dump(android.util.Printer, java.lang.String); method public final int getThemeResource(); - field public static final int CONFIG_COLORIMETRY = 16384; // 0x4000 + field public static final int COLOR_MODE_DEFAULT = 0; // 0x0 + field public static final int COLOR_MODE_HDR = 2; // 0x2 + field public static final int COLOR_MODE_WIDE_COLOR_GAMUT = 1; // 0x1 + field public static final int CONFIG_COLOR_MODE = 16384; // 0x4000 field public static final int CONFIG_DENSITY = 4096; // 0x1000 field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000 field public static final int CONFIG_KEYBOARD = 16; // 0x10 @@ -9636,6 +9641,7 @@ package android.content.pm { field public static final int SCREEN_ORIENTATION_USER_LANDSCAPE = 11; // 0xb field public static final int SCREEN_ORIENTATION_USER_PORTRAIT = 12; // 0xc field public static final int UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW = 1; // 0x1 + field public int colorMode; field public int configChanges; field public int documentLaunchMode; field public int flags; @@ -9760,6 +9766,7 @@ package android.content.pm { field public boolean enabled; field public boolean exported; field public java.lang.String processName; + field public java.lang.String splitName; } public class ConfigurationInfo implements android.os.Parcelable { @@ -10175,6 +10182,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice"; field public static final java.lang.String FEATURE_CONSUMER_IR = "android.hardware.consumerir"; field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin"; + field public static final java.lang.String FEATURE_EMBEDDED = "android.hardware.type.embedded"; field public static final java.lang.String FEATURE_ETHERNET = "android.hardware.ethernet"; field public static final java.lang.String FEATURE_FAKETOUCH = "android.hardware.faketouch"; field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct"; @@ -10610,16 +10618,16 @@ package android.content.res { method public void setToDefaults(); method public int updateFrom(android.content.res.Configuration); method public void writeToParcel(android.os.Parcel, int); - field public static final int COLORIMETRY_HDR_MASK = 12; // 0xc - field public static final int COLORIMETRY_HDR_NO = 4; // 0x4 - field public static final int COLORIMETRY_HDR_SHIFT = 2; // 0x2 - field public static final int COLORIMETRY_HDR_UNDEFINED = 0; // 0x0 - field public static final int COLORIMETRY_HDR_YES = 8; // 0x8 - field public static final int COLORIMETRY_UNDEFINED = 0; // 0x0 - field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_MASK = 3; // 0x3 - field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_NO = 1; // 0x1 - field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0 - field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_YES = 2; // 0x2 + field public static final int COLOR_MODE_HDR_MASK = 12; // 0xc + field public static final int COLOR_MODE_HDR_NO = 4; // 0x4 + field public static final int COLOR_MODE_HDR_SHIFT = 2; // 0x2 + field public static final int COLOR_MODE_HDR_UNDEFINED = 0; // 0x0 + field public static final int COLOR_MODE_HDR_YES = 8; // 0x8 + field public static final int COLOR_MODE_UNDEFINED = 0; // 0x0 + field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 3; // 0x3 + field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_NO = 1; // 0x1 + field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0 + field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 2; // 0x2 field public static final android.os.Parcelable.Creator<android.content.res.Configuration> CREATOR; field public static final int DENSITY_DPI_UNDEFINED = 0; // 0x0 field public static final int HARDKEYBOARDHIDDEN_NO = 1; // 0x1 @@ -10685,7 +10693,7 @@ package android.content.res { field public static final int UI_MODE_TYPE_UNDEFINED = 0; // 0x0 field public static final int UI_MODE_TYPE_VR_HEADSET = 7; // 0x7 field public static final int UI_MODE_TYPE_WATCH = 6; // 0x6 - field public int colorimetry; + field public int colorMode; field public int densityDpi; field public float fontScale; field public int hardKeyboardHidden; @@ -13226,6 +13234,7 @@ package android.graphics { } public class Typeface { + method public static void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback); method public static android.graphics.Typeface create(java.lang.String, int); method public static android.graphics.Typeface create(android.graphics.Typeface, int); method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String); @@ -13246,6 +13255,14 @@ package android.graphics { field public static final android.graphics.Typeface SERIF; } + public static abstract interface Typeface.FontRequestCallback { + method public abstract void onTypefaceRequestFailed(int); + method public abstract void onTypefaceRetrieved(android.graphics.Typeface); + field public static final int FAIL_REASON_FONT_LOAD_ERROR = 1; // 0x1 + field public static final int FAIL_REASON_FONT_NOT_FOUND = 2; // 0x2 + field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = 0; // 0x0 + } + public class Xfermode { ctor public Xfermode(); } @@ -13652,6 +13669,23 @@ package android.graphics.drawable { method public void addLevel(int, int, android.graphics.drawable.Drawable); } + public class MaskableIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { + ctor public MaskableIconDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable); + method public void draw(android.graphics.Canvas); + method public android.graphics.drawable.Drawable getBackground(); + method public android.graphics.drawable.Drawable getForeground(); + method public android.graphics.Path getIconMask(); + method public int getOpacity(); + method public void invalidateDrawable(android.graphics.drawable.Drawable); + method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long); + method public void setAlpha(int); + method public void setColorFilter(android.graphics.ColorFilter); + method public void setOpacity(int); + method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable); + field public static final float DEFAULT_VIEW_PORT_SCALE = 0.6666667f; + field public static final float MASK_SIZE = 100.0f; + } + public class NinePatchDrawable extends android.graphics.drawable.Drawable { ctor public deprecated NinePatchDrawable(android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String); ctor public NinePatchDrawable(android.content.res.Resources, android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String); @@ -13800,6 +13834,19 @@ package android.graphics.drawable.shapes { } +package android.graphics.fonts { + + public final class FontRequest implements android.os.Parcelable { + ctor public FontRequest(java.lang.String, java.lang.String); + method public int describeContents(); + method public java.lang.String getProviderAuthority(); + method public java.lang.String getQuery(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR; + } + +} + package android.graphics.pdf { public class PdfDocument { @@ -14161,6 +14208,7 @@ package android.hardware { public final class Sensor { method public int getFifoMaxEventCount(); method public int getFifoReservedEventCount(); + method public int getHighestDirectReportRateLevel(); method public int getId(); method public int getMaxDelay(); method public float getMaximumRange(); @@ -14174,6 +14222,7 @@ package android.hardware { method public java.lang.String getVendor(); method public int getVersion(); method public boolean isAdditionalInfoSupported(); + method public boolean isDirectChannelTypeSupported(int); method public boolean isDynamicSensor(); method public boolean isWakeUpSensor(); field public static final int REPORTING_MODE_CONTINUOUS = 0; // 0x0 @@ -14251,6 +14300,17 @@ package android.hardware { field public final int type; } + public final class SensorDirectChannel implements java.lang.AutoCloseable { + method public void close(); + method public boolean isValid(); + field public static final int RATE_FAST = 2; // 0x2 + field public static final int RATE_NORMAL = 1; // 0x1 + field public static final int RATE_STOP = 0; // 0x0 + field public static final int RATE_VERY_FAST = 3; // 0x3 + field public static final int TYPE_ASHMEM = 1; // 0x1 + field public static final int TYPE_HARDWARE_BUFFER = 2; // 0x2 + } + public class SensorEvent { field public int accuracy; field public android.hardware.Sensor sensor; @@ -14282,6 +14342,9 @@ package android.hardware { public abstract class SensorManager { method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor); + method public int configureDirectChannel(android.hardware.SensorDirectChannel, android.hardware.Sensor, int); + method public android.hardware.SensorDirectChannel createDirectChannel(android.os.MemoryFile); + method public android.hardware.SensorDirectChannel createDirectChannel(android.hardware.HardwareBuffer); method public boolean flush(android.hardware.SensorEventListener); method public static float getAltitude(float, float); method public static void getAngleChange(float[], float[], float[]); @@ -14411,7 +14474,7 @@ package android.hardware.camera2 { method public abstract int capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract int captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract void close(); - method public abstract void finishDeferredConfiguration(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException; + method public abstract void finalizeOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException; method public abstract android.hardware.camera2.CameraDevice getDevice(); method public abstract android.view.Surface getInputSurface(); method public abstract boolean isReprocessable(); @@ -15052,10 +15115,12 @@ package android.hardware.camera2.params { ctor public OutputConfiguration(android.view.Surface); ctor public OutputConfiguration(int, android.view.Surface); ctor public OutputConfiguration(android.util.Size, java.lang.Class<T>); + method public void addSurface(android.view.Surface); method public int describeContents(); + method public void enableSurfaceSharing(); method public android.view.Surface getSurface(); method public int getSurfaceGroupId(); - method public void setDeferredSurface(android.view.Surface); + method public java.util.List<android.view.Surface> getSurfaces(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR; field public static final int SURFACE_GROUP_ID_NONE = -1; // 0xffffffff @@ -17605,6 +17670,15 @@ package android.icu.text { method public boolean isTransitionalDifferent(); } + public final class ListFormatter { + method public java.lang.String format(java.lang.Object...); + method public java.lang.String format(java.util.Collection<?>); + method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale); + method public static android.icu.text.ListFormatter getInstance(java.util.Locale); + method public static android.icu.text.ListFormatter getInstance(); + method public java.lang.String getPatternForNumItems(int); + } + public abstract class LocaleDisplayNames { method public abstract android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type); method public abstract android.icu.text.LocaleDisplayNames.DialectHandling getDialectHandling(); @@ -17614,6 +17688,8 @@ package android.icu.text { method public static android.icu.text.LocaleDisplayNames getInstance(android.icu.util.ULocale, android.icu.text.DisplayContext...); method public static android.icu.text.LocaleDisplayNames getInstance(java.util.Locale, android.icu.text.DisplayContext...); method public abstract android.icu.util.ULocale getLocale(); + method public java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiList(java.util.Set<android.icu.util.ULocale>, boolean, java.util.Comparator<java.lang.Object>); + method public abstract java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiListCompareWholeItems(java.util.Set<android.icu.util.ULocale>, java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem>); method public abstract java.lang.String keyDisplayName(java.lang.String); method public abstract java.lang.String keyValueDisplayName(java.lang.String, java.lang.String); method public abstract java.lang.String languageDisplayName(java.lang.String); @@ -17633,9 +17709,19 @@ package android.icu.text { enum_constant public static final android.icu.text.LocaleDisplayNames.DialectHandling STANDARD_NAMES; } + public static class LocaleDisplayNames.UiListItem { + ctor public LocaleDisplayNames.UiListItem(android.icu.util.ULocale, android.icu.util.ULocale, java.lang.String, java.lang.String); + method public static java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem> getComparator(java.util.Comparator<java.lang.Object>, boolean); + field public final android.icu.util.ULocale minimized; + field public final android.icu.util.ULocale modified; + field public final java.lang.String nameInDisplayLocale; + field public final java.lang.String nameInSelf; + } + public class MeasureFormat extends android.icu.text.UFormat { method public final boolean equals(java.lang.Object); method public java.lang.StringBuffer format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition); + method public java.lang.StringBuilder formatMeasurePerUnit(android.icu.util.Measure, android.icu.util.MeasureUnit, java.lang.StringBuilder, java.text.FieldPosition); method public final java.lang.String formatMeasures(android.icu.util.Measure...); method public java.lang.StringBuilder formatMeasures(java.lang.StringBuilder, java.text.FieldPosition, android.icu.util.Measure...); method public static android.icu.text.MeasureFormat getCurrencyFormat(android.icu.util.ULocale); @@ -18104,6 +18190,14 @@ package android.icu.text { method public void setUpperCaseFirst(boolean); } + public final class ScientificNumberFormatter { + method public java.lang.String format(java.lang.Object); + method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.util.ULocale, java.lang.String, java.lang.String); + method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.text.DecimalFormat, java.lang.String, java.lang.String); + method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.util.ULocale); + method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.text.DecimalFormat); + } + public abstract class SearchIterator { ctor protected SearchIterator(java.text.CharacterIterator, android.icu.text.BreakIterator); method public final int first(); @@ -18879,6 +18973,34 @@ package android.icu.util { method public long getToDate(); } + public final class EthiopicCalendar extends android.icu.util.CECalendar { + ctor public EthiopicCalendar(); + ctor public EthiopicCalendar(android.icu.util.TimeZone); + ctor public EthiopicCalendar(java.util.Locale); + ctor public EthiopicCalendar(android.icu.util.ULocale); + ctor public EthiopicCalendar(android.icu.util.TimeZone, java.util.Locale); + ctor public EthiopicCalendar(android.icu.util.TimeZone, android.icu.util.ULocale); + ctor public EthiopicCalendar(int, int, int); + ctor public EthiopicCalendar(java.util.Date); + ctor public EthiopicCalendar(int, int, int, int, int, int); + method protected deprecated int handleGetExtendedYear(); + method public boolean isAmeteAlemEra(); + method public void setAmeteAlemEra(boolean); + field public static final int GENBOT = 8; // 0x8 + field public static final int HAMLE = 10; // 0xa + field public static final int HEDAR = 2; // 0x2 + field public static final int MEGABIT = 6; // 0x6 + field public static final int MESKEREM = 0; // 0x0 + field public static final int MIAZIA = 7; // 0x7 + field public static final int NEHASSE = 11; // 0xb + field public static final int PAGUMEN = 12; // 0xc + field public static final int SENE = 9; // 0x9 + field public static final int TAHSAS = 3; // 0x3 + field public static final int TEKEMT = 1; // 0x1 + field public static final int TER = 4; // 0x4 + field public static final int YEKATIT = 5; // 0x5 + } + public abstract interface Freezable<T> implements java.lang.Cloneable { method public abstract T cloneAsThawed(); method public abstract T freeze(); @@ -19407,6 +19529,35 @@ package android.icu.util { enum_constant public static final android.icu.util.ULocale.Category FORMAT; } + public final class UniversalTimeScale { + method public static android.icu.math.BigDecimal bigDecimalFrom(double, int); + method public static android.icu.math.BigDecimal bigDecimalFrom(long, int); + method public static android.icu.math.BigDecimal bigDecimalFrom(android.icu.math.BigDecimal, int); + method public static long from(long, int); + method public static long getTimeScaleValue(int, int); + method public static android.icu.math.BigDecimal toBigDecimal(long, int); + method public static android.icu.math.BigDecimal toBigDecimal(android.icu.math.BigDecimal, int); + method public static long toLong(long, int); + field public static final int DB2_TIME = 8; // 0x8 + field public static final int DOTNET_DATE_TIME = 4; // 0x4 + field public static final int EPOCH_OFFSET_PLUS_1_VALUE = 6; // 0x6 + field public static final int EPOCH_OFFSET_VALUE = 1; // 0x1 + field public static final int EXCEL_TIME = 7; // 0x7 + field public static final int FROM_MAX_VALUE = 3; // 0x3 + field public static final int FROM_MIN_VALUE = 2; // 0x2 + field public static final int ICU4C_TIME = 2; // 0x2 + field public static final int JAVA_TIME = 0; // 0x0 + field public static final int MAC_OLD_TIME = 5; // 0x5 + field public static final int MAC_TIME = 6; // 0x6 + field public static final int MAX_SCALE = 10; // 0xa + field public static final int TO_MAX_VALUE = 5; // 0x5 + field public static final int TO_MIN_VALUE = 4; // 0x4 + field public static final int UNITS_VALUE = 0; // 0x0 + field public static final int UNIX_MICROSECONDS_TIME = 9; // 0x9 + field public static final int UNIX_TIME = 1; // 0x1 + field public static final int WINDOWS_FILE_TIME = 3; // 0x3 + } + public abstract interface ValueIterator { method public abstract boolean next(android.icu.util.ValueIterator.Element); method public abstract void reset(); @@ -23580,6 +23731,7 @@ package android.media.tv { field public static final java.lang.String TYPE_NTSC = "TYPE_NTSC"; field public static final java.lang.String TYPE_OTHER = "TYPE_OTHER"; field public static final java.lang.String TYPE_PAL = "TYPE_PAL"; + field public static final java.lang.String TYPE_PREVIEW = "TYPE_PREVIEW"; field public static final java.lang.String TYPE_SECAM = "TYPE_SECAM"; field public static final java.lang.String TYPE_S_DMB = "TYPE_S_DMB"; field public static final java.lang.String TYPE_T_DMB = "TYPE_T_DMB"; @@ -23620,8 +23772,14 @@ package android.media.tv { field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2"; field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3"; field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id"; field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description"; field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri"; + field public static final java.lang.String COLUMN_PREVIEW_DURATION = "preview_duration"; + field public static final java.lang.String COLUMN_PREVIEW_INTENT_URI = "preview_intent_uri"; + field public static final java.lang.String COLUMN_PREVIEW_LAST_PLAYBACK_POSITION = "preview_last_playback_position"; + field public static final java.lang.String COLUMN_PREVIEW_VIDEO_URI = "preview_video_uri"; + field public static final java.lang.String COLUMN_PREVIEW_WEIGHT = "preview_weight"; field public static final java.lang.String COLUMN_RECORDING_PROHIBITED = "recording_prohibited"; field public static final java.lang.String COLUMN_SEARCHABLE = "searchable"; field public static final java.lang.String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number"; @@ -23750,6 +23908,7 @@ package android.media.tv { field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED"; field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS"; field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS"; + field public static final java.lang.String ACTION_VIEW_RECORDING_SCHEDULES = "android.media.tv.action.VIEW_RECORDING_SCHEDULES"; field public static final int INPUT_STATE_CONNECTED = 0; // 0x0 field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1 field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2 @@ -30609,6 +30768,7 @@ package android.preference { method public android.preference.Preference.OnPreferenceChangeListener getOnPreferenceChangeListener(); method public android.preference.Preference.OnPreferenceClickListener getOnPreferenceClickListener(); method public int getOrder(); + method public android.preference.PreferenceGroup getParent(); method protected boolean getPersistedBoolean(boolean); method protected float getPersistedFloat(float); method protected int getPersistedInt(int); @@ -32937,7 +33097,6 @@ package android.provider { method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public android.content.res.AssetFileDescriptor openTypedDocument(java.lang.String, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); - method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal); method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException; method public android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], android.os.Bundle) throws java.io.FileNotFoundException; @@ -32951,6 +33110,16 @@ package android.provider { method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]); } + public class FontsContract { + } + + public static final class FontsContract.Columns implements android.provider.BaseColumns { + ctor public FontsContract.Columns(); + field public static final java.lang.String STYLE = "font_style"; + field public static final java.lang.String TTC_INDEX = "font_ttc_index"; + field public static final java.lang.String VARIATION_SETTINGS = "font_variation_settings"; + } + public final deprecated class LiveFolders implements android.provider.BaseColumns { field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER"; field public static final java.lang.String DESCRIPTION = "description"; @@ -35719,6 +35888,7 @@ package android.service.notification { method public final android.os.IBinder onBind(android.content.Intent); method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean); method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String); + method public final void unsnoozeNotification(java.lang.String); method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel); field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; } @@ -35734,6 +35904,7 @@ package android.service.notification { method public final int getCurrentInterruptionFilter(); method public final int getCurrentListenerHints(); method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking(); + method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications(); method public android.os.IBinder onBind(android.content.Intent); method public void onInterruptionFilterChanged(int); method public void onListenerConnected(); @@ -35752,8 +35923,6 @@ package android.service.notification { method public final void setNotificationsShown(java.lang.String[]); method public final void snoozeNotification(java.lang.String, java.lang.String); method public final void snoozeNotification(java.lang.String, long); - method public final void snoozeNotification(java.lang.String); - method public final void unsnoozeNotification(java.lang.String); field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4 field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2 @@ -35788,6 +35957,7 @@ package android.service.notification { public static class NotificationListenerService.Ranking { ctor public NotificationListenerService.Ranking(); + method public boolean canShowBadge(); method public java.util.List<java.lang.String> getAdditionalPeople(); method public android.app.NotificationChannel getChannel(); method public int getImportance(); @@ -35829,7 +35999,6 @@ package android.service.notification { method public int getId(); method public java.lang.String getKey(); method public android.app.Notification getNotification(); - method public android.app.NotificationChannel getNotificationChannel(); method public java.lang.String getOverrideGroupKey(); method public java.lang.String getPackageName(); method public long getPostTime(); @@ -36245,6 +36414,7 @@ package android.speech.tts { method public abstract int getMaxBufferSize(); method public abstract boolean hasFinished(); method public abstract boolean hasStarted(); + method public default void rangeStart(int, int, int); method public abstract int start(int, int, int); } @@ -36397,6 +36567,7 @@ package android.speech.tts { method public void onError(java.lang.String, int); method public abstract void onStart(java.lang.String); method public void onStop(java.lang.String, boolean); + method public void onUtteranceRangeStart(java.lang.String, int, int); } public class Voice implements android.os.Parcelable { @@ -38488,6 +38659,7 @@ package android.telephony { method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String); method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String); method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String); + method public boolean isConcurrentVoiceAndDataAllowed(); method public boolean isHearingAidCompatibilitySupported(); method public boolean isNetworkRoaming(); method public boolean isSmsCapable(); @@ -39925,22 +40097,6 @@ package android.text { method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic); } - public abstract interface TextAssistant { - method public abstract void addLinks(android.text.Spannable, int); - method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int); - } - - public class TextClassification { - ctor public TextClassification(); - method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence(); - } - - public final class TextClassificationManager implements android.text.TextAssistant { - method public void addLinks(android.text.Spannable, int); - method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence); - method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int); - } - public abstract interface TextDirectionHeuristic { method public abstract boolean isRtl(char[], int, int); method public abstract boolean isRtl(java.lang.CharSequence, int, int); @@ -39956,13 +40112,6 @@ package android.text { field public static final android.text.TextDirectionHeuristic RTL; } - public final class TextLanguage { - ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>); - method public int getEndIndex(); - method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence(); - method public int getStartIndex(); - } - public class TextPaint extends android.graphics.Paint { ctor public TextPaint(); ctor public TextPaint(int); @@ -39975,13 +40124,6 @@ package android.text { field public int linkColor; } - public class TextSelection { - ctor public TextSelection(); - method public int getSelectionEndIndex(); - method public int getSelectionStartIndex(); - method public android.text.TextClassification getTextClassification(); - } - public class TextUtils { method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String); method public static java.lang.CharSequence concat(java.lang.CharSequence...); @@ -43726,6 +43868,7 @@ package android.view { method public float getElevation(); method public boolean getFilterTouchesWhenObscured(); method public boolean getFitsSystemWindows(); + method public int getFocusable(); method public java.util.ArrayList<android.view.View> getFocusables(int); method public void getFocusedRect(android.graphics.Rect); method public android.graphics.drawable.Drawable getForeground(); @@ -44030,6 +44173,7 @@ package android.view { method public void setFilterTouchesWhenObscured(boolean); method public void setFitsSystemWindows(boolean); method public void setFocusable(boolean); + method public void setFocusable(int); method public void setFocusableInTouchMode(boolean); method public void setFocusedByDefault(boolean); method public void setForeground(android.graphics.drawable.Drawable); @@ -44168,8 +44312,10 @@ package android.view { field protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET; field public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 2; // 0x2 field public static final int FIND_VIEWS_WITH_TEXT = 1; // 0x1 + field public static final int FOCUSABLE = 1; // 0x1 field public static final int FOCUSABLES_ALL = 0; // 0x0 field public static final int FOCUSABLES_TOUCH_MODE = 1; // 0x1 + field public static final int FOCUSABLE_AUTO = 16; // 0x10 field protected static final int[] FOCUSED_SELECTED_STATE_SET; field protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET; field protected static final int[] FOCUSED_STATE_SET; @@ -44199,6 +44345,7 @@ package android.view { field public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff field public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000 field public static final int MEASURED_STATE_TOO_SMALL = 16777216; // 0x1000000 + field public static final int NOT_FOCUSABLE = 0; // 0x0 field public static final int NO_ID = -1; // 0xffffffff field public static final int OVER_SCROLL_ALWAYS = 0; // 0x0 field public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; // 0x1 @@ -44903,6 +45050,7 @@ package android.view { method public boolean getAllowReturnTransitionOverlap(); method public final android.view.WindowManager.LayoutParams getAttributes(); method public final android.view.Window.Callback getCallback(); + method public int getColorMode(); method public final android.view.Window getContainer(); method public android.transition.Scene getContentScene(); method public final android.content.Context getContext(); @@ -44959,6 +45107,7 @@ package android.view { method public abstract void setChildDrawable(int, android.graphics.drawable.Drawable); method public abstract void setChildInt(int, int); method public void setClipToOutline(boolean); + method public void setColorMode(int); method public void setContainer(android.view.Window); method public abstract void setContentView(int); method public abstract void setContentView(android.view.View); @@ -45157,8 +45306,10 @@ package android.view { method public final int copyFrom(android.view.WindowManager.LayoutParams); method public java.lang.String debug(java.lang.String); method public int describeContents(); + method public int getColorMode(); method public final java.lang.CharSequence getTitle(); method public static boolean mayUseInputMethod(int); + method public void setColorMode(int); method public final void setTitle(java.lang.CharSequence); method public void writeToParcel(android.os.Parcel, int); field public static final int ALPHA_CHANGED = 128; // 0x80 @@ -46493,6 +46644,83 @@ package android.view.inputmethod { } +package android.view.textclassifier { + + public abstract interface LinksInfo { + method public abstract boolean apply(java.lang.CharSequence); + } + + public final class TextClassificationManager { + method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence); + method public android.view.textclassifier.TextClassifier getDefaultTextClassifier(); + } + + public final class TextClassificationResult { + method public float getConfidenceScore(java.lang.String); + method public java.lang.String getEntity(int); + method public int getEntityCount(); + method public android.graphics.drawable.Drawable getIcon(); + method public android.content.Intent getIntent(); + method public java.lang.CharSequence getLabel(); + method public android.view.View.OnClickListener getOnClickListener(); + method public java.lang.String getText(); + } + + public static final class TextClassificationResult.Builder { + ctor public TextClassificationResult.Builder(); + method public android.view.textclassifier.TextClassificationResult build(); + method public android.view.textclassifier.TextClassificationResult.Builder setEntityType(java.lang.String, float); + method public android.view.textclassifier.TextClassificationResult.Builder setIcon(android.graphics.drawable.Drawable); + method public android.view.textclassifier.TextClassificationResult.Builder setIntent(android.content.Intent); + method public android.view.textclassifier.TextClassificationResult.Builder setLabel(java.lang.String); + method public android.view.textclassifier.TextClassificationResult.Builder setOnClickListener(android.view.View.OnClickListener); + method public android.view.textclassifier.TextClassificationResult.Builder setText(java.lang.String); + } + + public abstract interface TextClassifier { + method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int); + method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int); + method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int); + field public static final android.view.textclassifier.TextClassifier NO_OP; + field public static final java.lang.String TYPE_ADDRESS = "address"; + field public static final java.lang.String TYPE_EMAIL = "email"; + field public static final java.lang.String TYPE_OTHER = "other"; + field public static final java.lang.String TYPE_PHONE = "phone"; + } + + public static abstract class TextClassifier.EntityType implements java.lang.annotation.Annotation { + } + + public final class TextLanguage { + method public float getConfidenceScore(java.util.Locale); + method public int getEndIndex(); + method public java.util.Locale getLanguage(int); + method public int getLanguageCount(); + method public int getStartIndex(); + } + + public static final class TextLanguage.Builder { + ctor public TextLanguage.Builder(int, int); + method public android.view.textclassifier.TextLanguage build(); + method public android.view.textclassifier.TextLanguage.Builder setLanguage(java.util.Locale, float); + } + + public final class TextSelection { + method public float getConfidenceScore(java.lang.String); + method public java.lang.String getEntity(int); + method public int getEntityCount(); + method public int getSelectionEndIndex(); + method public int getSelectionStartIndex(); + } + + public static final class TextSelection.Builder { + ctor public TextSelection.Builder(int, int); + method public android.view.textclassifier.TextSelection build(); + method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float); + } + +} + package android.view.textservice { public final class SentenceSuggestionsInfo implements android.os.Parcelable { @@ -49582,7 +49810,7 @@ package android.widget { method public float getShadowRadius(); method public final boolean getShowSoftInputOnFocus(); method public java.lang.CharSequence getText(); - method public android.text.TextAssistant getTextAssistant(); + method public android.view.textclassifier.TextClassifier getTextClassifier(); method public final android.content.res.ColorStateList getTextColors(); method public java.util.Locale getTextLocale(); method public android.os.LocaleList getTextLocales(); @@ -49698,7 +49926,7 @@ package android.widget { method public final void setText(int, android.widget.TextView.BufferType); method public void setTextAppearance(int); method public deprecated void setTextAppearance(android.content.Context, int); - method public void setTextAssistant(android.text.TextAssistant); + method public void setTextClassifier(android.view.textclassifier.TextClassifier); method public void setTextColor(int); method public void setTextColor(android.content.res.ColorStateList); method public void setTextIsSelectable(boolean); @@ -61841,6 +62069,9 @@ package java.util { method public static <E> java.util.Collection<E> checkedCollection(java.util.Collection<E>, java.lang.Class<E>); method public static <E> java.util.List<E> checkedList(java.util.List<E>, java.lang.Class<E>); method public static <K, V> java.util.Map<K, V> checkedMap(java.util.Map<K, V>, java.lang.Class<K>, java.lang.Class<V>); + method public static <K, V> java.util.NavigableMap<K, V> checkedNavigableMap(java.util.NavigableMap<K, V>, java.lang.Class<K>, java.lang.Class<V>); + method public static <E> java.util.NavigableSet<E> checkedNavigableSet(java.util.NavigableSet<E>, java.lang.Class<E>); + method public static <E> java.util.Queue<E> checkedQueue(java.util.Queue<E>, java.lang.Class<E>); method public static <E> java.util.Set<E> checkedSet(java.util.Set<E>, java.lang.Class<E>); method public static <K, V> java.util.SortedMap<K, V> checkedSortedMap(java.util.SortedMap<K, V>, java.lang.Class<K>, java.lang.Class<V>); method public static <E> java.util.SortedSet<E> checkedSortedSet(java.util.SortedSet<E>, java.lang.Class<E>); @@ -61851,7 +62082,11 @@ package java.util { method public static final <T> java.util.List<T> emptyList(); method public static <T> java.util.ListIterator<T> emptyListIterator(); method public static final <K, V> java.util.Map<K, V> emptyMap(); + method public static final <K, V> java.util.NavigableMap<K, V> emptyNavigableMap(); + method public static <E> java.util.NavigableSet<E> emptyNavigableSet(); method public static final <T> java.util.Set<T> emptySet(); + method public static final <K, V> java.util.SortedMap<K, V> emptySortedMap(); + method public static <E> java.util.SortedSet<E> emptySortedSet(); method public static <T> java.util.Enumeration<T> enumeration(java.util.Collection<T>); method public static <T> void fill(java.util.List<? super T>, T); method public static int frequency(java.util.Collection<?>, java.lang.Object); @@ -61880,12 +62115,16 @@ package java.util { method public static <T> java.util.Collection<T> synchronizedCollection(java.util.Collection<T>); method public static <T> java.util.List<T> synchronizedList(java.util.List<T>); method public static <K, V> java.util.Map<K, V> synchronizedMap(java.util.Map<K, V>); + method public static <K, V> java.util.NavigableMap<K, V> synchronizedNavigableMap(java.util.NavigableMap<K, V>); + method public static <T> java.util.NavigableSet<T> synchronizedNavigableSet(java.util.NavigableSet<T>); method public static <T> java.util.Set<T> synchronizedSet(java.util.Set<T>); method public static <K, V> java.util.SortedMap<K, V> synchronizedSortedMap(java.util.SortedMap<K, V>); method public static <T> java.util.SortedSet<T> synchronizedSortedSet(java.util.SortedSet<T>); method public static <T> java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>); method public static <T> java.util.List<T> unmodifiableList(java.util.List<? extends T>); method public static <K, V> java.util.Map<K, V> unmodifiableMap(java.util.Map<? extends K, ? extends V>); + method public static <K, V> java.util.NavigableMap<K, V> unmodifiableNavigableMap(java.util.NavigableMap<K, ? extends V>); + method public static <T> java.util.NavigableSet<T> unmodifiableNavigableSet(java.util.NavigableSet<T>); method public static <T> java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>); method public static <K, V> java.util.SortedMap<K, V> unmodifiableSortedMap(java.util.SortedMap<K, ? extends V>); method public static <T> java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>); diff --git a/api/system-current.txt b/api/system-current.txt index a3deab04eb28..1f3235d8af48 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -217,6 +217,7 @@ package android { field public static final java.lang.String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH"; field public static final java.lang.String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE"; field public static final java.lang.String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP"; + field public static final java.lang.String SET_MEDIA_KEY_LISTENER = "android.permission.SET_MEDIA_KEY_LISTENER"; field public static final java.lang.String SET_ORIENTATION = "android.permission.SET_ORIENTATION"; field public static final java.lang.String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED"; field public static final deprecated java.lang.String SET_PREFERRED_APPLICATIONS = "android.permission.SET_PREFERRED_APPLICATIONS"; @@ -224,6 +225,7 @@ package android { field public static final java.lang.String SET_SCREEN_COMPATIBILITY = "android.permission.SET_SCREEN_COMPATIBILITY"; field public static final java.lang.String SET_TIME = "android.permission.SET_TIME"; field public static final java.lang.String SET_TIME_ZONE = "android.permission.SET_TIME_ZONE"; + field public static final java.lang.String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER"; field public static final java.lang.String SET_WALLPAPER = "android.permission.SET_WALLPAPER"; field public static final java.lang.String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT"; field public static final java.lang.String SET_WALLPAPER_HINTS = "android.permission.SET_WALLPAPER_HINTS"; @@ -517,6 +519,7 @@ package android { field public static final int colorForeground = 16842800; // 0x1010030 field public static final int colorForegroundInverse = 16843270; // 0x1010206 field public static final int colorLongPressedHighlight = 16843662; // 0x101038e + field public static final int colorMode = 16844108; // 0x101054c field public static final int colorMultiSelectHighlight = 16843665; // 0x1010391 field public static final int colorPressedHighlight = 16843661; // 0x101038d field public static final int colorPrimary = 16843827; // 0x1010433 @@ -1282,6 +1285,7 @@ package android { field public static final int spinnerStyle = 16842881; // 0x1010081 field public static final int spinnersShown = 16843595; // 0x101034b field public static final int splitMotionEvents = 16843503; // 0x10102ef + field public static final int splitName = 16844107; // 0x101054b field public static final int splitTrack = 16843852; // 0x101044c field public static final int spotShadowAlpha = 16843967; // 0x10104bf field public static final int src = 16843033; // 0x1010119 @@ -1930,6 +1934,7 @@ package android { field public static final int tabs = 16908307; // 0x1020013 field public static final int text1 = 16908308; // 0x1020014 field public static final int text2 = 16908309; // 0x1020015 + field public static final int textAssist = 16908353; // 0x1020041 field public static final int title = 16908310; // 0x1020016 field public static final int toggle = 16908311; // 0x1020017 field public static final int undo = 16908338; // 0x1020032 @@ -3655,7 +3660,6 @@ package android.app { method public int getRequestedOrientation(); method public final android.view.SearchEvent getSearchEvent(); method public int getTaskId(); - method public android.text.TextAssistant getTextAssistant(); method public final java.lang.CharSequence getTitle(); method public final int getTitleColor(); method public android.app.VoiceInteractor getVoiceInteractor(); @@ -3807,7 +3811,6 @@ package android.app { method public final void setResult(int, android.content.Intent); method public final deprecated void setSecondaryProgress(int); method public void setTaskDescription(android.app.ActivityManager.TaskDescription); - method public void setTextAssistant(android.text.TextAssistant); method public void setTitle(java.lang.CharSequence); method public void setTitle(int); method public deprecated void setTitleColor(int); @@ -6787,15 +6790,18 @@ package android.app.backup { method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver); method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver, int); method public int requestRestore(android.app.backup.RestoreObserver); - method public java.lang.String selectBackupTransport(java.lang.String); + method public deprecated java.lang.String selectBackupTransport(java.lang.String); + method public void selectBackupTransport(android.content.ComponentName, android.app.backup.SelectBackupTransportCallback); method public void setAutoRestore(boolean); method public void setBackupEnabled(boolean); field public static final int ERROR_AGENT_FAILURE = -1003; // 0xfffffc15 field public static final int ERROR_BACKUP_NOT_ALLOWED = -2001; // 0xfffff82f field public static final int ERROR_PACKAGE_NOT_FOUND = -2002; // 0xfffff82e field public static final int ERROR_TRANSPORT_ABORTED = -1000; // 0xfffffc18 + field public static final int ERROR_TRANSPORT_INVALID = -2; // 0xfffffffe field public static final int ERROR_TRANSPORT_PACKAGE_REJECTED = -1002; // 0xfffffc16 field public static final int ERROR_TRANSPORT_QUOTA_EXCEEDED = -1005; // 0xfffffc13 + field public static final int ERROR_TRANSPORT_UNAVAILABLE = -1; // 0xffffffff field public static final int FLAG_NON_INCREMENTAL_BACKUP = 1; // 0x1 field public static final java.lang.String PACKAGE_MANAGER_SENTINEL = "@pm@"; field public static final int SUCCESS = 0; // 0x0 @@ -6910,6 +6916,12 @@ package android.app.backup { field public long token; } + public abstract class SelectBackupTransportCallback { + ctor public SelectBackupTransportCallback(); + method public void onFailure(int); + method public void onSuccess(java.lang.String); + } + public class SharedPreferencesBackupHelper extends android.app.backup.FileBackupHelperBase implements android.app.backup.BackupHelper { ctor public SharedPreferencesBackupHelper(android.content.Context, java.lang.String...); method public void performBackup(android.os.ParcelFileDescriptor, android.app.backup.BackupDataOutput, android.os.ParcelFileDescriptor); @@ -9249,6 +9261,7 @@ package android.content { field public static final java.lang.String ACTION_CALL_BUTTON = "android.intent.action.CALL_BUTTON"; field public static final java.lang.String ACTION_CAMERA_BUTTON = "android.intent.action.CAMERA_BUTTON"; field public static final java.lang.String ACTION_CHOOSER = "android.intent.action.CHOOSER"; + field public static final java.lang.String ACTION_CLEAR_PACKAGE = "android.intent.action.CLEAR_PACKAGE"; field public static final java.lang.String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS"; field public static final java.lang.String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED"; field public static final java.lang.String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT"; @@ -9978,7 +9991,10 @@ package android.content.pm { method public int describeContents(); method public void dump(android.util.Printer, java.lang.String); method public final int getThemeResource(); - field public static final int CONFIG_COLORIMETRY = 16384; // 0x4000 + field public static final int COLOR_MODE_DEFAULT = 0; // 0x0 + field public static final int COLOR_MODE_HDR = 2; // 0x2 + field public static final int COLOR_MODE_WIDE_COLOR_GAMUT = 1; // 0x1 + field public static final int CONFIG_COLOR_MODE = 16384; // 0x4000 field public static final int CONFIG_DENSITY = 4096; // 0x1000 field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000 field public static final int CONFIG_KEYBOARD = 16; // 0x10 @@ -10039,6 +10055,7 @@ package android.content.pm { field public static final int SCREEN_ORIENTATION_USER_LANDSCAPE = 11; // 0xb field public static final int SCREEN_ORIENTATION_USER_PORTRAIT = 12; // 0xc field public static final int UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW = 1; // 0x1 + field public int colorMode; field public int configChanges; field public int documentLaunchMode; field public int flags; @@ -10164,6 +10181,7 @@ package android.content.pm { field public boolean enabled; field public boolean exported; field public java.lang.String processName; + field public java.lang.String splitName; } public class ConfigurationInfo implements android.os.Parcelable { @@ -10194,7 +10212,8 @@ package android.content.pm { public final class EphemeralResolveInfo implements android.os.Parcelable { ctor public deprecated EphemeralResolveInfo(android.net.Uri, java.lang.String, java.util.List<android.content.IntentFilter>); - ctor public EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List<android.content.pm.EphemeralIntentFilter>); + ctor public deprecated EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List<android.content.pm.EphemeralIntentFilter>); + ctor public EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List<android.content.pm.EphemeralIntentFilter>, int); ctor public EphemeralResolveInfo(java.lang.String, java.lang.String, java.util.List<android.content.pm.EphemeralIntentFilter>); method public int describeContents(); method public byte[] getDigestBytes(); @@ -10202,6 +10221,7 @@ package android.content.pm { method public deprecated java.util.List<android.content.IntentFilter> getFilters(); method public java.util.List<android.content.pm.EphemeralIntentFilter> getIntentFilters(); method public java.lang.String getPackageName(); + method public int getVersionCode(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.content.pm.EphemeralResolveInfo> CREATOR; field public static final java.lang.String SHA_ALGORITHM = "SHA-256"; @@ -10644,6 +10664,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice"; field public static final java.lang.String FEATURE_CONSUMER_IR = "android.hardware.consumerir"; field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin"; + field public static final java.lang.String FEATURE_EMBEDDED = "android.hardware.type.embedded"; field public static final java.lang.String FEATURE_ETHERNET = "android.hardware.ethernet"; field public static final java.lang.String FEATURE_FAKETOUCH = "android.hardware.faketouch"; field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct"; @@ -11155,16 +11176,16 @@ package android.content.res { method public void setToDefaults(); method public int updateFrom(android.content.res.Configuration); method public void writeToParcel(android.os.Parcel, int); - field public static final int COLORIMETRY_HDR_MASK = 12; // 0xc - field public static final int COLORIMETRY_HDR_NO = 4; // 0x4 - field public static final int COLORIMETRY_HDR_SHIFT = 2; // 0x2 - field public static final int COLORIMETRY_HDR_UNDEFINED = 0; // 0x0 - field public static final int COLORIMETRY_HDR_YES = 8; // 0x8 - field public static final int COLORIMETRY_UNDEFINED = 0; // 0x0 - field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_MASK = 3; // 0x3 - field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_NO = 1; // 0x1 - field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0 - field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_YES = 2; // 0x2 + field public static final int COLOR_MODE_HDR_MASK = 12; // 0xc + field public static final int COLOR_MODE_HDR_NO = 4; // 0x4 + field public static final int COLOR_MODE_HDR_SHIFT = 2; // 0x2 + field public static final int COLOR_MODE_HDR_UNDEFINED = 0; // 0x0 + field public static final int COLOR_MODE_HDR_YES = 8; // 0x8 + field public static final int COLOR_MODE_UNDEFINED = 0; // 0x0 + field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 3; // 0x3 + field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_NO = 1; // 0x1 + field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0 + field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 2; // 0x2 field public static final android.os.Parcelable.Creator<android.content.res.Configuration> CREATOR; field public static final int DENSITY_DPI_UNDEFINED = 0; // 0x0 field public static final int HARDKEYBOARDHIDDEN_NO = 1; // 0x1 @@ -11230,7 +11251,7 @@ package android.content.res { field public static final int UI_MODE_TYPE_UNDEFINED = 0; // 0x0 field public static final int UI_MODE_TYPE_VR_HEADSET = 7; // 0x7 field public static final int UI_MODE_TYPE_WATCH = 6; // 0x6 - field public int colorimetry; + field public int colorMode; field public int densityDpi; field public float fontScale; field public int hardKeyboardHidden; @@ -13771,6 +13792,7 @@ package android.graphics { } public class Typeface { + method public static void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback); method public static android.graphics.Typeface create(java.lang.String, int); method public static android.graphics.Typeface create(android.graphics.Typeface, int); method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String); @@ -13791,6 +13813,14 @@ package android.graphics { field public static final android.graphics.Typeface SERIF; } + public static abstract interface Typeface.FontRequestCallback { + method public abstract void onTypefaceRequestFailed(int); + method public abstract void onTypefaceRetrieved(android.graphics.Typeface); + field public static final int FAIL_REASON_FONT_LOAD_ERROR = 1; // 0x1 + field public static final int FAIL_REASON_FONT_NOT_FOUND = 2; // 0x2 + field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = 0; // 0x0 + } + public class Xfermode { ctor public Xfermode(); } @@ -14197,6 +14227,23 @@ package android.graphics.drawable { method public void addLevel(int, int, android.graphics.drawable.Drawable); } + public class MaskableIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { + ctor public MaskableIconDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable); + method public void draw(android.graphics.Canvas); + method public android.graphics.drawable.Drawable getBackground(); + method public android.graphics.drawable.Drawable getForeground(); + method public android.graphics.Path getIconMask(); + method public int getOpacity(); + method public void invalidateDrawable(android.graphics.drawable.Drawable); + method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long); + method public void setAlpha(int); + method public void setColorFilter(android.graphics.ColorFilter); + method public void setOpacity(int); + method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable); + field public static final float DEFAULT_VIEW_PORT_SCALE = 0.6666667f; + field public static final float MASK_SIZE = 100.0f; + } + public class NinePatchDrawable extends android.graphics.drawable.Drawable { ctor public deprecated NinePatchDrawable(android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String); ctor public NinePatchDrawable(android.content.res.Resources, android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String); @@ -14345,6 +14392,19 @@ package android.graphics.drawable.shapes { } +package android.graphics.fonts { + + public final class FontRequest implements android.os.Parcelable { + ctor public FontRequest(java.lang.String, java.lang.String); + method public int describeContents(); + method public java.lang.String getProviderAuthority(); + method public java.lang.String getQuery(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR; + } + +} + package android.graphics.pdf { public class PdfDocument { @@ -14706,6 +14766,7 @@ package android.hardware { public final class Sensor { method public int getFifoMaxEventCount(); method public int getFifoReservedEventCount(); + method public int getHighestDirectReportRateLevel(); method public int getId(); method public int getMaxDelay(); method public float getMaximumRange(); @@ -14721,6 +14782,7 @@ package android.hardware { method public int getVersion(); method public boolean isAdditionalInfoSupported(); method public boolean isDataInjectionSupported(); + method public boolean isDirectChannelTypeSupported(int); method public boolean isDynamicSensor(); method public boolean isWakeUpSensor(); field public static final int REPORTING_MODE_CONTINUOUS = 0; // 0x0 @@ -14802,6 +14864,17 @@ package android.hardware { field public final int type; } + public final class SensorDirectChannel implements java.lang.AutoCloseable { + method public void close(); + method public boolean isValid(); + field public static final int RATE_FAST = 2; // 0x2 + field public static final int RATE_NORMAL = 1; // 0x1 + field public static final int RATE_STOP = 0; // 0x0 + field public static final int RATE_VERY_FAST = 3; // 0x3 + field public static final int TYPE_ASHMEM = 1; // 0x1 + field public static final int TYPE_HARDWARE_BUFFER = 2; // 0x2 + } + public class SensorEvent { field public int accuracy; field public android.hardware.Sensor sensor; @@ -14833,6 +14906,9 @@ package android.hardware { public abstract class SensorManager { method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor); + method public int configureDirectChannel(android.hardware.SensorDirectChannel, android.hardware.Sensor, int); + method public android.hardware.SensorDirectChannel createDirectChannel(android.os.MemoryFile); + method public android.hardware.SensorDirectChannel createDirectChannel(android.hardware.HardwareBuffer); method public boolean flush(android.hardware.SensorEventListener); method public static float getAltitude(float, float); method public static void getAngleChange(float[], float[], float[]); @@ -14964,7 +15040,7 @@ package android.hardware.camera2 { method public abstract int capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract int captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract void close(); - method public abstract void finishDeferredConfiguration(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException; + method public abstract void finalizeOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException; method public abstract android.hardware.camera2.CameraDevice getDevice(); method public abstract android.view.Surface getInputSurface(); method public abstract boolean isReprocessable(); @@ -15607,11 +15683,13 @@ package android.hardware.camera2.params { ctor public OutputConfiguration(android.view.Surface, int); ctor public OutputConfiguration(int, android.view.Surface, int); ctor public OutputConfiguration(android.util.Size, java.lang.Class<T>); + method public void addSurface(android.view.Surface); method public int describeContents(); + method public void enableSurfaceSharing(); method public int getRotation(); method public android.view.Surface getSurface(); method public int getSurfaceGroupId(); - method public void setDeferredSurface(android.view.Surface); + method public java.util.List<android.view.Surface> getSurfaces(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR; field public static final int ROTATION_0 = 0; // 0x0 @@ -18872,6 +18950,15 @@ package android.icu.text { method public boolean isTransitionalDifferent(); } + public final class ListFormatter { + method public java.lang.String format(java.lang.Object...); + method public java.lang.String format(java.util.Collection<?>); + method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale); + method public static android.icu.text.ListFormatter getInstance(java.util.Locale); + method public static android.icu.text.ListFormatter getInstance(); + method public java.lang.String getPatternForNumItems(int); + } + public abstract class LocaleDisplayNames { method public abstract android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type); method public abstract android.icu.text.LocaleDisplayNames.DialectHandling getDialectHandling(); @@ -18881,6 +18968,8 @@ package android.icu.text { method public static android.icu.text.LocaleDisplayNames getInstance(android.icu.util.ULocale, android.icu.text.DisplayContext...); method public static android.icu.text.LocaleDisplayNames getInstance(java.util.Locale, android.icu.text.DisplayContext...); method public abstract android.icu.util.ULocale getLocale(); + method public java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiList(java.util.Set<android.icu.util.ULocale>, boolean, java.util.Comparator<java.lang.Object>); + method public abstract java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiListCompareWholeItems(java.util.Set<android.icu.util.ULocale>, java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem>); method public abstract java.lang.String keyDisplayName(java.lang.String); method public abstract java.lang.String keyValueDisplayName(java.lang.String, java.lang.String); method public abstract java.lang.String languageDisplayName(java.lang.String); @@ -18900,9 +18989,19 @@ package android.icu.text { enum_constant public static final android.icu.text.LocaleDisplayNames.DialectHandling STANDARD_NAMES; } + public static class LocaleDisplayNames.UiListItem { + ctor public LocaleDisplayNames.UiListItem(android.icu.util.ULocale, android.icu.util.ULocale, java.lang.String, java.lang.String); + method public static java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem> getComparator(java.util.Comparator<java.lang.Object>, boolean); + field public final android.icu.util.ULocale minimized; + field public final android.icu.util.ULocale modified; + field public final java.lang.String nameInDisplayLocale; + field public final java.lang.String nameInSelf; + } + public class MeasureFormat extends android.icu.text.UFormat { method public final boolean equals(java.lang.Object); method public java.lang.StringBuffer format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition); + method public java.lang.StringBuilder formatMeasurePerUnit(android.icu.util.Measure, android.icu.util.MeasureUnit, java.lang.StringBuilder, java.text.FieldPosition); method public final java.lang.String formatMeasures(android.icu.util.Measure...); method public java.lang.StringBuilder formatMeasures(java.lang.StringBuilder, java.text.FieldPosition, android.icu.util.Measure...); method public static android.icu.text.MeasureFormat getCurrencyFormat(android.icu.util.ULocale); @@ -19371,6 +19470,14 @@ package android.icu.text { method public void setUpperCaseFirst(boolean); } + public final class ScientificNumberFormatter { + method public java.lang.String format(java.lang.Object); + method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.util.ULocale, java.lang.String, java.lang.String); + method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.text.DecimalFormat, java.lang.String, java.lang.String); + method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.util.ULocale); + method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.text.DecimalFormat); + } + public abstract class SearchIterator { ctor protected SearchIterator(java.text.CharacterIterator, android.icu.text.BreakIterator); method public final int first(); @@ -20146,6 +20253,34 @@ package android.icu.util { method public long getToDate(); } + public final class EthiopicCalendar extends android.icu.util.CECalendar { + ctor public EthiopicCalendar(); + ctor public EthiopicCalendar(android.icu.util.TimeZone); + ctor public EthiopicCalendar(java.util.Locale); + ctor public EthiopicCalendar(android.icu.util.ULocale); + ctor public EthiopicCalendar(android.icu.util.TimeZone, java.util.Locale); + ctor public EthiopicCalendar(android.icu.util.TimeZone, android.icu.util.ULocale); + ctor public EthiopicCalendar(int, int, int); + ctor public EthiopicCalendar(java.util.Date); + ctor public EthiopicCalendar(int, int, int, int, int, int); + method protected deprecated int handleGetExtendedYear(); + method public boolean isAmeteAlemEra(); + method public void setAmeteAlemEra(boolean); + field public static final int GENBOT = 8; // 0x8 + field public static final int HAMLE = 10; // 0xa + field public static final int HEDAR = 2; // 0x2 + field public static final int MEGABIT = 6; // 0x6 + field public static final int MESKEREM = 0; // 0x0 + field public static final int MIAZIA = 7; // 0x7 + field public static final int NEHASSE = 11; // 0xb + field public static final int PAGUMEN = 12; // 0xc + field public static final int SENE = 9; // 0x9 + field public static final int TAHSAS = 3; // 0x3 + field public static final int TEKEMT = 1; // 0x1 + field public static final int TER = 4; // 0x4 + field public static final int YEKATIT = 5; // 0x5 + } + public abstract interface Freezable<T> implements java.lang.Cloneable { method public abstract T cloneAsThawed(); method public abstract T freeze(); @@ -20674,6 +20809,35 @@ package android.icu.util { enum_constant public static final android.icu.util.ULocale.Category FORMAT; } + public final class UniversalTimeScale { + method public static android.icu.math.BigDecimal bigDecimalFrom(double, int); + method public static android.icu.math.BigDecimal bigDecimalFrom(long, int); + method public static android.icu.math.BigDecimal bigDecimalFrom(android.icu.math.BigDecimal, int); + method public static long from(long, int); + method public static long getTimeScaleValue(int, int); + method public static android.icu.math.BigDecimal toBigDecimal(long, int); + method public static android.icu.math.BigDecimal toBigDecimal(android.icu.math.BigDecimal, int); + method public static long toLong(long, int); + field public static final int DB2_TIME = 8; // 0x8 + field public static final int DOTNET_DATE_TIME = 4; // 0x4 + field public static final int EPOCH_OFFSET_PLUS_1_VALUE = 6; // 0x6 + field public static final int EPOCH_OFFSET_VALUE = 1; // 0x1 + field public static final int EXCEL_TIME = 7; // 0x7 + field public static final int FROM_MAX_VALUE = 3; // 0x3 + field public static final int FROM_MIN_VALUE = 2; // 0x2 + field public static final int ICU4C_TIME = 2; // 0x2 + field public static final int JAVA_TIME = 0; // 0x0 + field public static final int MAC_OLD_TIME = 5; // 0x5 + field public static final int MAC_TIME = 6; // 0x6 + field public static final int MAX_SCALE = 10; // 0xa + field public static final int TO_MAX_VALUE = 5; // 0x5 + field public static final int TO_MIN_VALUE = 4; // 0x4 + field public static final int UNITS_VALUE = 0; // 0x0 + field public static final int UNIX_MICROSECONDS_TIME = 9; // 0x9 + field public static final int UNIX_TIME = 1; // 0x1 + field public static final int WINDOWS_FILE_TIME = 3; // 0x3 + } + public abstract interface ValueIterator { method public abstract boolean next(android.icu.util.ValueIterator.Element); method public abstract void reset(); @@ -25067,12 +25231,22 @@ package android.media.session { method public void addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName, android.os.Handler); method public java.util.List<android.media.session.MediaController> getActiveSessions(android.content.ComponentName); method public void removeOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener); + method public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, android.os.Handler); + method public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, android.os.Handler); } public static abstract interface MediaSessionManager.OnActiveSessionsChangedListener { method public abstract void onActiveSessionsChanged(java.util.List<android.media.session.MediaController>); } + public static abstract interface MediaSessionManager.OnMediaKeyListener { + method public abstract boolean onMediaKey(android.view.KeyEvent); + } + + public static abstract interface MediaSessionManager.OnVolumeKeyLongPressListener { + method public abstract void onVolumeKeyLongPress(android.view.KeyEvent); + } + public final class PlaybackState implements android.os.Parcelable { method public int describeContents(); method public long getActions(); @@ -25268,6 +25442,7 @@ package android.media.tv { field public static final java.lang.String COLUMN_SEARCHABLE = "searchable"; field public static final java.lang.String COLUMN_SERVICE_ID = "service_id"; field public static final java.lang.String COLUMN_SERVICE_TYPE = "service_type"; + field public static final java.lang.String COLUMN_TRANSIENT = "transient"; field public static final java.lang.String COLUMN_TRANSPORT_STREAM_ID = "transport_stream_id"; field public static final java.lang.String COLUMN_TYPE = "type"; field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number"; @@ -25299,6 +25474,7 @@ package android.media.tv { field public static final java.lang.String TYPE_NTSC = "TYPE_NTSC"; field public static final java.lang.String TYPE_OTHER = "TYPE_OTHER"; field public static final java.lang.String TYPE_PAL = "TYPE_PAL"; + field public static final java.lang.String TYPE_PREVIEW = "TYPE_PREVIEW"; field public static final java.lang.String TYPE_SECAM = "TYPE_SECAM"; field public static final java.lang.String TYPE_S_DMB = "TYPE_S_DMB"; field public static final java.lang.String TYPE_T_DMB = "TYPE_T_DMB"; @@ -25339,8 +25515,14 @@ package android.media.tv { field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2"; field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3"; field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id"; field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description"; field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri"; + field public static final java.lang.String COLUMN_PREVIEW_DURATION = "preview_duration"; + field public static final java.lang.String COLUMN_PREVIEW_INTENT_URI = "preview_intent_uri"; + field public static final java.lang.String COLUMN_PREVIEW_LAST_PLAYBACK_POSITION = "preview_last_playback_position"; + field public static final java.lang.String COLUMN_PREVIEW_VIDEO_URI = "preview_video_uri"; + field public static final java.lang.String COLUMN_PREVIEW_WEIGHT = "preview_weight"; field public static final java.lang.String COLUMN_RECORDING_PROHIBITED = "recording_prohibited"; field public static final java.lang.String COLUMN_SEARCHABLE = "searchable"; field public static final java.lang.String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number"; @@ -25350,6 +25532,7 @@ package android.media.tv { field public static final java.lang.String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis"; field public static final java.lang.String COLUMN_THUMBNAIL_URI = "thumbnail_uri"; field public static final java.lang.String COLUMN_TITLE = "title"; + field public static final java.lang.String COLUMN_TRANSIENT = "transient"; field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number"; field public static final java.lang.String COLUMN_VIDEO_HEIGHT = "video_height"; field public static final java.lang.String COLUMN_VIDEO_WIDTH = "video_width"; @@ -25549,6 +25732,7 @@ package android.media.tv { field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED"; field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS"; field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS"; + field public static final java.lang.String ACTION_VIEW_RECORDING_SCHEDULES = "android.media.tv.action.VIEW_RECORDING_SCHEDULES"; field public static final int INPUT_STATE_CONNECTED = 0; // 0x0 field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1 field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2 @@ -25804,6 +25988,47 @@ package android.media.tv { } +package android.metrics { + + public class LogMaker { + ctor public LogMaker(int); + ctor public LogMaker(java.lang.Object[]); + method public android.metrics.LogMaker addTaggedData(int, java.lang.Object); + method public void deserialize(java.lang.Object[]); + method public int getCategory(); + method public long getCounterBucket(); + method public java.lang.String getCounterName(); + method public int getCounterValue(); + method public java.lang.String getPackageName(); + method public int getSubtype(); + method public java.lang.Object getTaggedData(int); + method public long getTimestamp(); + method public int getType(); + method public boolean isLongCounterBucket(); + method public boolean isValidValue(java.lang.Object); + method public java.lang.Object[] serialize(); + method public android.metrics.LogMaker setCategory(int); + method public android.metrics.LogMaker setCounterBucket(int); + method public android.metrics.LogMaker setCounterBucket(long); + method public android.metrics.LogMaker setCounterName(java.lang.String); + method public android.metrics.LogMaker setCounterValue(int); + method public android.metrics.LogMaker setPackageName(java.lang.String); + method public android.metrics.LogMaker setSubtype(int); + method public android.metrics.LogMaker setTimestamp(long); + method public android.metrics.LogMaker setType(int); + } + + public class MetricsReader { + ctor public MetricsReader(); + method public void checkpoint(); + method public boolean hasNext(); + method public android.metrics.LogMaker next(); + method public void read(long); + method public void reset(); + } + +} + package android.mtp { public final class MtpConstants { @@ -33376,6 +33601,7 @@ package android.preference { method public android.preference.Preference.OnPreferenceChangeListener getOnPreferenceChangeListener(); method public android.preference.Preference.OnPreferenceClickListener getOnPreferenceClickListener(); method public int getOrder(); + method public android.preference.PreferenceGroup getParent(); method protected boolean getPersistedBoolean(boolean); method protected float getPersistedFloat(float); method protected int getPersistedInt(int); @@ -35760,7 +35986,6 @@ package android.provider { method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public android.content.res.AssetFileDescriptor openTypedDocument(java.lang.String, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); - method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal); method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException; method public android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], android.os.Bundle) throws java.io.FileNotFoundException; @@ -35774,6 +35999,16 @@ package android.provider { method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]); } + public class FontsContract { + } + + public static final class FontsContract.Columns implements android.provider.BaseColumns { + ctor public FontsContract.Columns(); + field public static final java.lang.String STYLE = "font_style"; + field public static final java.lang.String TTC_INDEX = "font_ttc_index"; + field public static final java.lang.String VARIATION_SETTINGS = "font_variation_settings"; + } + public final deprecated class LiveFolders implements android.provider.BaseColumns { field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER"; field public static final java.lang.String DESCRIPTION = "description"; @@ -38189,6 +38424,18 @@ package android.security { package android.security.keystore { + public abstract class AttestationUtils { + method public static java.security.cert.X509Certificate[] attestDeviceIds(android.content.Context, int[], byte[]) throws android.security.keystore.DeviceIdAttestationException; + field public static final int ID_TYPE_IMEI = 2; // 0x2 + field public static final int ID_TYPE_MEID = 3; // 0x3 + field public static final int ID_TYPE_SERIAL = 1; // 0x1 + } + + public class DeviceIdAttestationException extends java.lang.Exception { + ctor public DeviceIdAttestationException(java.lang.String); + ctor public DeviceIdAttestationException(java.lang.String, java.lang.Throwable); + } + public class KeyExpiredException extends java.security.InvalidKeyException { ctor public KeyExpiredException(); ctor public KeyExpiredException(java.lang.String); @@ -38657,6 +38904,7 @@ package android.service.notification { method public final android.os.IBinder onBind(android.content.Intent); method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean); method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String); + method public final void unsnoozeNotification(java.lang.String); method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel); field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; } @@ -38674,6 +38922,7 @@ package android.service.notification { method public final int getCurrentInterruptionFilter(); method public final int getCurrentListenerHints(); method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking(); + method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications(); method public android.os.IBinder onBind(android.content.Intent); method public void onInterruptionFilterChanged(int); method public void onListenerConnected(); @@ -38694,9 +38943,7 @@ package android.service.notification { method public final void setOnNotificationPostedTrim(int); method public final void snoozeNotification(java.lang.String, java.lang.String); method public final void snoozeNotification(java.lang.String, long); - method public final void snoozeNotification(java.lang.String); method public void unregisterAsSystemService() throws android.os.RemoteException; - method public final void unsnoozeNotification(java.lang.String); field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4 field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2 @@ -38733,6 +38980,7 @@ package android.service.notification { public static class NotificationListenerService.Ranking { ctor public NotificationListenerService.Ranking(); + method public boolean canShowBadge(); method public java.util.List<java.lang.String> getAdditionalPeople(); method public android.app.NotificationChannel getChannel(); method public int getImportance(); @@ -38774,7 +39022,6 @@ package android.service.notification { method public int getId(); method public java.lang.String getKey(); method public android.app.Notification getNotification(); - method public android.app.NotificationChannel getNotificationChannel(); method public java.lang.String getOverrideGroupKey(); method public java.lang.String getPackageName(); method public long getPostTime(); @@ -39235,6 +39482,7 @@ package android.speech.tts { method public abstract int getMaxBufferSize(); method public abstract boolean hasFinished(); method public abstract boolean hasStarted(); + method public default void rangeStart(int, int, int); method public abstract int start(int, int, int); } @@ -39387,6 +39635,7 @@ package android.speech.tts { method public void onError(java.lang.String, int); method public abstract void onStart(java.lang.String); method public void onStop(java.lang.String, boolean); + method public void onUtteranceRangeStart(java.lang.String, int, int); } public class Voice implements android.os.Parcelable { @@ -41744,6 +41993,7 @@ package android.telephony { method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String); method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String); method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String); + method public boolean isConcurrentVoiceAndDataAllowed(); method public boolean isDataConnectivityPossible(); method public boolean isHearingAidCompatibilitySupported(); method public boolean isIdle(); @@ -43228,22 +43478,6 @@ package android.text { method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic); } - public abstract interface TextAssistant { - method public abstract void addLinks(android.text.Spannable, int); - method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int); - } - - public class TextClassification { - ctor public TextClassification(); - method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence(); - } - - public final class TextClassificationManager implements android.text.TextAssistant { - method public void addLinks(android.text.Spannable, int); - method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence); - method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int); - } - public abstract interface TextDirectionHeuristic { method public abstract boolean isRtl(char[], int, int); method public abstract boolean isRtl(java.lang.CharSequence, int, int); @@ -43259,13 +43493,6 @@ package android.text { field public static final android.text.TextDirectionHeuristic RTL; } - public final class TextLanguage { - ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>); - method public int getEndIndex(); - method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence(); - method public int getStartIndex(); - } - public class TextPaint extends android.graphics.Paint { ctor public TextPaint(); ctor public TextPaint(int); @@ -43278,13 +43505,6 @@ package android.text { field public int linkColor; } - public class TextSelection { - ctor public TextSelection(); - method public int getSelectionEndIndex(); - method public int getSelectionStartIndex(); - method public android.text.TextClassification getTextClassification(); - } - public class TextUtils { method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String); method public static java.lang.CharSequence concat(java.lang.CharSequence...); @@ -47030,6 +47250,7 @@ package android.view { method public float getElevation(); method public boolean getFilterTouchesWhenObscured(); method public boolean getFitsSystemWindows(); + method public int getFocusable(); method public java.util.ArrayList<android.view.View> getFocusables(int); method public void getFocusedRect(android.graphics.Rect); method public android.graphics.drawable.Drawable getForeground(); @@ -47334,6 +47555,7 @@ package android.view { method public void setFilterTouchesWhenObscured(boolean); method public void setFitsSystemWindows(boolean); method public void setFocusable(boolean); + method public void setFocusable(int); method public void setFocusableInTouchMode(boolean); method public void setFocusedByDefault(boolean); method public void setForeground(android.graphics.drawable.Drawable); @@ -47472,8 +47694,10 @@ package android.view { field protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET; field public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 2; // 0x2 field public static final int FIND_VIEWS_WITH_TEXT = 1; // 0x1 + field public static final int FOCUSABLE = 1; // 0x1 field public static final int FOCUSABLES_ALL = 0; // 0x0 field public static final int FOCUSABLES_TOUCH_MODE = 1; // 0x1 + field public static final int FOCUSABLE_AUTO = 16; // 0x10 field protected static final int[] FOCUSED_SELECTED_STATE_SET; field protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET; field protected static final int[] FOCUSED_STATE_SET; @@ -47503,6 +47727,7 @@ package android.view { field public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff field public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000 field public static final int MEASURED_STATE_TOO_SMALL = 16777216; // 0x1000000 + field public static final int NOT_FOCUSABLE = 0; // 0x0 field public static final int NO_ID = -1; // 0xffffffff field public static final int OVER_SCROLL_ALWAYS = 0; // 0x0 field public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; // 0x1 @@ -48207,6 +48432,7 @@ package android.view { method public boolean getAllowReturnTransitionOverlap(); method public final android.view.WindowManager.LayoutParams getAttributes(); method public final android.view.Window.Callback getCallback(); + method public int getColorMode(); method public final android.view.Window getContainer(); method public android.transition.Scene getContentScene(); method public final android.content.Context getContext(); @@ -48263,6 +48489,7 @@ package android.view { method public abstract void setChildDrawable(int, android.graphics.drawable.Drawable); method public abstract void setChildInt(int, int); method public void setClipToOutline(boolean); + method public void setColorMode(int); method public void setContainer(android.view.Window); method public abstract void setContentView(int); method public abstract void setContentView(android.view.View); @@ -48462,9 +48689,11 @@ package android.view { method public final int copyFrom(android.view.WindowManager.LayoutParams); method public java.lang.String debug(java.lang.String); method public int describeContents(); + method public int getColorMode(); method public final java.lang.CharSequence getTitle(); method public final long getUserActivityTimeout(); method public static boolean mayUseInputMethod(int); + method public void setColorMode(int); method public final void setTitle(java.lang.CharSequence); method public final void setUserActivityTimeout(long); method public void writeToParcel(android.os.Parcel, int); @@ -49800,6 +50029,83 @@ package android.view.inputmethod { } +package android.view.textclassifier { + + public abstract interface LinksInfo { + method public abstract boolean apply(java.lang.CharSequence); + } + + public final class TextClassificationManager { + method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence); + method public android.view.textclassifier.TextClassifier getDefaultTextClassifier(); + } + + public final class TextClassificationResult { + method public float getConfidenceScore(java.lang.String); + method public java.lang.String getEntity(int); + method public int getEntityCount(); + method public android.graphics.drawable.Drawable getIcon(); + method public android.content.Intent getIntent(); + method public java.lang.CharSequence getLabel(); + method public android.view.View.OnClickListener getOnClickListener(); + method public java.lang.String getText(); + } + + public static final class TextClassificationResult.Builder { + ctor public TextClassificationResult.Builder(); + method public android.view.textclassifier.TextClassificationResult build(); + method public android.view.textclassifier.TextClassificationResult.Builder setEntityType(java.lang.String, float); + method public android.view.textclassifier.TextClassificationResult.Builder setIcon(android.graphics.drawable.Drawable); + method public android.view.textclassifier.TextClassificationResult.Builder setIntent(android.content.Intent); + method public android.view.textclassifier.TextClassificationResult.Builder setLabel(java.lang.String); + method public android.view.textclassifier.TextClassificationResult.Builder setOnClickListener(android.view.View.OnClickListener); + method public android.view.textclassifier.TextClassificationResult.Builder setText(java.lang.String); + } + + public abstract interface TextClassifier { + method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int); + method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int); + method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int); + field public static final android.view.textclassifier.TextClassifier NO_OP; + field public static final java.lang.String TYPE_ADDRESS = "address"; + field public static final java.lang.String TYPE_EMAIL = "email"; + field public static final java.lang.String TYPE_OTHER = "other"; + field public static final java.lang.String TYPE_PHONE = "phone"; + } + + public static abstract class TextClassifier.EntityType implements java.lang.annotation.Annotation { + } + + public final class TextLanguage { + method public float getConfidenceScore(java.util.Locale); + method public int getEndIndex(); + method public java.util.Locale getLanguage(int); + method public int getLanguageCount(); + method public int getStartIndex(); + } + + public static final class TextLanguage.Builder { + ctor public TextLanguage.Builder(int, int); + method public android.view.textclassifier.TextLanguage build(); + method public android.view.textclassifier.TextLanguage.Builder setLanguage(java.util.Locale, float); + } + + public final class TextSelection { + method public float getConfidenceScore(java.lang.String); + method public java.lang.String getEntity(int); + method public int getEntityCount(); + method public int getSelectionEndIndex(); + method public int getSelectionStartIndex(); + } + + public static final class TextSelection.Builder { + ctor public TextSelection.Builder(int, int); + method public android.view.textclassifier.TextSelection build(); + method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float); + } + +} + package android.view.textservice { public final class SentenceSuggestionsInfo implements android.os.Parcelable { @@ -53250,7 +53556,7 @@ package android.widget { method public float getShadowRadius(); method public final boolean getShowSoftInputOnFocus(); method public java.lang.CharSequence getText(); - method public android.text.TextAssistant getTextAssistant(); + method public android.view.textclassifier.TextClassifier getTextClassifier(); method public final android.content.res.ColorStateList getTextColors(); method public java.util.Locale getTextLocale(); method public android.os.LocaleList getTextLocales(); @@ -53366,7 +53672,7 @@ package android.widget { method public final void setText(int, android.widget.TextView.BufferType); method public void setTextAppearance(int); method public deprecated void setTextAppearance(android.content.Context, int); - method public void setTextAssistant(android.text.TextAssistant); + method public void setTextClassifier(android.view.textclassifier.TextClassifier); method public void setTextColor(int); method public void setTextColor(android.content.res.ColorStateList); method public void setTextIsSelectable(boolean); @@ -65509,6 +65815,9 @@ package java.util { method public static <E> java.util.Collection<E> checkedCollection(java.util.Collection<E>, java.lang.Class<E>); method public static <E> java.util.List<E> checkedList(java.util.List<E>, java.lang.Class<E>); method public static <K, V> java.util.Map<K, V> checkedMap(java.util.Map<K, V>, java.lang.Class<K>, java.lang.Class<V>); + method public static <K, V> java.util.NavigableMap<K, V> checkedNavigableMap(java.util.NavigableMap<K, V>, java.lang.Class<K>, java.lang.Class<V>); + method public static <E> java.util.NavigableSet<E> checkedNavigableSet(java.util.NavigableSet<E>, java.lang.Class<E>); + method public static <E> java.util.Queue<E> checkedQueue(java.util.Queue<E>, java.lang.Class<E>); method public static <E> java.util.Set<E> checkedSet(java.util.Set<E>, java.lang.Class<E>); method public static <K, V> java.util.SortedMap<K, V> checkedSortedMap(java.util.SortedMap<K, V>, java.lang.Class<K>, java.lang.Class<V>); method public static <E> java.util.SortedSet<E> checkedSortedSet(java.util.SortedSet<E>, java.lang.Class<E>); @@ -65519,7 +65828,11 @@ package java.util { method public static final <T> java.util.List<T> emptyList(); method public static <T> java.util.ListIterator<T> emptyListIterator(); method public static final <K, V> java.util.Map<K, V> emptyMap(); + method public static final <K, V> java.util.NavigableMap<K, V> emptyNavigableMap(); + method public static <E> java.util.NavigableSet<E> emptyNavigableSet(); method public static final <T> java.util.Set<T> emptySet(); + method public static final <K, V> java.util.SortedMap<K, V> emptySortedMap(); + method public static <E> java.util.SortedSet<E> emptySortedSet(); method public static <T> java.util.Enumeration<T> enumeration(java.util.Collection<T>); method public static <T> void fill(java.util.List<? super T>, T); method public static int frequency(java.util.Collection<?>, java.lang.Object); @@ -65548,12 +65861,16 @@ package java.util { method public static <T> java.util.Collection<T> synchronizedCollection(java.util.Collection<T>); method public static <T> java.util.List<T> synchronizedList(java.util.List<T>); method public static <K, V> java.util.Map<K, V> synchronizedMap(java.util.Map<K, V>); + method public static <K, V> java.util.NavigableMap<K, V> synchronizedNavigableMap(java.util.NavigableMap<K, V>); + method public static <T> java.util.NavigableSet<T> synchronizedNavigableSet(java.util.NavigableSet<T>); method public static <T> java.util.Set<T> synchronizedSet(java.util.Set<T>); method public static <K, V> java.util.SortedMap<K, V> synchronizedSortedMap(java.util.SortedMap<K, V>); method public static <T> java.util.SortedSet<T> synchronizedSortedSet(java.util.SortedSet<T>); method public static <T> java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>); method public static <T> java.util.List<T> unmodifiableList(java.util.List<? extends T>); method public static <K, V> java.util.Map<K, V> unmodifiableMap(java.util.Map<? extends K, ? extends V>); + method public static <K, V> java.util.NavigableMap<K, V> unmodifiableNavigableMap(java.util.NavigableMap<K, ? extends V>); + method public static <T> java.util.NavigableSet<T> unmodifiableNavigableSet(java.util.NavigableSet<T>); method public static <T> java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>); method public static <K, V> java.util.SortedMap<K, V> unmodifiableSortedMap(java.util.SortedMap<K, ? extends V>); method public static <T> java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>); diff --git a/api/test-current.txt b/api/test-current.txt index f07f3b479111..0d4cffa8ae7e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -408,6 +408,7 @@ package android { field public static final int colorForeground = 16842800; // 0x1010030 field public static final int colorForegroundInverse = 16843270; // 0x1010206 field public static final int colorLongPressedHighlight = 16843662; // 0x101038e + field public static final int colorMode = 16844108; // 0x101054c field public static final int colorMultiSelectHighlight = 16843665; // 0x1010391 field public static final int colorPressedHighlight = 16843661; // 0x101038d field public static final int colorPrimary = 16843827; // 0x1010433 @@ -1169,6 +1170,7 @@ package android { field public static final int spinnerStyle = 16842881; // 0x1010081 field public static final int spinnersShown = 16843595; // 0x101034b field public static final int splitMotionEvents = 16843503; // 0x10102ef + field public static final int splitName = 16844107; // 0x101054b field public static final int splitTrack = 16843852; // 0x101044c field public static final int spotShadowAlpha = 16843967; // 0x10104bf field public static final int src = 16843033; // 0x1010119 @@ -1817,6 +1819,7 @@ package android { field public static final int tabs = 16908307; // 0x1020013 field public static final int text1 = 16908308; // 0x1020014 field public static final int text2 = 16908309; // 0x1020015 + field public static final int textAssist = 16908353; // 0x1020041 field public static final int title = 16908310; // 0x1020016 field public static final int toggle = 16908311; // 0x1020017 field public static final int undo = 16908338; // 0x1020032 @@ -3538,7 +3541,6 @@ package android.app { method public int getRequestedOrientation(); method public final android.view.SearchEvent getSearchEvent(); method public int getTaskId(); - method public android.text.TextAssistant getTextAssistant(); method public final java.lang.CharSequence getTitle(); method public final int getTitleColor(); method public android.app.VoiceInteractor getVoiceInteractor(); @@ -3688,7 +3690,6 @@ package android.app { method public final void setResult(int, android.content.Intent); method public final deprecated void setSecondaryProgress(int); method public void setTaskDescription(android.app.ActivityManager.TaskDescription); - method public void setTextAssistant(android.text.TextAssistant); method public void setTitle(java.lang.CharSequence); method public void setTitle(int); method public deprecated void setTitleColor(int); @@ -8886,6 +8887,7 @@ package android.content { field public static final java.lang.String ACTION_CALL_BUTTON = "android.intent.action.CALL_BUTTON"; field public static final java.lang.String ACTION_CAMERA_BUTTON = "android.intent.action.CAMERA_BUTTON"; field public static final java.lang.String ACTION_CHOOSER = "android.intent.action.CHOOSER"; + field public static final java.lang.String ACTION_CLEAR_PACKAGE = "android.intent.action.CLEAR_PACKAGE"; field public static final java.lang.String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS"; field public static final java.lang.String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED"; field public static final java.lang.String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT"; @@ -9600,7 +9602,10 @@ package android.content.pm { method public int describeContents(); method public void dump(android.util.Printer, java.lang.String); method public final int getThemeResource(); - field public static final int CONFIG_COLORIMETRY = 16384; // 0x4000 + field public static final int COLOR_MODE_DEFAULT = 0; // 0x0 + field public static final int COLOR_MODE_HDR = 2; // 0x2 + field public static final int COLOR_MODE_WIDE_COLOR_GAMUT = 1; // 0x1 + field public static final int CONFIG_COLOR_MODE = 16384; // 0x4000 field public static final int CONFIG_DENSITY = 4096; // 0x1000 field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000 field public static final int CONFIG_KEYBOARD = 16; // 0x10 @@ -9661,6 +9666,7 @@ package android.content.pm { field public static final int SCREEN_ORIENTATION_USER_LANDSCAPE = 11; // 0xb field public static final int SCREEN_ORIENTATION_USER_PORTRAIT = 12; // 0xc field public static final int UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW = 1; // 0x1 + field public int colorMode; field public int configChanges; field public int documentLaunchMode; field public int flags; @@ -9787,6 +9793,7 @@ package android.content.pm { field public boolean enabled; field public boolean exported; field public java.lang.String processName; + field public java.lang.String splitName; } public class ConfigurationInfo implements android.os.Parcelable { @@ -10205,6 +10212,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice"; field public static final java.lang.String FEATURE_CONSUMER_IR = "android.hardware.consumerir"; field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin"; + field public static final java.lang.String FEATURE_EMBEDDED = "android.hardware.type.embedded"; field public static final java.lang.String FEATURE_ETHERNET = "android.hardware.ethernet"; field public static final java.lang.String FEATURE_FAKETOUCH = "android.hardware.faketouch"; field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct"; @@ -10642,16 +10650,16 @@ package android.content.res { method public void setToDefaults(); method public int updateFrom(android.content.res.Configuration); method public void writeToParcel(android.os.Parcel, int); - field public static final int COLORIMETRY_HDR_MASK = 12; // 0xc - field public static final int COLORIMETRY_HDR_NO = 4; // 0x4 - field public static final int COLORIMETRY_HDR_SHIFT = 2; // 0x2 - field public static final int COLORIMETRY_HDR_UNDEFINED = 0; // 0x0 - field public static final int COLORIMETRY_HDR_YES = 8; // 0x8 - field public static final int COLORIMETRY_UNDEFINED = 0; // 0x0 - field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_MASK = 3; // 0x3 - field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_NO = 1; // 0x1 - field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0 - field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_YES = 2; // 0x2 + field public static final int COLOR_MODE_HDR_MASK = 12; // 0xc + field public static final int COLOR_MODE_HDR_NO = 4; // 0x4 + field public static final int COLOR_MODE_HDR_SHIFT = 2; // 0x2 + field public static final int COLOR_MODE_HDR_UNDEFINED = 0; // 0x0 + field public static final int COLOR_MODE_HDR_YES = 8; // 0x8 + field public static final int COLOR_MODE_UNDEFINED = 0; // 0x0 + field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 3; // 0x3 + field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_NO = 1; // 0x1 + field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0 + field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 2; // 0x2 field public static final android.os.Parcelable.Creator<android.content.res.Configuration> CREATOR; field public static final int DENSITY_DPI_UNDEFINED = 0; // 0x0 field public static final int HARDKEYBOARDHIDDEN_NO = 1; // 0x1 @@ -10717,7 +10725,7 @@ package android.content.res { field public static final int UI_MODE_TYPE_UNDEFINED = 0; // 0x0 field public static final int UI_MODE_TYPE_VR_HEADSET = 7; // 0x7 field public static final int UI_MODE_TYPE_WATCH = 6; // 0x6 - field public int colorimetry; + field public int colorMode; field public int densityDpi; field public float fontScale; field public int hardKeyboardHidden; @@ -13258,6 +13266,7 @@ package android.graphics { } public class Typeface { + method public static void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback); method public static android.graphics.Typeface create(java.lang.String, int); method public static android.graphics.Typeface create(android.graphics.Typeface, int); method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String); @@ -13278,6 +13287,14 @@ package android.graphics { field public static final android.graphics.Typeface SERIF; } + public static abstract interface Typeface.FontRequestCallback { + method public abstract void onTypefaceRequestFailed(int); + method public abstract void onTypefaceRetrieved(android.graphics.Typeface); + field public static final int FAIL_REASON_FONT_LOAD_ERROR = 1; // 0x1 + field public static final int FAIL_REASON_FONT_NOT_FOUND = 2; // 0x2 + field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = 0; // 0x0 + } + public class Xfermode { ctor public Xfermode(); } @@ -13684,6 +13701,23 @@ package android.graphics.drawable { method public void addLevel(int, int, android.graphics.drawable.Drawable); } + public class MaskableIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { + ctor public MaskableIconDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable); + method public void draw(android.graphics.Canvas); + method public android.graphics.drawable.Drawable getBackground(); + method public android.graphics.drawable.Drawable getForeground(); + method public android.graphics.Path getIconMask(); + method public int getOpacity(); + method public void invalidateDrawable(android.graphics.drawable.Drawable); + method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long); + method public void setAlpha(int); + method public void setColorFilter(android.graphics.ColorFilter); + method public void setOpacity(int); + method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable); + field public static final float DEFAULT_VIEW_PORT_SCALE = 0.6666667f; + field public static final float MASK_SIZE = 100.0f; + } + public class NinePatchDrawable extends android.graphics.drawable.Drawable { ctor public deprecated NinePatchDrawable(android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String); ctor public NinePatchDrawable(android.content.res.Resources, android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String); @@ -13832,6 +13866,19 @@ package android.graphics.drawable.shapes { } +package android.graphics.fonts { + + public final class FontRequest implements android.os.Parcelable { + ctor public FontRequest(java.lang.String, java.lang.String); + method public int describeContents(); + method public java.lang.String getProviderAuthority(); + method public java.lang.String getQuery(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR; + } + +} + package android.graphics.pdf { public class PdfDocument { @@ -14193,6 +14240,7 @@ package android.hardware { public final class Sensor { method public int getFifoMaxEventCount(); method public int getFifoReservedEventCount(); + method public int getHighestDirectReportRateLevel(); method public int getId(); method public int getMaxDelay(); method public float getMaximumRange(); @@ -14206,6 +14254,7 @@ package android.hardware { method public java.lang.String getVendor(); method public int getVersion(); method public boolean isAdditionalInfoSupported(); + method public boolean isDirectChannelTypeSupported(int); method public boolean isDynamicSensor(); method public boolean isWakeUpSensor(); field public static final int REPORTING_MODE_CONTINUOUS = 0; // 0x0 @@ -14283,6 +14332,17 @@ package android.hardware { field public final int type; } + public final class SensorDirectChannel implements java.lang.AutoCloseable { + method public void close(); + method public boolean isValid(); + field public static final int RATE_FAST = 2; // 0x2 + field public static final int RATE_NORMAL = 1; // 0x1 + field public static final int RATE_STOP = 0; // 0x0 + field public static final int RATE_VERY_FAST = 3; // 0x3 + field public static final int TYPE_ASHMEM = 1; // 0x1 + field public static final int TYPE_HARDWARE_BUFFER = 2; // 0x2 + } + public class SensorEvent { field public int accuracy; field public android.hardware.Sensor sensor; @@ -14314,6 +14374,9 @@ package android.hardware { public abstract class SensorManager { method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor); + method public int configureDirectChannel(android.hardware.SensorDirectChannel, android.hardware.Sensor, int); + method public android.hardware.SensorDirectChannel createDirectChannel(android.os.MemoryFile); + method public android.hardware.SensorDirectChannel createDirectChannel(android.hardware.HardwareBuffer); method public boolean flush(android.hardware.SensorEventListener); method public static float getAltitude(float, float); method public static void getAngleChange(float[], float[], float[]); @@ -14443,7 +14506,7 @@ package android.hardware.camera2 { method public abstract int capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract int captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public abstract void close(); - method public abstract void finishDeferredConfiguration(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException; + method public abstract void finalizeOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException; method public abstract android.hardware.camera2.CameraDevice getDevice(); method public abstract android.view.Surface getInputSurface(); method public abstract boolean isReprocessable(); @@ -15084,10 +15147,12 @@ package android.hardware.camera2.params { ctor public OutputConfiguration(android.view.Surface); ctor public OutputConfiguration(int, android.view.Surface); ctor public OutputConfiguration(android.util.Size, java.lang.Class<T>); + method public void addSurface(android.view.Surface); method public int describeContents(); + method public void enableSurfaceSharing(); method public android.view.Surface getSurface(); method public int getSurfaceGroupId(); - method public void setDeferredSurface(android.view.Surface); + method public java.util.List<android.view.Surface> getSurfaces(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR; field public static final int SURFACE_GROUP_ID_NONE = -1; // 0xffffffff @@ -17637,6 +17702,15 @@ package android.icu.text { method public boolean isTransitionalDifferent(); } + public final class ListFormatter { + method public java.lang.String format(java.lang.Object...); + method public java.lang.String format(java.util.Collection<?>); + method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale); + method public static android.icu.text.ListFormatter getInstance(java.util.Locale); + method public static android.icu.text.ListFormatter getInstance(); + method public java.lang.String getPatternForNumItems(int); + } + public abstract class LocaleDisplayNames { method public abstract android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type); method public abstract android.icu.text.LocaleDisplayNames.DialectHandling getDialectHandling(); @@ -17646,6 +17720,8 @@ package android.icu.text { method public static android.icu.text.LocaleDisplayNames getInstance(android.icu.util.ULocale, android.icu.text.DisplayContext...); method public static android.icu.text.LocaleDisplayNames getInstance(java.util.Locale, android.icu.text.DisplayContext...); method public abstract android.icu.util.ULocale getLocale(); + method public java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiList(java.util.Set<android.icu.util.ULocale>, boolean, java.util.Comparator<java.lang.Object>); + method public abstract java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiListCompareWholeItems(java.util.Set<android.icu.util.ULocale>, java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem>); method public abstract java.lang.String keyDisplayName(java.lang.String); method public abstract java.lang.String keyValueDisplayName(java.lang.String, java.lang.String); method public abstract java.lang.String languageDisplayName(java.lang.String); @@ -17665,9 +17741,19 @@ package android.icu.text { enum_constant public static final android.icu.text.LocaleDisplayNames.DialectHandling STANDARD_NAMES; } + public static class LocaleDisplayNames.UiListItem { + ctor public LocaleDisplayNames.UiListItem(android.icu.util.ULocale, android.icu.util.ULocale, java.lang.String, java.lang.String); + method public static java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem> getComparator(java.util.Comparator<java.lang.Object>, boolean); + field public final android.icu.util.ULocale minimized; + field public final android.icu.util.ULocale modified; + field public final java.lang.String nameInDisplayLocale; + field public final java.lang.String nameInSelf; + } + public class MeasureFormat extends android.icu.text.UFormat { method public final boolean equals(java.lang.Object); method public java.lang.StringBuffer format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition); + method public java.lang.StringBuilder formatMeasurePerUnit(android.icu.util.Measure, android.icu.util.MeasureUnit, java.lang.StringBuilder, java.text.FieldPosition); method public final java.lang.String formatMeasures(android.icu.util.Measure...); method public java.lang.StringBuilder formatMeasures(java.lang.StringBuilder, java.text.FieldPosition, android.icu.util.Measure...); method public static android.icu.text.MeasureFormat getCurrencyFormat(android.icu.util.ULocale); @@ -18136,6 +18222,14 @@ package android.icu.text { method public void setUpperCaseFirst(boolean); } + public final class ScientificNumberFormatter { + method public java.lang.String format(java.lang.Object); + method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.util.ULocale, java.lang.String, java.lang.String); + method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.text.DecimalFormat, java.lang.String, java.lang.String); + method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.util.ULocale); + method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.text.DecimalFormat); + } + public abstract class SearchIterator { ctor protected SearchIterator(java.text.CharacterIterator, android.icu.text.BreakIterator); method public final int first(); @@ -18911,6 +19005,34 @@ package android.icu.util { method public long getToDate(); } + public final class EthiopicCalendar extends android.icu.util.CECalendar { + ctor public EthiopicCalendar(); + ctor public EthiopicCalendar(android.icu.util.TimeZone); + ctor public EthiopicCalendar(java.util.Locale); + ctor public EthiopicCalendar(android.icu.util.ULocale); + ctor public EthiopicCalendar(android.icu.util.TimeZone, java.util.Locale); + ctor public EthiopicCalendar(android.icu.util.TimeZone, android.icu.util.ULocale); + ctor public EthiopicCalendar(int, int, int); + ctor public EthiopicCalendar(java.util.Date); + ctor public EthiopicCalendar(int, int, int, int, int, int); + method protected deprecated int handleGetExtendedYear(); + method public boolean isAmeteAlemEra(); + method public void setAmeteAlemEra(boolean); + field public static final int GENBOT = 8; // 0x8 + field public static final int HAMLE = 10; // 0xa + field public static final int HEDAR = 2; // 0x2 + field public static final int MEGABIT = 6; // 0x6 + field public static final int MESKEREM = 0; // 0x0 + field public static final int MIAZIA = 7; // 0x7 + field public static final int NEHASSE = 11; // 0xb + field public static final int PAGUMEN = 12; // 0xc + field public static final int SENE = 9; // 0x9 + field public static final int TAHSAS = 3; // 0x3 + field public static final int TEKEMT = 1; // 0x1 + field public static final int TER = 4; // 0x4 + field public static final int YEKATIT = 5; // 0x5 + } + public abstract interface Freezable<T> implements java.lang.Cloneable { method public abstract T cloneAsThawed(); method public abstract T freeze(); @@ -19439,6 +19561,35 @@ package android.icu.util { enum_constant public static final android.icu.util.ULocale.Category FORMAT; } + public final class UniversalTimeScale { + method public static android.icu.math.BigDecimal bigDecimalFrom(double, int); + method public static android.icu.math.BigDecimal bigDecimalFrom(long, int); + method public static android.icu.math.BigDecimal bigDecimalFrom(android.icu.math.BigDecimal, int); + method public static long from(long, int); + method public static long getTimeScaleValue(int, int); + method public static android.icu.math.BigDecimal toBigDecimal(long, int); + method public static android.icu.math.BigDecimal toBigDecimal(android.icu.math.BigDecimal, int); + method public static long toLong(long, int); + field public static final int DB2_TIME = 8; // 0x8 + field public static final int DOTNET_DATE_TIME = 4; // 0x4 + field public static final int EPOCH_OFFSET_PLUS_1_VALUE = 6; // 0x6 + field public static final int EPOCH_OFFSET_VALUE = 1; // 0x1 + field public static final int EXCEL_TIME = 7; // 0x7 + field public static final int FROM_MAX_VALUE = 3; // 0x3 + field public static final int FROM_MIN_VALUE = 2; // 0x2 + field public static final int ICU4C_TIME = 2; // 0x2 + field public static final int JAVA_TIME = 0; // 0x0 + field public static final int MAC_OLD_TIME = 5; // 0x5 + field public static final int MAC_TIME = 6; // 0x6 + field public static final int MAX_SCALE = 10; // 0xa + field public static final int TO_MAX_VALUE = 5; // 0x5 + field public static final int TO_MIN_VALUE = 4; // 0x4 + field public static final int UNITS_VALUE = 0; // 0x0 + field public static final int UNIX_MICROSECONDS_TIME = 9; // 0x9 + field public static final int UNIX_TIME = 1; // 0x1 + field public static final int WINDOWS_FILE_TIME = 3; // 0x3 + } + public abstract interface ValueIterator { method public abstract boolean next(android.icu.util.ValueIterator.Element); method public abstract void reset(); @@ -23670,6 +23821,7 @@ package android.media.tv { field public static final java.lang.String TYPE_NTSC = "TYPE_NTSC"; field public static final java.lang.String TYPE_OTHER = "TYPE_OTHER"; field public static final java.lang.String TYPE_PAL = "TYPE_PAL"; + field public static final java.lang.String TYPE_PREVIEW = "TYPE_PREVIEW"; field public static final java.lang.String TYPE_SECAM = "TYPE_SECAM"; field public static final java.lang.String TYPE_S_DMB = "TYPE_S_DMB"; field public static final java.lang.String TYPE_T_DMB = "TYPE_T_DMB"; @@ -23710,8 +23862,14 @@ package android.media.tv { field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2"; field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3"; field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id"; field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description"; field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri"; + field public static final java.lang.String COLUMN_PREVIEW_DURATION = "preview_duration"; + field public static final java.lang.String COLUMN_PREVIEW_INTENT_URI = "preview_intent_uri"; + field public static final java.lang.String COLUMN_PREVIEW_LAST_PLAYBACK_POSITION = "preview_last_playback_position"; + field public static final java.lang.String COLUMN_PREVIEW_VIDEO_URI = "preview_video_uri"; + field public static final java.lang.String COLUMN_PREVIEW_WEIGHT = "preview_weight"; field public static final java.lang.String COLUMN_RECORDING_PROHIBITED = "recording_prohibited"; field public static final java.lang.String COLUMN_SEARCHABLE = "searchable"; field public static final java.lang.String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number"; @@ -23840,6 +23998,7 @@ package android.media.tv { field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED"; field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS"; field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS"; + field public static final java.lang.String ACTION_VIEW_RECORDING_SCHEDULES = "android.media.tv.action.VIEW_RECORDING_SCHEDULES"; field public static final int INPUT_STATE_CONNECTED = 0; // 0x0 field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1 field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2 @@ -30722,6 +30881,7 @@ package android.preference { method public android.preference.Preference.OnPreferenceChangeListener getOnPreferenceChangeListener(); method public android.preference.Preference.OnPreferenceClickListener getOnPreferenceClickListener(); method public int getOrder(); + method public android.preference.PreferenceGroup getParent(); method protected boolean getPersistedBoolean(boolean); method protected float getPersistedFloat(float); method protected int getPersistedInt(int); @@ -33053,7 +33213,6 @@ package android.provider { method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public android.content.res.AssetFileDescriptor openTypedDocument(java.lang.String, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); - method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal); method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException; method public android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], android.os.Bundle) throws java.io.FileNotFoundException; @@ -33067,6 +33226,16 @@ package android.provider { method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]); } + public class FontsContract { + } + + public static final class FontsContract.Columns implements android.provider.BaseColumns { + ctor public FontsContract.Columns(); + field public static final java.lang.String STYLE = "font_style"; + field public static final java.lang.String TTC_INDEX = "font_ttc_index"; + field public static final java.lang.String VARIATION_SETTINGS = "font_variation_settings"; + } + public final deprecated class LiveFolders implements android.provider.BaseColumns { field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER"; field public static final java.lang.String DESCRIPTION = "description"; @@ -35372,6 +35541,18 @@ package android.security { package android.security.keystore { + public abstract class AttestationUtils { + method public static java.security.cert.X509Certificate[] attestDeviceIds(android.content.Context, int[], byte[]) throws android.security.keystore.DeviceIdAttestationException; + field public static final int ID_TYPE_IMEI = 2; // 0x2 + field public static final int ID_TYPE_MEID = 3; // 0x3 + field public static final int ID_TYPE_SERIAL = 1; // 0x1 + } + + public class DeviceIdAttestationException extends java.lang.Exception { + ctor public DeviceIdAttestationException(java.lang.String); + ctor public DeviceIdAttestationException(java.lang.String, java.lang.Throwable); + } + public class KeyExpiredException extends java.security.InvalidKeyException { ctor public KeyExpiredException(); ctor public KeyExpiredException(java.lang.String); @@ -35840,6 +36021,7 @@ package android.service.notification { method public final android.os.IBinder onBind(android.content.Intent); method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean); method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String); + method public final void unsnoozeNotification(java.lang.String); method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel); field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; } @@ -35855,6 +36037,7 @@ package android.service.notification { method public final int getCurrentInterruptionFilter(); method public final int getCurrentListenerHints(); method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking(); + method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications(); method public android.os.IBinder onBind(android.content.Intent); method public void onInterruptionFilterChanged(int); method public void onListenerConnected(); @@ -35873,8 +36056,6 @@ package android.service.notification { method public final void setNotificationsShown(java.lang.String[]); method public final void snoozeNotification(java.lang.String, java.lang.String); method public final void snoozeNotification(java.lang.String, long); - method public final void snoozeNotification(java.lang.String); - method public final void unsnoozeNotification(java.lang.String); field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4 field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2 @@ -35909,6 +36090,7 @@ package android.service.notification { public static class NotificationListenerService.Ranking { ctor public NotificationListenerService.Ranking(); + method public boolean canShowBadge(); method public java.util.List<java.lang.String> getAdditionalPeople(); method public android.app.NotificationChannel getChannel(); method public int getImportance(); @@ -35950,7 +36132,6 @@ package android.service.notification { method public int getId(); method public java.lang.String getKey(); method public android.app.Notification getNotification(); - method public android.app.NotificationChannel getNotificationChannel(); method public java.lang.String getOverrideGroupKey(); method public java.lang.String getPackageName(); method public long getPostTime(); @@ -36366,6 +36547,7 @@ package android.speech.tts { method public abstract int getMaxBufferSize(); method public abstract boolean hasFinished(); method public abstract boolean hasStarted(); + method public default void rangeStart(int, int, int); method public abstract int start(int, int, int); } @@ -36518,6 +36700,7 @@ package android.speech.tts { method public void onError(java.lang.String, int); method public abstract void onStart(java.lang.String); method public void onStop(java.lang.String, boolean); + method public void onUtteranceRangeStart(java.lang.String, int, int); } public class Voice implements android.os.Parcelable { @@ -38609,6 +38792,7 @@ package android.telephony { method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String); method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String); method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String); + method public boolean isConcurrentVoiceAndDataAllowed(); method public boolean isHearingAidCompatibilitySupported(); method public boolean isNetworkRoaming(); method public boolean isSmsCapable(); @@ -40049,22 +40233,6 @@ package android.text { method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic); } - public abstract interface TextAssistant { - method public abstract void addLinks(android.text.Spannable, int); - method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int); - } - - public class TextClassification { - ctor public TextClassification(); - method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence(); - } - - public final class TextClassificationManager implements android.text.TextAssistant { - method public void addLinks(android.text.Spannable, int); - method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence); - method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int); - } - public abstract interface TextDirectionHeuristic { method public abstract boolean isRtl(char[], int, int); method public abstract boolean isRtl(java.lang.CharSequence, int, int); @@ -40080,13 +40248,6 @@ package android.text { field public static final android.text.TextDirectionHeuristic RTL; } - public final class TextLanguage { - ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>); - method public int getEndIndex(); - method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence(); - method public int getStartIndex(); - } - public class TextPaint extends android.graphics.Paint { ctor public TextPaint(); ctor public TextPaint(int); @@ -40099,13 +40260,6 @@ package android.text { field public int linkColor; } - public class TextSelection { - ctor public TextSelection(); - method public int getSelectionEndIndex(); - method public int getSelectionStartIndex(); - method public android.text.TextClassification getTextClassification(); - } - public class TextUtils { method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String); method public static java.lang.CharSequence concat(java.lang.CharSequence...); @@ -44017,6 +44171,7 @@ package android.view { method public float getElevation(); method public boolean getFilterTouchesWhenObscured(); method public boolean getFitsSystemWindows(); + method public int getFocusable(); method public java.util.ArrayList<android.view.View> getFocusables(int); method public void getFocusedRect(android.graphics.Rect); method public android.graphics.drawable.Drawable getForeground(); @@ -44322,6 +44477,7 @@ package android.view { method public void setFilterTouchesWhenObscured(boolean); method public void setFitsSystemWindows(boolean); method public void setFocusable(boolean); + method public void setFocusable(int); method public void setFocusableInTouchMode(boolean); method public void setFocusedByDefault(boolean); method public void setForeground(android.graphics.drawable.Drawable); @@ -44460,8 +44616,10 @@ package android.view { field protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET; field public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 2; // 0x2 field public static final int FIND_VIEWS_WITH_TEXT = 1; // 0x1 + field public static final int FOCUSABLE = 1; // 0x1 field public static final int FOCUSABLES_ALL = 0; // 0x0 field public static final int FOCUSABLES_TOUCH_MODE = 1; // 0x1 + field public static final int FOCUSABLE_AUTO = 16; // 0x10 field protected static final int[] FOCUSED_SELECTED_STATE_SET; field protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET; field protected static final int[] FOCUSED_STATE_SET; @@ -44491,6 +44649,7 @@ package android.view { field public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff field public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000 field public static final int MEASURED_STATE_TOO_SMALL = 16777216; // 0x1000000 + field public static final int NOT_FOCUSABLE = 0; // 0x0 field public static final int NO_ID = -1; // 0xffffffff field public static final int OVER_SCROLL_ALWAYS = 0; // 0x0 field public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; // 0x1 @@ -45199,6 +45358,7 @@ package android.view { method public boolean getAllowReturnTransitionOverlap(); method public final android.view.WindowManager.LayoutParams getAttributes(); method public final android.view.Window.Callback getCallback(); + method public int getColorMode(); method public final android.view.Window getContainer(); method public android.transition.Scene getContentScene(); method public final android.content.Context getContext(); @@ -45255,6 +45415,7 @@ package android.view { method public abstract void setChildDrawable(int, android.graphics.drawable.Drawable); method public abstract void setChildInt(int, int); method public void setClipToOutline(boolean); + method public void setColorMode(int); method public void setContainer(android.view.Window); method public abstract void setContentView(int); method public abstract void setContentView(android.view.View); @@ -45453,8 +45614,10 @@ package android.view { method public final int copyFrom(android.view.WindowManager.LayoutParams); method public java.lang.String debug(java.lang.String); method public int describeContents(); + method public int getColorMode(); method public final java.lang.CharSequence getTitle(); method public static boolean mayUseInputMethod(int); + method public void setColorMode(int); method public final void setTitle(java.lang.CharSequence); method public void writeToParcel(android.os.Parcel, int); field public static final int ALPHA_CHANGED = 128; // 0x80 @@ -46791,6 +46954,83 @@ package android.view.inputmethod { } +package android.view.textclassifier { + + public abstract interface LinksInfo { + method public abstract boolean apply(java.lang.CharSequence); + } + + public final class TextClassificationManager { + method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence); + method public android.view.textclassifier.TextClassifier getDefaultTextClassifier(); + } + + public final class TextClassificationResult { + method public float getConfidenceScore(java.lang.String); + method public java.lang.String getEntity(int); + method public int getEntityCount(); + method public android.graphics.drawable.Drawable getIcon(); + method public android.content.Intent getIntent(); + method public java.lang.CharSequence getLabel(); + method public android.view.View.OnClickListener getOnClickListener(); + method public java.lang.String getText(); + } + + public static final class TextClassificationResult.Builder { + ctor public TextClassificationResult.Builder(); + method public android.view.textclassifier.TextClassificationResult build(); + method public android.view.textclassifier.TextClassificationResult.Builder setEntityType(java.lang.String, float); + method public android.view.textclassifier.TextClassificationResult.Builder setIcon(android.graphics.drawable.Drawable); + method public android.view.textclassifier.TextClassificationResult.Builder setIntent(android.content.Intent); + method public android.view.textclassifier.TextClassificationResult.Builder setLabel(java.lang.String); + method public android.view.textclassifier.TextClassificationResult.Builder setOnClickListener(android.view.View.OnClickListener); + method public android.view.textclassifier.TextClassificationResult.Builder setText(java.lang.String); + } + + public abstract interface TextClassifier { + method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int); + method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int); + method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int); + field public static final android.view.textclassifier.TextClassifier NO_OP; + field public static final java.lang.String TYPE_ADDRESS = "address"; + field public static final java.lang.String TYPE_EMAIL = "email"; + field public static final java.lang.String TYPE_OTHER = "other"; + field public static final java.lang.String TYPE_PHONE = "phone"; + } + + public static abstract class TextClassifier.EntityType implements java.lang.annotation.Annotation { + } + + public final class TextLanguage { + method public float getConfidenceScore(java.util.Locale); + method public int getEndIndex(); + method public java.util.Locale getLanguage(int); + method public int getLanguageCount(); + method public int getStartIndex(); + } + + public static final class TextLanguage.Builder { + ctor public TextLanguage.Builder(int, int); + method public android.view.textclassifier.TextLanguage build(); + method public android.view.textclassifier.TextLanguage.Builder setLanguage(java.util.Locale, float); + } + + public final class TextSelection { + method public float getConfidenceScore(java.lang.String); + method public java.lang.String getEntity(int); + method public int getEntityCount(); + method public int getSelectionEndIndex(); + method public int getSelectionStartIndex(); + } + + public static final class TextSelection.Builder { + ctor public TextSelection.Builder(int, int); + method public android.view.textclassifier.TextSelection build(); + method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float); + } + +} + package android.view.textservice { public final class SentenceSuggestionsInfo implements android.os.Parcelable { @@ -49887,7 +50127,7 @@ package android.widget { method public float getShadowRadius(); method public final boolean getShowSoftInputOnFocus(); method public java.lang.CharSequence getText(); - method public android.text.TextAssistant getTextAssistant(); + method public android.view.textclassifier.TextClassifier getTextClassifier(); method public final android.content.res.ColorStateList getTextColors(); method public java.util.Locale getTextLocale(); method public android.os.LocaleList getTextLocales(); @@ -50003,7 +50243,7 @@ package android.widget { method public final void setText(int, android.widget.TextView.BufferType); method public void setTextAppearance(int); method public deprecated void setTextAppearance(android.content.Context, int); - method public void setTextAssistant(android.text.TextAssistant); + method public void setTextClassifier(android.view.textclassifier.TextClassifier); method public void setTextColor(int); method public void setTextColor(android.content.res.ColorStateList); method public void setTextIsSelectable(boolean); @@ -62154,6 +62394,9 @@ package java.util { method public static <E> java.util.Collection<E> checkedCollection(java.util.Collection<E>, java.lang.Class<E>); method public static <E> java.util.List<E> checkedList(java.util.List<E>, java.lang.Class<E>); method public static <K, V> java.util.Map<K, V> checkedMap(java.util.Map<K, V>, java.lang.Class<K>, java.lang.Class<V>); + method public static <K, V> java.util.NavigableMap<K, V> checkedNavigableMap(java.util.NavigableMap<K, V>, java.lang.Class<K>, java.lang.Class<V>); + method public static <E> java.util.NavigableSet<E> checkedNavigableSet(java.util.NavigableSet<E>, java.lang.Class<E>); + method public static <E> java.util.Queue<E> checkedQueue(java.util.Queue<E>, java.lang.Class<E>); method public static <E> java.util.Set<E> checkedSet(java.util.Set<E>, java.lang.Class<E>); method public static <K, V> java.util.SortedMap<K, V> checkedSortedMap(java.util.SortedMap<K, V>, java.lang.Class<K>, java.lang.Class<V>); method public static <E> java.util.SortedSet<E> checkedSortedSet(java.util.SortedSet<E>, java.lang.Class<E>); @@ -62164,7 +62407,11 @@ package java.util { method public static final <T> java.util.List<T> emptyList(); method public static <T> java.util.ListIterator<T> emptyListIterator(); method public static final <K, V> java.util.Map<K, V> emptyMap(); + method public static final <K, V> java.util.NavigableMap<K, V> emptyNavigableMap(); + method public static <E> java.util.NavigableSet<E> emptyNavigableSet(); method public static final <T> java.util.Set<T> emptySet(); + method public static final <K, V> java.util.SortedMap<K, V> emptySortedMap(); + method public static <E> java.util.SortedSet<E> emptySortedSet(); method public static <T> java.util.Enumeration<T> enumeration(java.util.Collection<T>); method public static <T> void fill(java.util.List<? super T>, T); method public static int frequency(java.util.Collection<?>, java.lang.Object); @@ -62193,12 +62440,16 @@ package java.util { method public static <T> java.util.Collection<T> synchronizedCollection(java.util.Collection<T>); method public static <T> java.util.List<T> synchronizedList(java.util.List<T>); method public static <K, V> java.util.Map<K, V> synchronizedMap(java.util.Map<K, V>); + method public static <K, V> java.util.NavigableMap<K, V> synchronizedNavigableMap(java.util.NavigableMap<K, V>); + method public static <T> java.util.NavigableSet<T> synchronizedNavigableSet(java.util.NavigableSet<T>); method public static <T> java.util.Set<T> synchronizedSet(java.util.Set<T>); method public static <K, V> java.util.SortedMap<K, V> synchronizedSortedMap(java.util.SortedMap<K, V>); method public static <T> java.util.SortedSet<T> synchronizedSortedSet(java.util.SortedSet<T>); method public static <T> java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>); method public static <T> java.util.List<T> unmodifiableList(java.util.List<? extends T>); method public static <K, V> java.util.Map<K, V> unmodifiableMap(java.util.Map<? extends K, ? extends V>); + method public static <K, V> java.util.NavigableMap<K, V> unmodifiableNavigableMap(java.util.NavigableMap<K, ? extends V>); + method public static <T> java.util.NavigableSet<T> unmodifiableNavigableSet(java.util.NavigableSet<T>); method public static <T> java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>); method public static <K, V> java.util.SortedMap<K, V> unmodifiableSortedMap(java.util.SortedMap<K, ? extends V>); method public static <T> java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>); diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 780db5e26c35..7e913913dfae 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -18,20 +18,24 @@ package com.android.commands.bmgr; import android.app.backup.BackupManager; import android.app.backup.BackupProgress; -import android.app.backup.RestoreSet; import android.app.backup.IBackupManager; import android.app.backup.IBackupObserver; import android.app.backup.IRestoreObserver; import android.app.backup.IRestoreSession; +import android.app.backup.RestoreSet; +import android.app.backup.ISelectBackupTransportCallback; +import android.content.ComponentName; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.util.Log; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.concurrent.CountDownLatch; public final class Bmgr { IBackupManager mBmgr; @@ -363,6 +367,11 @@ public final class Bmgr { return; } + if ("-c".equals(which)) { + doTransportByComponent(); + return; + } + String old = mBmgr.selectBackupTransport(which); if (old == null) { System.out.println("Unknown transport '" + which @@ -370,12 +379,50 @@ public final class Bmgr { } else { System.out.println("Selected transport " + which + " (formerly " + old + ")"); } + } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } } + private void doTransportByComponent() { + String which = nextArg(); + if (which == null) { + showUsage(); + return; + } + + final CountDownLatch latch = new CountDownLatch(1); + + try { + mBmgr.selectBackupTransportAsync(ComponentName.unflattenFromString(which), + new ISelectBackupTransportCallback.Stub() { + @Override + public void onSuccess(String transportName) { + System.out.println("Success. Selected transport: " + transportName); + latch.countDown(); + } + + @Override + public void onFailure(int reason) { + System.err.println("Failure. error=" + reason); + latch.countDown(); + } + }); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(BMGR_NOT_RUNNING_ERR); + return; + } + + try { + latch.await(); + } catch (InterruptedException e) { + System.err.println("Operation interrupted."); + } + } + private void doWipe() { String transport = nextArg(); if (transport == null) { @@ -427,7 +474,16 @@ public final class Bmgr { } private void doListTransports() { + String arg = nextArg(); + try { + if ("-c".equals(arg)) { + for (ComponentName transport : mBmgr.listAllTransportComponents()) { + System.out.println(transport.flattenToShortString()); + } + return; + } + String current = mBmgr.getCurrentTransport(); String[] transports = mBmgr.listAllTransports(); if (transports == null || transports.length == 0) { @@ -649,9 +705,9 @@ public final class Bmgr { System.err.println(" bmgr backup PACKAGE"); System.err.println(" bmgr enable BOOL"); System.err.println(" bmgr enabled"); - System.err.println(" bmgr list transports"); + System.err.println(" bmgr list transports [-c]"); System.err.println(" bmgr list sets"); - System.err.println(" bmgr transport WHICH"); + System.err.println(" bmgr transport WHICH|-c WHICH_COMPONENT"); System.err.println(" bmgr restore TOKEN"); System.err.println(" bmgr restore TOKEN PACKAGE..."); System.err.println(" bmgr restore PACKAGE"); @@ -673,15 +729,18 @@ public final class Bmgr { System.err.println("the backup mechanism."); System.err.println(""); System.err.println("The 'list transports' command reports the names of the backup transports"); - System.err.println("currently available on the device. These names can be passed as arguments"); + System.err.println("BackupManager is currently bound to. These names can be passed as arguments"); System.err.println("to the 'transport' and 'wipe' commands. The currently active transport"); - System.err.println("is indicated with a '*' character."); + System.err.println("is indicated with a '*' character. If -c flag is used, all available"); + System.err.println("transport components on the device are listed. These can be used with"); + System.err.println("the component variant of 'transport' command."); System.err.println(""); System.err.println("The 'list sets' command reports the token and name of each restore set"); System.err.println("available to the device via the currently active transport."); System.err.println(""); System.err.println("The 'transport' command designates the named transport as the currently"); - System.err.println("active one. This setting is persistent across reboots."); + System.err.println("active one. This setting is persistent across reboots. If -c flag is"); + System.err.println("specified, the following string is treated as a component name."); System.err.println(""); System.err.println("The 'restore' command when given just a restore token initiates a full-system"); System.err.println("restore operation from the currently active transport. It will deliver"); diff --git a/cmds/media/src/com/android/commands/media/VolumeCtrl.java b/cmds/media/src/com/android/commands/media/VolumeCtrl.java index a17193202bbd..1629c6f178f0 100755 --- a/cmds/media/src/com/android/commands/media/VolumeCtrl.java +++ b/cmds/media/src/com/android/commands/media/VolumeCtrl.java @@ -42,6 +42,7 @@ public class VolumeCtrl { // --stream affects --set, --adj or --get options. // --show affects --set and --adj options. + // --get can be used with --set, --adj or by itself. public final static String USAGE = new String( "the options are as follows: \n" + "\t\t--stream STREAM selects the stream to control, see AudioManager.STREAM_*\n" + @@ -56,9 +57,8 @@ public class VolumeCtrl { "\t\tadb shell media volume --stream 3 --get\n" ); - private final static int VOLUME_CONTROL_MODE_SET = 0; - private final static int VOLUME_CONTROL_MODE_ADJUST = 1; - private final static int VOLUME_CONTROL_MODE_GET = 2; + private final static int VOLUME_CONTROL_MODE_SET = 1; + private final static int VOLUME_CONTROL_MODE_ADJUST = 2; private final static String ADJUST_LOWER = "lower"; private final static String ADJUST_SAME = "same"; @@ -69,9 +69,10 @@ public class VolumeCtrl { // Default parameters int stream = AudioManager.STREAM_MUSIC; int volIndex = 5; - int mode = VOLUME_CONTROL_MODE_SET; + int mode = 0; int adjDir = AudioManager.ADJUST_RAISE; boolean showUi = false; + boolean doGet = false; //---------------------------------------- // read options @@ -83,7 +84,7 @@ public class VolumeCtrl { showUi = true; break; case "--get": - mode = VOLUME_CONTROL_MODE_GET; + doGet = true; log(LOG_V, "will get volume"); break; case "--stream": @@ -150,15 +151,16 @@ public class VolumeCtrl { // Non-interactive test final int flag = showUi? AudioManager.FLAG_SHOW_UI : 0; final String pack = cmd.getClass().getPackage().getName(); - if (mode == VOLUME_CONTROL_MODE_GET) { - log(LOG_V, "volume is " + audioService.getStreamVolume(stream) + - " in range [" + audioService.getStreamMinVolume(stream) + - ".." + audioService.getStreamMaxVolume(stream) + "]"); - } else if (mode == VOLUME_CONTROL_MODE_SET) { + if (mode == VOLUME_CONTROL_MODE_SET) { audioService.setStreamVolume(stream, volIndex, flag, pack/*callingPackage*/); } else if (mode == VOLUME_CONTROL_MODE_ADJUST) { audioService.adjustStreamVolume(stream, adjDir, flag, pack); } + if (doGet) { + log(LOG_V, "volume is " + audioService.getStreamVolume(stream) + + " in range [" + audioService.getStreamMinVolume(stream) + + ".." + audioService.getStreamMaxVolume(stream) + "]"); + } } //-------------------------------------------- diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 556d7add513f..df4c4af17f69 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -78,8 +78,6 @@ import android.service.autofill.AutoFillService; import android.service.autofill.IAutoFillAppCallback; import android.text.Selection; import android.text.SpannableStringBuilder; -import android.text.TextAssistant; -import android.text.TextClassificationManager; import android.text.TextUtils; import android.text.method.TextKeyListener; import android.transition.Scene; @@ -792,8 +790,6 @@ public class Activity extends ContextThemeWrapper private VoiceInteractor mVoiceInteractor; - private TextAssistant mTextAssistant; - private CharSequence mTitle; private int mTitleColor = 0; @@ -1398,24 +1394,6 @@ public class Activity extends ContextThemeWrapper } /** - * Sets the default {@link TextAssistant} for {@link android.widget.TextView}s in this Activity. - */ - public void setTextAssistant(TextAssistant textAssistant) { - mTextAssistant = textAssistant; - } - - /** - * Returns the default {@link TextAssistant} for {@link android.widget.TextView}s - * in this Activity. - */ - public TextAssistant getTextAssistant() { - if (mTextAssistant != null) { - return mTextAssistant; - } - return getSystemService(TextClassificationManager.class); - } - - /** * This is called for activities that set launchMode to "singleTop" in * their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} * flag when calling {@link #startActivity}. In either case, when the @@ -3130,7 +3108,7 @@ public class Activity extends ContextThemeWrapper */ @Override public void enterPictureInPictureModeIfPossible() { - if (mActivityInfo.resizeMode == ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE) { + if (mActivityInfo.supportsPictureInPicture()) { enterPictureInPictureMode(); } } @@ -6814,6 +6792,8 @@ public class Activity extends ContextThemeWrapper } mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; + + mWindow.setColorMode(info.colorMode); } /** @hide */ diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index c1a888d28428..3cb920a6f28d 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1482,7 +1482,7 @@ public class ActivityManager { * True if the task can go in the docked stack. * @hide */ - public boolean isDockable; + public boolean supportsSplitScreenMultiWindow; /** * The resize mode of the task. See {@link ActivityInfo#resizeMode}. @@ -1533,7 +1533,7 @@ public class ActivityManager { } else { dest.writeInt(0); } - dest.writeInt(isDockable ? 1 : 0); + dest.writeInt(supportsSplitScreenMultiWindow ? 1 : 0); dest.writeInt(resizeMode); } @@ -1557,7 +1557,7 @@ public class ActivityManager { numActivities = source.readInt(); bounds = source.readInt() > 0 ? Rect.CREATOR.createFromParcel(source) : null; - isDockable = source.readInt() == 1; + supportsSplitScreenMultiWindow = source.readInt() == 1; resizeMode = source.readInt(); } @@ -1745,7 +1745,7 @@ public class ActivityManager { * True if the task can go in the docked stack. * @hide */ - public boolean isDockable; + public boolean supportsSplitScreenMultiWindow; /** * The resize mode of the task. See {@link ActivityInfo#resizeMode}. @@ -1775,7 +1775,7 @@ public class ActivityManager { Parcelable.PARCELABLE_WRITE_RETURN_VALUE); dest.writeInt(numActivities); dest.writeInt(numRunning); - dest.writeInt(isDockable ? 1 : 0); + dest.writeInt(supportsSplitScreenMultiWindow ? 1 : 0); dest.writeInt(resizeMode); } @@ -1792,7 +1792,7 @@ public class ActivityManager { description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); numActivities = source.readInt(); numRunning = source.readInt(); - isDockable = source.readInt() != 0; + supportsSplitScreenMultiWindow = source.readInt() != 0; resizeMode = source.readInt(); } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 9cc13abcbabc..603126b3badd 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -245,8 +245,10 @@ public class AppOpsManager { public static final int OP_READ_PHONE_NUMBER = 65; /** @hide Request package installs through package installer */ public static final int OP_REQUEST_INSTALL_PACKAGES = 66; + /** @hide Enter picture-in-picture when hidden. */ + public static final int OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE = 67; /** @hide */ - public static final int _NUM_OP = 67; + public static final int _NUM_OP = 68; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -464,6 +466,7 @@ public class AppOpsManager { OP_AUDIO_ACCESSIBILITY_VOLUME, OP_READ_PHONE_NUMBER, OP_REQUEST_INSTALL_PACKAGES, + OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE, }; /** @@ -538,6 +541,7 @@ public class AppOpsManager { null, // OP_AUDIO_ACCESSIBILITY_VOLUME OPSTR_READ_PHONE_NUMBER, null, // OP_REQUEST_INSTALL_PACKAGES + null, }; /** @@ -612,6 +616,7 @@ public class AppOpsManager { "AUDIO_ACCESSIBILITY_VOLUME", "READ_PHONE_NUMBER", "REQUEST_INSTALL_PACKAGES", + "OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE", }; /** @@ -686,6 +691,7 @@ public class AppOpsManager { null, // no permission for changing accessibility volume Manifest.permission.READ_PHONE_NUMBER, Manifest.permission.REQUEST_INSTALL_PACKAGES, + null, // no permission for entering picture-in-picture on hide }; /** @@ -761,6 +767,7 @@ public class AppOpsManager { UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ACCESSIBILITY_VOLUME null, // READ_PHONE_NUMBER null, // REQUEST_INSTALL_PACKAGES + null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE }; /** @@ -835,6 +842,7 @@ public class AppOpsManager { false, // AUDIO_ACCESSIBILITY_VOLUME false, // READ_PHONE_NUMBER false, // REQUEST_INSTALL_PACKAGES + false, // ENTER_PICTURE_IN_PICTURE_ON_HIDE }; /** @@ -908,6 +916,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // OP_AUDIO_ACCESSIBILITY_VOLUME AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_DEFAULT, // OP_REQUEST_INSTALL_PACKAGES + AppOpsManager.MODE_ALLOWED, // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE }; /** @@ -985,6 +994,7 @@ public class AppOpsManager { false, // OP_AUDIO_ACCESSIBILITY_VOLUME false, false, // OP_REQUEST_INSTALL_PACKAGES + false, // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE }; /** diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index f909af069224..740af9cb69de 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -47,6 +47,8 @@ interface INotificationManager in Notification notification, inout int[] idReceived, int userId); void cancelNotificationWithTag(String pkg, String tag, int id, int userId); + void setShowBadge(String pkg, int uid, boolean showBadge); + boolean canShowBadge(String pkg, int uid); void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled); boolean areNotificationsEnabledForPackage(String pkg, int uid); boolean areNotificationsEnabled(String pkg); @@ -73,8 +75,6 @@ interface INotificationManager void snoozeNotificationUntilContextFromListener(in INotificationListener token, String key, String snoozeCriterionId); void snoozeNotificationUntilFromListener(in INotificationListener token, String key, long until); - void snoozeNotificationFromListener(in INotificationListener token, String key); - void unsnoozeNotificationFromListener(in INotificationListener token, String key); void requestBindListener(in ComponentName component); void requestUnbindListener(in INotificationListener token); @@ -84,6 +84,7 @@ interface INotificationManager void setNotificationsShownFromListener(in INotificationListener token, in String[] keys); ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token, in String[] keys, int trim); + ParceledListSlice getSnoozedNotificationsFromListener(in INotificationListener token, int trim); void requestHintsFromListener(in INotificationListener token, int hints); int getHintsFromListener(in INotificationListener token); void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter); @@ -98,6 +99,7 @@ interface INotificationManager void updateNotificationChannelFromAssistant(in INotificationListener token, String pkg, in NotificationChannel channel); void deleteNotificationChannelFromAssistant(in INotificationListener token, String pkg, String channelId); ParceledListSlice getNotificationChannelsFromAssistant(in INotificationListener token, String pkg); + void unsnoozeNotificationFromAssistant(in INotificationListener token, String key); ComponentName getEffectsSuppressor(); boolean matchesCallFilter(in Bundle extras); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 6793c90f9be1..c0bf0c46988a 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -24,25 +24,25 @@ import android.app.trust.ITrustManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.os.Binder; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; -import android.os.PowerManager; import android.os.RemoteException; -import android.os.IBinder; -import android.os.IUserManager; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.os.UserHandle; import android.util.Log; -import android.view.IWindowManager; import android.view.IOnKeyguardExitResult; -import android.view.WindowManager; +import android.view.IWindowManager; import android.view.WindowManager.LayoutParams; import android.view.WindowManagerGlobal; import com.android.internal.policy.IKeyguardDismissCallback; +import java.util.List; + /** * Class that can be used to lock and unlock the keyboard. Get an instance of this * class by calling {@link android.content.Context#getSystemService(java.lang.String)} @@ -100,12 +100,9 @@ public class KeyguardManager { Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL); intent.putExtra(EXTRA_TITLE, title); intent.putExtra(EXTRA_DESCRIPTION, description); - if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { - intent.setPackage("com.google.android.apps.wearable.settings"); - } else { - // For security reasons, only allow this to come from system settings. - intent.setPackage("com.android.settings"); - } + + // explicitly set the package for security + intent.setPackage(getSettingsPackageForIntent(intent)); return intent; } @@ -126,15 +123,23 @@ public class KeyguardManager { intent.putExtra(EXTRA_TITLE, title); intent.putExtra(EXTRA_DESCRIPTION, description); intent.putExtra(Intent.EXTRA_USER_ID, userId); - if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { - intent.setPackage("com.google.android.apps.wearable.settings"); - } else { - // For security reasons, only allow this to come from system settings. - intent.setPackage("com.android.settings"); - } + + // explicitly set the package for security + intent.setPackage(getSettingsPackageForIntent(intent)); + return intent; } + private String getSettingsPackageForIntent(Intent intent) { + List<ResolveInfo> resolveInfos = mContext.getPackageManager() + .queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY); + for (int i = 0; i < resolveInfos.size(); i++) { + return resolveInfos.get(i).activityInfo.packageName; + } + + return "com.android.settings"; + } + /** * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD} * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 601dfceb3a4a..82917d27d0d6 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1051,7 +1051,7 @@ public class Notification implements Parcelable private final Bundle mExtras; private Icon mIcon; private final RemoteInput[] mRemoteInputs; - private boolean mAllowGeneratedReplies = false; + private boolean mAllowGeneratedReplies = true; /** * Small icon representing the action. @@ -1093,7 +1093,7 @@ public class Notification implements Parcelable */ @Deprecated public Action(int icon, CharSequence title, PendingIntent intent) { - this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, false); + this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true); } /** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */ @@ -1166,7 +1166,7 @@ public class Notification implements Parcelable private final Icon mIcon; private final CharSequence mTitle; private final PendingIntent mIntent; - private boolean mAllowGeneratedReplies; + private boolean mAllowGeneratedReplies = true; private final Bundle mExtras; private ArrayList<RemoteInput> mRemoteInputs; @@ -1188,7 +1188,7 @@ public class Notification implements Parcelable * @param intent the {@link PendingIntent} to fire when users trigger this action */ public Builder(Icon icon, CharSequence title, PendingIntent intent) { - this(icon, title, intent, new Bundle(), null, false); + this(icon, title, intent, new Bundle(), null, true); } /** @@ -1260,7 +1260,7 @@ public class Notification implements Parcelable * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false} * otherwise * @return this object for method chaining - * The default value is {@code false} + * The default value is {@code true} */ public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) { mAllowGeneratedReplies = allowGeneratedReplies; diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 56ef791d365a..be5f80a82b1b 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -122,6 +122,7 @@ public final class NotificationChannel implements Parcelable { private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED; private static final boolean DEFAULT_DELETED = false; + private static final boolean DEFAULT_SHOW_BADGE = true; private final String mId; private CharSequence mName; @@ -133,7 +134,7 @@ public final class NotificationChannel implements Parcelable { private long[] mVibration; private int mUserLockedFields; private boolean mVibrationEnabled; - private boolean mShowBadge; + private boolean mShowBadge = DEFAULT_SHOW_BADGE; private boolean mDeleted = DEFAULT_DELETED; /** @@ -368,6 +369,8 @@ public final class NotificationChannel implements Parcelable { /** * Returns whether notifications posted to this channel can appear as badges in a Launcher * application. + * + * Note that badging may be disabled for other reasons. */ public boolean canShowBadge() { return mShowBadge; diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index a37f22b888b0..5d8909c1dbae 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -119,7 +119,6 @@ import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.FontManager; -import android.text.TextClassificationManager; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; @@ -128,6 +127,7 @@ import android.view.WindowManagerImpl; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.CaptioningManager; import android.view.inputmethod.InputMethodManager; +import android.view.textclassifier.TextClassificationManager; import android.view.textservice.TextServicesManager; import com.android.internal.app.IAppOpsService; @@ -228,10 +228,10 @@ final class SystemServiceRegistry { }}); registerService(Context.TEXT_CLASSIFICATION_SERVICE, TextClassificationManager.class, - new StaticServiceFetcher<TextClassificationManager>() { + new CachedServiceFetcher<TextClassificationManager>() { @Override - public TextClassificationManager createService() { - return new TextClassificationManager(); + public TextClassificationManager createService(ContextImpl ctx) { + return new TextClassificationManager(ctx); }}); registerService(Context.CLIPBOARD_SERVICE, ClipboardManager.class, diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java index 540683deeecb..f0abe33c4df3 100644 --- a/core/java/android/app/backup/BackupManager.java +++ b/core/java/android/app/backup/BackupManager.java @@ -17,6 +17,7 @@ package android.app.backup; import android.annotation.SystemApi; +import android.content.ComponentName; import android.content.Context; import android.os.Handler; import android.os.Message; @@ -157,6 +158,25 @@ public class BackupManager { @SystemApi public static final String PACKAGE_MANAGER_SENTINEL = "@pm@"; + + /** + * This error code is passed to {@link SelectBackupTransportCallback#onFailure(int)} + * if the requested transport is unavailable. + * + * @hide + */ + @SystemApi + public static final int ERROR_TRANSPORT_UNAVAILABLE = -1; + + /** + * This error code is passed to {@link SelectBackupTransportCallback#onFailure(int)} if the + * requested transport is not a valid BackupTransport. + * + * @hide + */ + @SystemApi + public static final int ERROR_TRANSPORT_INVALID = -2; + private Context mContext; private static IBackupManager sService; @@ -390,17 +410,20 @@ public class BackupManager { } /** - * Specify the current backup transport. Callers must hold the - * android.permission.BACKUP permission to use this method. + * Specify the current backup transport. + * + * <p> Callers must hold the android.permission.BACKUP permission to use this method. * * @param transport The name of the transport to select. This should be one - * of the names returned by {@link #listAllTransports()}. + * of the names returned by {@link #listAllTransports()}. This is the String returned by + * {@link BackupTransport#name()} for the particular transport. * @return The name of the previously selected transport. If the given transport * name is not one of the currently available transports, no change is made to * the current transport setting and the method returns null. * * @hide */ + @Deprecated @SystemApi public String selectBackupTransport(String transport) { checkServiceBinder(); @@ -415,6 +438,34 @@ public class BackupManager { } /** + * Specify the current backup transport and get notified when the transport is ready to be used. + * This method is async because BackupManager might need to bind to the specified transport + * which is in a separate process. + * + * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * + * @param transport ComponentName of the service hosting the transport. This is different from + * the transport's name that is returned by {@link BackupTransport#name()}. + * @param listener A listener object to get a callback on the transport being selected. + * + * @hide + */ + @SystemApi + public void selectBackupTransport(ComponentName transport, + SelectBackupTransportCallback listener) { + checkServiceBinder(); + if (sService != null) { + try { + SelectTransportListenerWrapper wrapper = listener == null ? + null : new SelectTransportListenerWrapper(mContext, listener); + sService.selectBackupTransportAsync(transport, wrapper); + } catch (RemoteException e) { + Log.e(TAG, "selectBackupTransportAsync() couldn't connect"); + } + } + } + + /** * Schedule an immediate backup attempt for all pending key/value updates. This * is primarily intended for transports to use when they detect a suitable * opportunity for doing a backup pass. If there are no pending updates to @@ -598,4 +649,35 @@ public class BackupManager { mHandler.obtainMessage(MSG_FINISHED, status, 0)); } } + + private class SelectTransportListenerWrapper extends ISelectBackupTransportCallback.Stub { + + private final Handler mHandler; + private final SelectBackupTransportCallback mListener; + + SelectTransportListenerWrapper(Context context, SelectBackupTransportCallback listener) { + mHandler = new Handler(context.getMainLooper()); + mListener = listener; + } + + @Override + public void onSuccess(final String transportName) { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onSuccess(transportName); + } + }); + } + + @Override + public void onFailure(final int reason) { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onFailure(reason); + } + }); + } + } } diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index fe23c288c7ce..1657e2e98698 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -19,8 +19,10 @@ package android.app.backup; import android.app.backup.IBackupObserver; import android.app.backup.IFullBackupRestoreObserver; import android.app.backup.IRestoreSession; +import android.app.backup.ISelectBackupTransportCallback; import android.os.ParcelFileDescriptor; import android.content.Intent; +import android.content.ComponentName; /** * Direct interface to the Backup Manager Service that applications invoke on. The only @@ -217,6 +219,8 @@ interface IBackupManager { */ String[] listAllTransports(); + ComponentName[] listAllTransportComponents(); + /** * Retrieve the list of whitelisted transport components. Callers do </i>not</i> need * any special permission. @@ -238,6 +242,21 @@ interface IBackupManager { String selectBackupTransport(String transport); /** + * Specify the current backup transport and get notified when the transport is ready to be used. + * This method is async because BackupManager might need to bind to the specified transport + * which is in a separate process. + * + * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * + * @param transport ComponentName of the service hosting the transport. This is different from + * the transport's name that is returned by {@link BackupTransport#name()}. + * @param listener A listener object to get a callback on the transport being selected. + * + * @hide + */ + void selectBackupTransportAsync(in ComponentName transport, ISelectBackupTransportCallback listener); + + /** * Get the configuration Intent, if any, from the given transport. Callers must * hold the android.permission.BACKUP permission in order to use this method. * diff --git a/core/java/android/app/backup/ISelectBackupTransportCallback.aidl b/core/java/android/app/backup/ISelectBackupTransportCallback.aidl new file mode 100644 index 000000000000..5de7c5e1ed6a --- /dev/null +++ b/core/java/android/app/backup/ISelectBackupTransportCallback.aidl @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.backup; + +/** + * Callback class for receiving success or failure callbacks on selecting a backup transport. These + * methods will all be called on your application's main thread. + * + * @hide + */ +oneway interface ISelectBackupTransportCallback { + + /** + * Called when BackupManager has successfully bound to the requested transport. + * + * @param transportName Name of the selected transport. This is the String returned by + * {@link BackupTransport#name()}. + */ + void onSuccess(String transportName); + + /** + * Called when BackupManager fails to bind to the requested transport. + * + * @param reason Error code denoting reason for failure. + */ + void onFailure(int reason); +} diff --git a/core/java/android/app/backup/SelectBackupTransportCallback.java b/core/java/android/app/backup/SelectBackupTransportCallback.java new file mode 100644 index 000000000000..0c8a0dcb86ef --- /dev/null +++ b/core/java/android/app/backup/SelectBackupTransportCallback.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.app.backup; + +import android.annotation.SystemApi; + +/** + * Callback class for receiving success or failure callbacks on selecting a backup transport. These + * methods will all be called on your application's main thread. + * + * @hide + */ +@SystemApi +public abstract class SelectBackupTransportCallback { + + /** + * Called when BackupManager has successfully bound to the requested transport. + * + * @param transportName Name of the selected transport. This is the String returned by + * {@link BackupTransport#name()}. + */ + public void onSuccess(String transportName){} + + /** + * Called when BackupManager fails to bind to the requested transport. + * + * @param reason Error code denoting reason for failure. + */ + public void onFailure(int reason){} +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index f41d7f2f168d..38e6fbeb007f 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -34,9 +34,7 @@ import android.annotation.TestApi; import android.annotation.UserIdInt; import android.app.IApplicationThread; import android.app.IServiceConnection; -import android.app.LoadedApk; import android.app.Notification; -import android.app.admin.DevicePolicyManager; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; @@ -64,6 +62,7 @@ import android.view.Display; import android.view.DisplayAdjustments; import android.view.ViewDebug; import android.view.WindowManager; +import android.view.textclassifier.TextClassificationManager; import java.io.File; import java.io.FileInputStream; @@ -3348,10 +3347,10 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a - * {@link android.text.TextClassificationManager} for text classification services. + * {@link TextClassificationManager} for text classification services. * * @see #getSystemService - * @see android.text.TextClassificationManager + * @see TextClassificationManager */ public static final String TEXT_CLASSIFICATION_SERVICE = "textclassification"; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index c5500949b25f..44e106e2abfc 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1628,6 +1628,19 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE"; /** + * Activity Action: Launch application uninstaller. + * <p> + * Input: The data must be a package: URI whose scheme specific part is + * the package name of the current installed package to be uninstalled. + * You can optionally supply {@link #EXTRA_RETURN_RESULT}. + * <p> + * Output: Nothing. + * </p> + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CLEAR_PACKAGE = "android.intent.action.CLEAR_PACKAGE"; + + /** * Specify whether the package should be uninstalled for all users. * @hide because these should not be part of normal application flow. */ @@ -3966,6 +3979,12 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_EPHEMERAL_TOKEN = "android.intent.extra.EPHEMERAL_TOKEN"; /** + * The version code of the app to install components from. + * @hide + */ + public static final String EXTRA_VERSION_CODE = "android.intent.extra.VERSION_CODE"; + + /** * A Bundle forming a mapping of potential target package names to different extras Bundles * to add to the default intent extras in {@link #EXTRA_INTENT} when used with * {@link #ACTION_CHOOSER}. Each key should be a package name. The package need not diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 4bd091dae77e..92cb7099eb5d 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -177,10 +177,14 @@ public class ActivityInfo extends ComponentInfo */ public static final int RESIZE_MODE_RESIZEABLE = 2; /** - * Activity is resizeable and supported picture-in-picture mode. + * Activity is resizeable and supported picture-in-picture mode. This flag is now deprecated + * since activities do not need to be resizeable to support picture-in-picture. + * See {@link #FLAG_SUPPORTS_PICTURE_IN_PICTURE}. + * * @hide + * @deprecated */ - public static final int RESIZE_MODE_RESIZEABLE_AND_PIPABLE = 3; + public static final int RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED = 3; /** * Activity does not support resizing, but we are forcing it to be resizeable. Only affects * certain pre-N apps where we force them to be resizeable. @@ -220,6 +224,44 @@ public class ActivityInfo extends ComponentInfo public String requestedVrComponent; /** + * Value for {@link #colorMode} indicating that the activity should use the + * default color mode (sRGB, low dynamic range). + * + * @see android.R.attr#colorMode + */ + public static final int COLOR_MODE_DEFAULT = 0; + /** + * Value of {@link #colorMode} indicating that the activity should use a + * wide color gamut if the presentation display supports it. + * + * @see android.R.attr#colorMode + */ + public static final int COLOR_MODE_WIDE_COLOR_GAMUT = 1; + /** + * Value of {@link #colorMode} indicating that the activity should use a + * high dynamic range if the presentation display supports it. + * + * @see android.R.attr#colorMode + */ + public static final int COLOR_MODE_HDR = 2; + + /** @hide */ + @IntDef({ + COLOR_MODE_DEFAULT, + COLOR_MODE_WIDE_COLOR_GAMUT, + COLOR_MODE_HDR, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ColorMode {} + + /** + * The color mode requested by this activity. The target display may not be + * able to honor the request. + */ + @ColorMode + public int colorMode = COLOR_MODE_DEFAULT; + + /** * Bit in {@link #flags} indicating whether this activity is able to * run in multiple processes. If * true, the system may instantiate it in the some process as the @@ -369,6 +411,13 @@ public class ActivityInfo extends ComponentInfo public static final int FLAG_VISIBLE_TO_EPHEMERAL = 0x100000; /** + * Bit in {@link #flags} indicating if the activity supports picture-in-picture mode. + * See {@link android.R.attr#supportsPictureInPicture}. + * @hide + */ + public static final int FLAG_SUPPORTS_PICTURE_IN_PICTURE = 0x200000; + + /** * @hide Bit in {@link #flags}: If set, this component will only be seen * by the system user. Only works with broadcast receivers. Set from the * android.R.attr#systemUserOnly attribute. @@ -566,7 +615,7 @@ public class ActivityInfo extends ComponentInfo CONFIG_SMALLEST_SCREEN_SIZE, CONFIG_DENSITY, CONFIG_LAYOUT_DIRECTION, - CONFIG_COLORIMETRY, + CONFIG_COLOR_MODE, CONFIG_FONT_SCALE, }) @Retention(RetentionPolicy.SOURCE) @@ -675,7 +724,7 @@ public class ActivityInfo extends ComponentInfo * can itself handle the change to the display color gamut or dynamic * range. Set from the {@link android.R.attr#configChanges} attribute. */ - public static final int CONFIG_COLORIMETRY = 0x4000; + public static final int CONFIG_COLOR_MODE = 0x4000; /** * Bit in {@link #configChanges} that indicates that the activity * can itself handle asset path changes. Set from the {@link android.R.attr#configChanges} @@ -713,7 +762,7 @@ public class ActivityInfo extends ComponentInfo Configuration.NATIVE_CONFIG_SMALLEST_SCREEN_SIZE, // SMALLEST SCREEN SIZE Configuration.NATIVE_CONFIG_DENSITY, // DENSITY Configuration.NATIVE_CONFIG_LAYOUTDIR, // LAYOUT DIRECTION - Configuration.NATIVE_CONFIG_COLORIMETRY, // COLORIMETRY + Configuration.NATIVE_CONFIG_COLOR_MODE, // COLOR_MODE }; /** @@ -770,7 +819,7 @@ public class ActivityInfo extends ComponentInfo * {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION}, * {@link #CONFIG_ORIENTATION}, {@link #CONFIG_SCREEN_LAYOUT}, * {@link #CONFIG_DENSITY}, {@link #CONFIG_LAYOUT_DIRECTION} and - * {@link #CONFIG_COLORIMETRY}. + * {@link #CONFIG_COLOR_MODE}. * Set from the {@link android.R.attr#configChanges} attribute. */ public int configChanges; @@ -873,6 +922,7 @@ public class ActivityInfo extends ComponentInfo resizeMode = orig.resizeMode; requestedVrComponent = orig.requestedVrComponent; rotationAnimation = orig.rotationAnimation; + colorMode = orig.colorMode; } /** @@ -926,10 +976,17 @@ public class ActivityInfo extends ComponentInfo || screenOrientation == SCREEN_ORIENTATION_USER_PORTRAIT; } + /** + * Returns true if the activity supports picture-in-picture. + * @hide + */ + public boolean supportsPictureInPicture() { + return (flags & FLAG_SUPPORTS_PICTURE_IN_PICTURE) != 0; + } + /** @hide */ public static boolean isResizeableMode(int mode) { return mode == RESIZE_MODE_RESIZEABLE - || mode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE || mode == RESIZE_MODE_FORCE_RESIZEABLE || mode == RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY || mode == RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY @@ -953,8 +1010,6 @@ public class ActivityInfo extends ComponentInfo return "RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION"; case RESIZE_MODE_RESIZEABLE: return "RESIZE_MODE_RESIZEABLE"; - case RESIZE_MODE_RESIZEABLE_AND_PIPABLE: - return "RESIZE_MODE_RESIZEABLE_AND_PIPABLE"; case RESIZE_MODE_FORCE_RESIZEABLE: return "RESIZE_MODE_FORCE_RESIZEABLE"; case RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY: @@ -1055,6 +1110,7 @@ public class ActivityInfo extends ComponentInfo dest.writeInt(resizeMode); dest.writeString(requestedVrComponent); dest.writeInt(rotationAnimation); + dest.writeInt(colorMode); } public static final Parcelable.Creator<ActivityInfo> CREATOR @@ -1090,6 +1146,7 @@ public class ActivityInfo extends ComponentInfo resizeMode = source.readInt(); requestedVrComponent = source.readString(); rotationAnimation = source.readInt(); + colorMode = source.readInt(); } /** diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java index 5cd15ddd2faa..b091d7ed4168 100644 --- a/core/java/android/content/pm/ComponentInfo.java +++ b/core/java/android/content/pm/ComponentInfo.java @@ -72,6 +72,11 @@ public class ComponentInfo extends PackageItemInfo { */ public boolean directBootAware = false; + /** + * The name of the split that contains the code for this component. + */ + public String splitName; + /** @removed */ @Deprecated public boolean encryptionAware = false; diff --git a/core/java/android/content/pm/EphemeralResolveInfo.java b/core/java/android/content/pm/EphemeralResolveInfo.java index f6200886cd71..1d7b8f28453c 100644 --- a/core/java/android/content/pm/EphemeralResolveInfo.java +++ b/core/java/android/content/pm/EphemeralResolveInfo.java @@ -43,9 +43,11 @@ public final class EphemeralResolveInfo implements Parcelable { private final String mPackageName; /** The filters used to match domain */ private final List<EphemeralIntentFilter> mFilters; + /** The version code of the app that this class resolves to */ + private final int mVersionCode; /** Filters only for legacy clients */ @Deprecated - private List<IntentFilter> mLegacyFilters; + private final List<IntentFilter> mLegacyFilters; @Deprecated public EphemeralResolveInfo(@NonNull Uri uri, @NonNull String packageName, @@ -59,10 +61,17 @@ public final class EphemeralResolveInfo implements Parcelable { mFilters.add(new EphemeralIntentFilter(packageName, filters)); mLegacyFilters = new ArrayList<IntentFilter>(filters.size()); mLegacyFilters.addAll(filters); + mVersionCode = -1; } + @Deprecated public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName, @Nullable List<EphemeralIntentFilter> filters) { + this(digest, packageName, filters, -1 /*versionCode*/); + } + + public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName, + @Nullable List<EphemeralIntentFilter> filters, int versionConde) { // validate arguments if ((packageName == null && (filters != null && filters.size() != 0)) || (packageName != null && (filters == null || filters.size() == 0))) { @@ -75,7 +84,9 @@ public final class EphemeralResolveInfo implements Parcelable { } else { mFilters = null; } + mLegacyFilters = null; mPackageName = packageName; + mVersionCode = versionConde; } public EphemeralResolveInfo(@NonNull String hostName, @Nullable String packageName, @@ -88,6 +99,7 @@ public final class EphemeralResolveInfo implements Parcelable { mPackageName = in.readString(); mFilters = new ArrayList<EphemeralIntentFilter>(); in.readList(mFilters, null /*loader*/); + mVersionCode = in.readInt(); mLegacyFilters = new ArrayList<IntentFilter>(); in.readList(mLegacyFilters, null /*loader*/); } @@ -104,15 +116,19 @@ public final class EphemeralResolveInfo implements Parcelable { return mPackageName; } + public List<EphemeralIntentFilter> getIntentFilters() { + return mFilters; + } + + public int getVersionCode() { + return mVersionCode; + } + @Deprecated public List<IntentFilter> getFilters() { return mLegacyFilters; } - public List<EphemeralIntentFilter> getIntentFilters() { - return mFilters; - } - @Override public int describeContents() { return 0; @@ -123,6 +139,7 @@ public final class EphemeralResolveInfo implements Parcelable { out.writeParcelable(mDigest, flags); out.writeString(mPackageName); out.writeList(mFilters); + out.writeInt(mVersionCode); out.writeList(mLegacyFilters); } diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index 51524164538a..c08bd1db8302 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -38,15 +38,21 @@ import java.util.List; interface ILauncherApps { void addOnAppsChangedListener(String callingPackage, in IOnAppsChangedListener listener); void removeOnAppsChangedListener(in IOnAppsChangedListener listener); - ParceledListSlice getLauncherActivities(String packageName, in UserHandle user); - ActivityInfo resolveActivity(in ComponentName component, in UserHandle user); - void startActivityAsUser(in ComponentName component, in Rect sourceBounds, + ParceledListSlice getLauncherActivities( + String callingPackage, String packageName, in UserHandle user); + ActivityInfo resolveActivity( + String callingPackage, in ComponentName component, in UserHandle user); + void startActivityAsUser(String callingPackage, + in ComponentName component, in Rect sourceBounds, in Bundle opts, in UserHandle user); - void showAppDetailsAsUser(in ComponentName component, in Rect sourceBounds, + void showAppDetailsAsUser( + String callingPackage, in ComponentName component, in Rect sourceBounds, in Bundle opts, in UserHandle user); - boolean isPackageEnabled(String packageName, in UserHandle user); - boolean isActivityEnabled(in ComponentName component, in UserHandle user); - ApplicationInfo getApplicationInfo(String packageName, int flags, in UserHandle user); + boolean isPackageEnabled(String callingPackage, String packageName, in UserHandle user); + boolean isActivityEnabled( + String callingPackage, in ComponentName component, in UserHandle user); + ApplicationInfo getApplicationInfo( + String callingPackage, String packageName, int flags, in UserHandle user); ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName, in List shortcutIds, in ComponentName componentName, int flags, in UserHandle user); @@ -62,7 +68,8 @@ interface ILauncherApps { boolean hasShortcutHostPermission(String callingPackage); - ParceledListSlice getShortcutConfigActivities(String packageName, in UserHandle user); + ParceledListSlice getShortcutConfigActivities( + String callingPackage, String packageName, in UserHandle user); IntentSender getShortcutConfigActivityIntent(String callingPackage, in ComponentName component, in UserHandle user); } diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index c6a86748bfe8..57d2ba7c84e5 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -407,7 +407,8 @@ public class LauncherApps { */ public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) { try { - return convertToActivityList(mService.getLauncherActivities(packageName, user), user); + return convertToActivityList(mService.getLauncherActivities(mContext.getPackageName(), + packageName, user), user); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -423,7 +424,8 @@ public class LauncherApps { */ public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) { try { - ActivityInfo ai = mService.resolveActivity(intent.getComponent(), user); + ActivityInfo ai = mService.resolveActivity(mContext.getPackageName(), + intent.getComponent(), user); if (ai != null) { LauncherActivityInfo info = new LauncherActivityInfo(mContext, ai, user); return info; @@ -448,7 +450,8 @@ public class LauncherApps { Log.i(TAG, "StartMainActivity " + component + " " + user.getIdentifier()); } try { - mService.startActivityAsUser(component, sourceBounds, opts, user); + mService.startActivityAsUser(mContext.getPackageName(), + component, sourceBounds, opts, user); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -466,7 +469,8 @@ public class LauncherApps { public void startAppDetailsActivity(ComponentName component, UserHandle user, Rect sourceBounds, Bundle opts) { try { - mService.showAppDetailsAsUser(component, sourceBounds, opts, user); + mService.showAppDetailsAsUser(mContext.getPackageName(), + component, sourceBounds, opts, user); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -486,7 +490,8 @@ public class LauncherApps { public List<LauncherActivityInfo> getShortcutConfigActivityList(@Nullable String packageName, @NonNull UserHandle user) { try { - return convertToActivityList(mService.getShortcutConfigActivities(packageName, user), + return convertToActivityList(mService.getShortcutConfigActivities( + mContext.getPackageName(), packageName, user), user); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); @@ -549,7 +554,7 @@ public class LauncherApps { */ public boolean isPackageEnabled(String packageName, UserHandle user) { try { - return mService.isPackageEnabled(packageName, user); + return mService.isPackageEnabled(mContext.getPackageName(), packageName, user); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -569,7 +574,7 @@ public class LauncherApps { public ApplicationInfo getApplicationInfo(String packageName, @ApplicationInfoFlags int flags, UserHandle user) { try { - return mService.getApplicationInfo(packageName, flags, user); + return mService.getApplicationInfo(mContext.getPackageName(), packageName, flags, user); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -585,7 +590,7 @@ public class LauncherApps { */ public boolean isActivityEnabled(ComponentName component, UserHandle user) { try { - return mService.isActivityEnabled(component, user); + return mService.isActivityEnabled(mContext.getPackageName(), component, user); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 98edbf817519..ffcb1f32b8d7 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2162,6 +2162,15 @@ public abstract class PackageManager { public static final String FEATURE_WATCH = "android.hardware.type.watch"; /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: This is a device for IoT and may not have an UI. An embedded + * device is defined as a full stack Android device with or without a display and no + * user-installable apps. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded"; + + /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: * The device supports printing. */ diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index d8d7abe6360a..cd51bce4a47b 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -18,12 +18,12 @@ package android.content.pm; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; import static android.content.pm.ActivityInfo.FLAG_ON_TOP_LAUNCHER; +import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; -import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -2402,7 +2402,7 @@ public class PackageParser { // cannot be windowed / resized. Note that an SDK version of 0 is common for // pre-Doughnut applications. if (pkg.applicationInfo.usesCompatibilityMode()) { - adjustPackageToBeUnresizeable(pkg); + adjustPackageToBeUnresizeableAndUnpipable(pkg); } return pkg; } @@ -2413,9 +2413,10 @@ public class PackageParser { * * @param pkg The package which needs to be marked as unresizable. */ - private void adjustPackageToBeUnresizeable(Package pkg) { + private void adjustPackageToBeUnresizeableAndUnpipable(Package pkg) { for (Activity a : pkg.activities) { a.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; + a.info.flags &= ~FLAG_SUPPORTS_PICTURE_IN_PICTURE; } } @@ -3899,6 +3900,9 @@ public class PackageParser { a.info.taskAffinity = buildTaskAffinityName(owner.applicationInfo.packageName, owner.applicationInfo.taskAffinity, str, outError); + a.info.splitName = + sa.getNonConfigurationString(R.styleable.AndroidManifestActivity_splitName, 0); + a.info.flags = 0; if (sa.getBoolean( R.styleable.AndroidManifestActivity_multiprocess, false)) { @@ -3997,6 +4001,11 @@ public class PackageParser { setActivityResizeMode(a.info, sa, owner); + if (sa.getBoolean(R.styleable.AndroidManifestActivity_supportsPictureInPicture, + false)) { + a.info.flags |= FLAG_SUPPORTS_PICTURE_IN_PICTURE; + } + if (sa.getBoolean(R.styleable.AndroidManifestActivity_alwaysFocusable, false)) { a.info.flags |= FLAG_ALWAYS_FOCUSABLE; } @@ -4017,6 +4026,9 @@ public class PackageParser { a.info.rotationAnimation = sa.getInt(R.styleable.AndroidManifestActivity_rotationAnimation, ROTATION_ANIMATION_ROTATE); + + a.info.colorMode = sa.getInt(R.styleable.AndroidManifestActivity_colorMode, + ActivityInfo.COLOR_MODE_DEFAULT); } else { a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE; a.info.configChanges = 0; @@ -4158,16 +4170,13 @@ public class PackageParser { private void setActivityResizeMode(ActivityInfo aInfo, TypedArray sa, Package owner) { final boolean appExplicitDefault = (owner.applicationInfo.privateFlags & PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_EXPLICITLY_SET) != 0; - final boolean supportsPip = - sa.getBoolean(R.styleable.AndroidManifestActivity_supportsPictureInPicture, false); if (sa.hasValue(R.styleable.AndroidManifestActivity_resizeableActivity) || appExplicitDefault) { // Activity or app explicitly set if it is resizeable or not; if (sa.getBoolean(R.styleable.AndroidManifestActivity_resizeableActivity, appExplicitDefault)) { - aInfo.resizeMode = - supportsPip ? RESIZE_MODE_RESIZEABLE_AND_PIPABLE : RESIZE_MODE_RESIZEABLE; + aInfo.resizeMode = RESIZE_MODE_RESIZEABLE; } else { aInfo.resizeMode = RESIZE_MODE_UNRESIZEABLE; } @@ -4520,6 +4529,9 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestProvider_initOrder, 0); + p.info.splitName = + sa.getNonConfigurationString(R.styleable.AndroidManifestProvider_splitName, 0); + p.info.flags = 0; if (sa.getBoolean( @@ -4816,6 +4828,9 @@ public class PackageParser { s.info.permission = str.length() > 0 ? str.toString().intern() : null; } + s.info.splitName = + sa.getNonConfigurationString(R.styleable.AndroidManifestService_splitName, 0); + s.info.flags = 0; if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestService_stopWithTask, diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index a81329d9ee67..99fbee1ea3cc 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -102,66 +102,66 @@ public final class Configuration implements Parcelable, Comparable<Configuration public boolean userSetLocale; - /** Constant for {@link #colorimetry}: bits that encode whether the screen is wide gamut. */ - public static final int COLORIMETRY_WIDE_COLOR_GAMUT_MASK = 0x3; + /** Constant for {@link #colorMode}: bits that encode whether the screen is wide gamut. */ + public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 0x3; /** - * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_WIDE_COLOR_GAMUT_MASK} value + * Constant for {@link #colorMode}: a {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} value * indicating that it is unknown whether or not the screen is wide gamut. */ - public static final int COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED = 0x0; + public static final int COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0x0; /** - * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_WIDE_COLOR_GAMUT_MASK} value + * Constant for {@link #colorMode}: a {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} value * indicating that the screen is not wide gamut. * <p>Corresponds to the <code>-nowidecg</code> resource qualifier.</p> */ - public static final int COLORIMETRY_WIDE_COLOR_GAMUT_NO = 0x1; + public static final int COLOR_MODE_WIDE_COLOR_GAMUT_NO = 0x1; /** - * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_WIDE_COLOR_GAMUT_MASK} value + * Constant for {@link #colorMode}: a {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} value * indicating that the screen is wide gamut. * <p>Corresponds to the <code>-widecg</code> resource qualifier.</p> */ - public static final int COLORIMETRY_WIDE_COLOR_GAMUT_YES = 0x2; + public static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 0x2; - /** Constant for {@link #colorimetry}: bits that encode whether the dynamic range of the screen. */ - public static final int COLORIMETRY_HDR_MASK = 0xc; - /** Constant for {@link #colorimetry}: bits shift to get the screen dynamic range. */ - public static final int COLORIMETRY_HDR_SHIFT = 2; + /** Constant for {@link #colorMode}: bits that encode whether the dynamic range of the screen. */ + public static final int COLOR_MODE_HDR_MASK = 0xc; + /** Constant for {@link #colorMode}: bits shift to get the screen dynamic range. */ + public static final int COLOR_MODE_HDR_SHIFT = 2; /** - * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_HDR_MASK} value + * Constant for {@link #colorMode}: a {@link #COLOR_MODE_HDR_MASK} value * indicating that it is unknown whether or not the screen is HDR. */ - public static final int COLORIMETRY_HDR_UNDEFINED = 0x0; + public static final int COLOR_MODE_HDR_UNDEFINED = 0x0; /** - * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_HDR_MASK} value + * Constant for {@link #colorMode}: a {@link #COLOR_MODE_HDR_MASK} value * indicating that the screen is not HDR (low/standard dynamic range). * <p>Corresponds to the <code>-lowdr</code> resource qualifier.</p> */ - public static final int COLORIMETRY_HDR_NO = 0x1 << COLORIMETRY_HDR_SHIFT; + public static final int COLOR_MODE_HDR_NO = 0x1 << COLOR_MODE_HDR_SHIFT; /** - * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_HDR_MASK} value + * Constant for {@link #colorMode}: a {@link #COLOR_MODE_HDR_MASK} value * indicating that the screen is HDR (dynamic range). * <p>Corresponds to the <code>-highdr</code> resource qualifier.</p> */ - public static final int COLORIMETRY_HDR_YES = 0x2 << COLORIMETRY_HDR_SHIFT; + public static final int COLOR_MODE_HDR_YES = 0x2 << COLOR_MODE_HDR_SHIFT; - /** Constant for {@link #colorimetry}: a value indicating that colorimetry is undefined */ + /** Constant for {@link #colorMode}: a value indicating that the color mode is undefined */ @SuppressWarnings("PointlessBitwiseExpression") - public static final int COLORIMETRY_UNDEFINED = COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED | - COLORIMETRY_HDR_UNDEFINED; + public static final int COLOR_MODE_UNDEFINED = COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED | + COLOR_MODE_HDR_UNDEFINED; /** * Bit mask of for color capabilities of the screen. Currently there are two fields: - * <p>The {@link #COLORIMETRY_WIDE_COLOR_GAMUT_MASK} bits define the color gamut of + * <p>The {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} bits define the color gamut of * the screen. They may be one of - * {@link #COLORIMETRY_WIDE_COLOR_GAMUT_NO} or {@link #COLORIMETRY_WIDE_COLOR_GAMUT_YES}.</p> + * {@link #COLOR_MODE_WIDE_COLOR_GAMUT_NO} or {@link #COLOR_MODE_WIDE_COLOR_GAMUT_YES}.</p> * - * <p>The {@link #COLORIMETRY_HDR_MASK} defines the dynamic range of the screen. They may be - * one of {@link #COLORIMETRY_HDR_NO} or {@link #COLORIMETRY_HDR_YES}.</p> + * <p>The {@link #COLOR_MODE_HDR_MASK} defines the dynamic range of the screen. They may be + * one of {@link #COLOR_MODE_HDR_NO} or {@link #COLOR_MODE_HDR_YES}.</p> * * <p>See <a href="{@docRoot}guide/practices/screens_support.html">Supporting * Multiple Screens</a> for more information.</p> */ - public int colorimetry; + public int colorMode; /** Constant for {@link #screenLayout}: bits that encode the size. */ public static final int SCREENLAYOUT_SIZE_MASK = 0x0f; @@ -393,8 +393,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration if ((diff & ActivityInfo.CONFIG_SCREEN_LAYOUT) != 0) { list.add("CONFIG_SCREEN_LAYOUT"); } - if ((diff & ActivityInfo.CONFIG_COLORIMETRY) != 0) { - list.add("CONFIG_COLORIMETRY"); + if ((diff & ActivityInfo.CONFIG_COLOR_MODE) != 0) { + list.add("CONFIG_COLOR_MODE"); } if ((diff & ActivityInfo.CONFIG_UI_MODE) != 0) { list.add("CONFIG_UI_MODE"); @@ -776,7 +776,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration NATIVE_CONFIG_UI_MODE, NATIVE_CONFIG_SMALLEST_SCREEN_SIZE, NATIVE_CONFIG_LAYOUTDIR, - NATIVE_CONFIG_COLORIMETRY, + NATIVE_CONFIG_COLOR_MODE, }) @Retention(RetentionPolicy.SOURCE) public @interface NativeConfig {} @@ -813,8 +813,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration public static final int NATIVE_CONFIG_SMALLEST_SCREEN_SIZE = 0x2000; /** @hide Native-specific bit mask for LAYOUTDIR config ; DO NOT USE UNLESS YOU ARE SURE.*/ public static final int NATIVE_CONFIG_LAYOUTDIR = 0x4000; - /** @hide Native-specific bit mask for COLORIMETRY config ; DO NOT USE UNLESS YOU ARE SURE.*/ - public static final int NATIVE_CONFIG_COLORIMETRY = 0x10000; + /** @hide Native-specific bit mask for COLOR_MODE config ; DO NOT USE UNLESS YOU ARE SURE.*/ + public static final int NATIVE_CONFIG_COLOR_MODE = 0x10000; /** * <p>Construct an invalid Configuration. This state is only suitable for constructing a @@ -873,7 +873,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration navigationHidden = o.navigationHidden; orientation = o.orientation; screenLayout = o.screenLayout; - colorimetry = o.colorimetry; + colorMode = o.colorMode; uiMode = o.uiMode; screenWidthDp = o.screenWidthDp; screenHeightDp = o.screenHeightDp; @@ -954,19 +954,19 @@ public final class Configuration implements Parcelable, Comparable<Configuration default: sb.append(" layoutLong="); sb.append(screenLayout&SCREENLAYOUT_LONG_MASK); break; } - switch ((colorimetry&COLORIMETRY_HDR_MASK)) { - case COLORIMETRY_HDR_UNDEFINED: sb.append(" ?ldr"); break; // most likely not HDR - case COLORIMETRY_HDR_NO: /* ldr is not interesting to print */ break; - case COLORIMETRY_HDR_YES: sb.append(" hdr"); break; + switch ((colorMode &COLOR_MODE_HDR_MASK)) { + case COLOR_MODE_HDR_UNDEFINED: sb.append(" ?ldr"); break; // most likely not HDR + case COLOR_MODE_HDR_NO: /* ldr is not interesting to print */ break; + case COLOR_MODE_HDR_YES: sb.append(" hdr"); break; default: sb.append(" dynamicRange="); - sb.append(colorimetry&COLORIMETRY_HDR_MASK); break; + sb.append(colorMode &COLOR_MODE_HDR_MASK); break; } - switch ((colorimetry&COLORIMETRY_WIDE_COLOR_GAMUT_MASK)) { - case COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED: sb.append(" ?wideColorGamut"); break; - case COLORIMETRY_WIDE_COLOR_GAMUT_NO: /* not wide is not interesting to print */ break; - case COLORIMETRY_WIDE_COLOR_GAMUT_YES: sb.append(" widecg"); break; + switch ((colorMode &COLOR_MODE_WIDE_COLOR_GAMUT_MASK)) { + case COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED: sb.append(" ?wideColorGamut"); break; + case COLOR_MODE_WIDE_COLOR_GAMUT_NO: /* not wide is not interesting to print */ break; + case COLOR_MODE_WIDE_COLOR_GAMUT_YES: sb.append(" widecg"); break; default: sb.append(" wideColorGamut="); - sb.append(colorimetry&COLORIMETRY_WIDE_COLOR_GAMUT_MASK); break; + sb.append(colorMode &COLOR_MODE_WIDE_COLOR_GAMUT_MASK); break; } switch (orientation) { case ORIENTATION_UNDEFINED: sb.append(" ?orien"); break; @@ -1059,7 +1059,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration navigationHidden = NAVIGATIONHIDDEN_UNDEFINED; orientation = ORIENTATION_UNDEFINED; screenLayout = SCREENLAYOUT_UNDEFINED; - colorimetry = COLORIMETRY_UNDEFINED; + colorMode = COLOR_MODE_UNDEFINED; uiMode = UI_MODE_TYPE_UNDEFINED; screenWidthDp = compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED; screenHeightDp = compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED; @@ -1195,21 +1195,21 @@ public final class Configuration implements Parcelable, Comparable<Configuration | (delta.screenLayout & SCREENLAYOUT_COMPAT_NEEDED); } - if (((delta.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK) != - COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED) - && (delta.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK) - != (colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK)) { - changed |= ActivityInfo.CONFIG_COLORIMETRY; - colorimetry = (colorimetry & ~COLORIMETRY_WIDE_COLOR_GAMUT_MASK) - | (delta.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK); + if (((delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) != + COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED) + && (delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) + != (colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK)) { + changed |= ActivityInfo.CONFIG_COLOR_MODE; + colorMode = (colorMode & ~COLOR_MODE_WIDE_COLOR_GAMUT_MASK) + | (delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK); } - if (((delta.colorimetry & COLORIMETRY_HDR_MASK) != COLORIMETRY_HDR_UNDEFINED) - && (delta.colorimetry & COLORIMETRY_HDR_MASK) - != (colorimetry & COLORIMETRY_HDR_MASK)) { - changed |= ActivityInfo.CONFIG_COLORIMETRY; - colorimetry = (colorimetry & ~COLORIMETRY_HDR_MASK) - | (delta.colorimetry & COLORIMETRY_HDR_MASK); + if (((delta.colorMode & COLOR_MODE_HDR_MASK) != COLOR_MODE_HDR_UNDEFINED) + && (delta.colorMode & COLOR_MODE_HDR_MASK) + != (colorMode & COLOR_MODE_HDR_MASK)) { + changed |= ActivityInfo.CONFIG_COLOR_MODE; + colorMode = (colorMode & ~COLOR_MODE_HDR_MASK) + | (delta.colorMode & COLOR_MODE_HDR_MASK); } if (delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED) @@ -1362,17 +1362,17 @@ public final class Configuration implements Parcelable, Comparable<Configuration changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT; } if ((compareUndefined || - (delta.colorimetry & COLORIMETRY_HDR_MASK) != COLORIMETRY_HDR_UNDEFINED) - && (colorimetry & COLORIMETRY_HDR_MASK) != - (delta.colorimetry & COLORIMETRY_HDR_MASK)) { - changed |= ActivityInfo.CONFIG_COLORIMETRY; + (delta.colorMode & COLOR_MODE_HDR_MASK) != COLOR_MODE_HDR_UNDEFINED) + && (colorMode & COLOR_MODE_HDR_MASK) != + (delta.colorMode & COLOR_MODE_HDR_MASK)) { + changed |= ActivityInfo.CONFIG_COLOR_MODE; } if ((compareUndefined || - (delta.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK) != - COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED) - && (colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK) != - (delta.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK)) { - changed |= ActivityInfo.CONFIG_COLORIMETRY; + (delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) != + COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED) + && (colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) != + (delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK)) { + changed |= ActivityInfo.CONFIG_COLOR_MODE; } if ((compareUndefined || delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED)) && uiMode != delta.uiMode) { @@ -1485,7 +1485,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration dest.writeInt(navigationHidden); dest.writeInt(orientation); dest.writeInt(screenLayout); - dest.writeInt(colorimetry); + dest.writeInt(colorMode); dest.writeInt(uiMode); dest.writeInt(screenWidthDp); dest.writeInt(screenHeightDp); @@ -1520,7 +1520,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration navigationHidden = source.readInt(); orientation = source.readInt(); screenLayout = source.readInt(); - colorimetry = source.readInt(); + colorMode = source.readInt(); uiMode = source.readInt(); screenWidthDp = source.readInt(); screenHeightDp = source.readInt(); @@ -1602,7 +1602,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration if (n != 0) return n; n = this.orientation - that.orientation; if (n != 0) return n; - n = this.colorimetry - that.colorimetry; + n = this.colorMode - that.colorMode; if (n != 0) return n; n = this.screenLayout - that.screenLayout; if (n != 0) return n; @@ -1649,7 +1649,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration result = 31 * result + navigationHidden; result = 31 * result + orientation; result = 31 * result + screenLayout; - result = 31 * result + colorimetry; + result = 31 * result + colorMode; result = 31 * result + uiMode; result = 31 * result + screenWidthDp; result = 31 * result + screenHeightDp; @@ -1763,7 +1763,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration * @return true if the screen has a wide color gamut, false otherwise */ public boolean isScreenWideColorGamut() { - return (colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK) == COLORIMETRY_WIDE_COLOR_GAMUT_YES; + return (colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) == COLOR_MODE_WIDE_COLOR_GAMUT_YES; } /** @@ -1772,7 +1772,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration * @return true if the screen has a high dynamic range, false otherwise */ public boolean isScreenHdr() { - return (colorimetry & COLORIMETRY_HDR_MASK) == COLORIMETRY_HDR_YES; + return (colorMode & COLOR_MODE_HDR_MASK) == COLOR_MODE_HDR_YES; } /** @@ -1907,22 +1907,22 @@ public final class Configuration implements Parcelable, Comparable<Configuration break; } - switch (config.colorimetry & Configuration.COLORIMETRY_HDR_MASK) { - case Configuration.COLORIMETRY_HDR_YES: + switch (config.colorMode & Configuration.COLOR_MODE_HDR_MASK) { + case Configuration.COLOR_MODE_HDR_YES: parts.add("highdr"); break; - case Configuration.COLORIMETRY_HDR_NO: + case Configuration.COLOR_MODE_HDR_NO: parts.add("lowdr"); break; default: break; } - switch (config.colorimetry & Configuration.COLORIMETRY_WIDE_COLOR_GAMUT_MASK) { - case Configuration.COLORIMETRY_WIDE_COLOR_GAMUT_YES: + switch (config.colorMode & Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_MASK) { + case Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES: parts.add("widecg"); break; - case Configuration.COLORIMETRY_WIDE_COLOR_GAMUT_NO: + case Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO: parts.add("nowidecg"); break; default: @@ -2154,14 +2154,14 @@ public final class Configuration implements Parcelable, Comparable<Configuration delta.screenLayout |= change.screenLayout & SCREENLAYOUT_ROUND_MASK; } - if ((base.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK) != - (change.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK)) { - delta.colorimetry |= change.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK; + if ((base.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) != + (change.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK)) { + delta.colorMode |= change.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK; } - if ((base.colorimetry & COLORIMETRY_HDR_MASK) != - (change.colorimetry & COLORIMETRY_HDR_MASK)) { - delta.colorimetry |= change.colorimetry & COLORIMETRY_HDR_MASK; + if ((base.colorMode & COLOR_MODE_HDR_MASK) != + (change.colorMode & COLOR_MODE_HDR_MASK)) { + delta.colorMode |= change.colorMode & COLOR_MODE_HDR_MASK; } if ((base.uiMode & UI_MODE_TYPE_MASK) != (change.uiMode & UI_MODE_TYPE_MASK)) { @@ -2206,7 +2206,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration private static final String XML_ATTR_NAVIGATION_HIDDEN = "navHid"; private static final String XML_ATTR_ORIENTATION = "ori"; private static final String XML_ATTR_SCREEN_LAYOUT = "scrLay"; - private static final String XML_ATTR_COLORIMETRY = "clrMtry"; + private static final String XML_ATTR_COLOR_MODE = "clrMod"; private static final String XML_ATTR_UI_MODE = "ui"; private static final String XML_ATTR_SCREEN_WIDTH = "width"; private static final String XML_ATTR_SCREEN_HEIGHT = "height"; @@ -2249,8 +2249,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration ORIENTATION_UNDEFINED); configOut.screenLayout = XmlUtils.readIntAttribute(parser, XML_ATTR_SCREEN_LAYOUT, SCREENLAYOUT_UNDEFINED); - configOut.colorimetry = XmlUtils.readIntAttribute(parser, XML_ATTR_COLORIMETRY, - COLORIMETRY_UNDEFINED); + configOut.colorMode = XmlUtils.readIntAttribute(parser, XML_ATTR_COLOR_MODE, + COLOR_MODE_UNDEFINED); configOut.uiMode = XmlUtils.readIntAttribute(parser, XML_ATTR_UI_MODE, 0); configOut.screenWidthDp = XmlUtils.readIntAttribute(parser, XML_ATTR_SCREEN_WIDTH, SCREEN_WIDTH_DP_UNDEFINED); @@ -2313,8 +2313,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration if (config.screenLayout != SCREENLAYOUT_UNDEFINED) { XmlUtils.writeIntAttribute(xml, XML_ATTR_SCREEN_LAYOUT, config.screenLayout); } - if (config.colorimetry != COLORIMETRY_UNDEFINED) { - XmlUtils.writeIntAttribute(xml, XML_ATTR_COLORIMETRY, config.colorimetry); + if (config.colorMode != COLOR_MODE_UNDEFINED) { + XmlUtils.writeIntAttribute(xml, XML_ATTR_COLOR_MODE, config.colorMode); } if (config.uiMode != 0) { XmlUtils.writeIntAttribute(xml, XML_ATTR_UI_MODE, config.uiMode); diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index 06ab13e9c0a5..d87c55e1c7c7 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -729,14 +729,22 @@ public final class Sensor { private static final int DATA_INJECTION_MASK = 0x10; private static final int DATA_INJECTION_SHIFT = 4; - // MASK for dynamic sensor (sensor that added during runtime), bit 6. + // MASK for dynamic sensor (sensor that added during runtime), bit 5. private static final int DYNAMIC_SENSOR_MASK = 0x20; private static final int DYNAMIC_SENSOR_SHIFT = 5; - // MASK for indication bit of sensor additional information support (bit 7). + // MASK for indication bit of sensor additional information support, bit 6. private static final int ADDITIONAL_INFO_MASK = 0x40; private static final int ADDITIONAL_INFO_SHIFT = 6; + // Mask for direct mode highest rate level, bit 7, 8, 9. + private static final int DIRECT_REPORT_MASK = 0x380; + private static final int DIRECT_REPORT_SHIFT = 7; + + // Mask for supported direct channel, bit 10, 11 + private static final int DIRECT_CHANNEL_MASK = 0xC00; + private static final int DIRECT_CHANNEL_SHIFT = 10; + // TODO(): The following arrays are fragile and error-prone. This needs to be refactored. // Note: This needs to be updated, whenever a new sensor is added. @@ -796,6 +804,42 @@ public final class Sensor { return ((mFlags & REPORTING_MODE_MASK) >> REPORTING_MODE_SHIFT); } + /** + * Get the highest supported direct report mode rate level of the sensor. + * + * @return Highest direct report rate level of this sensor. If the sensor does not support + * direct report mode, this returns {@link SensorDirectChannel#RATE_STOP}. + * @see SensorDirectChannel#RATE_STOP + * @see SensorDirectChannel#RATE_NORMAL + * @see SensorDirectChannel#RATE_FAST + * @see SensorDirectChannel#RATE_VERY_FAST + */ + @SensorDirectChannel.RateLevel + public int getHighestDirectReportRateLevel() { + int rateLevel = ((mFlags & DIRECT_REPORT_MASK) >> DIRECT_REPORT_SHIFT); + return rateLevel <= SensorDirectChannel.RATE_VERY_FAST + ? rateLevel : SensorDirectChannel.RATE_VERY_FAST; + } + + /** + * Test if sensor support direct channel backed by a specific type of shared memory. + * + * @param sharedMemType type of shared memory used by direct channel. + * @return <code>true</code> if the shared memory type is supported. + * @see SensorDirectChannel#TYPE_ASHMEM + * @see SensorDirectChannel#TYPE_HARDWARE_BUFFER + */ + public boolean isDirectChannelTypeSupported(@SensorDirectChannel.MemoryType int sharedMemType) { + switch (sharedMemType) { + case SensorDirectChannel.TYPE_ASHMEM: + return (mFlags & (1 << DIRECT_CHANNEL_SHIFT)) > 0; + case SensorDirectChannel.TYPE_HARDWARE_BUFFER: + return (mFlags & (1 << DIRECT_CHANNEL_SHIFT + 1)) > 0; + default: + return false; + } + } + static int getMaxLengthValuesArray(Sensor sensor, int sdkLevel) { // RotationVector length has changed to 3 to 5 for API level 18 // Set it to 3 for backward compatibility. diff --git a/core/java/android/hardware/SensorDirectChannel.java b/core/java/android/hardware/SensorDirectChannel.java new file mode 100644 index 000000000000..0efd62b463f8 --- /dev/null +++ b/core/java/android/hardware/SensorDirectChannel.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware; + +import android.annotation.IntDef; +import android.os.MemoryFile; + +import dalvik.system.CloseGuard; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Class representing a sensor direct channel. Use {@link + * SensorManager#createDirectChannel(android.os.MemoryFile)} to obtain object. + */ +public final class SensorDirectChannel implements AutoCloseable { + + // shared memory types + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {TYPE_ASHMEM, TYPE_HARDWARE_BUFFER}) + public @interface MemoryType {}; + /** + * Shared memory type ashmem, wrapped in MemoryFile object. + * + * @see SensorManager#createDirectChannel(MemoryFile) + */ + public static final int TYPE_ASHMEM = 1; + + /** + * Shared memory type wrapped by HardwareBuffer object. + * + * @see SensorManager#createDirectChannel(HardwareBuffer) + */ + public static final int TYPE_HARDWARE_BUFFER = 2; + + // sensor rate levels + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {RATE_STOP, RATE_NORMAL, RATE_FAST, RATE_VERY_FAST}) + public @interface RateLevel {}; + + /** + * Sensor stopped (no event output). + * + * @see SensorManager#configureDirectChannel(SensorDirectChannel, Sensor, int) + */ + public static final int RATE_STOP = 0; + /** + * Sensor operates at nominal rate of 50Hz. + * + * The actual rate is expected to be between 55% to 220% of nominal rate, thus between 27.5Hz to + * 110Hz. + * + * @see SensorManager#configureDirectChannel(SensorDirectChannel, Sensor, int) + */ + public static final int RATE_NORMAL = 1; //50Hz + /** + * Sensor operates at nominal rate of 200Hz. + * + * The actual rate is expected to be between 55% to 220% of nominal rate, thus between 110Hz to + * 440Hz. + * + * @see SensorManager#configureDirectChannel(SensorDirectChannel, Sensor, int) + */ + public static final int RATE_FAST = 2; // ~200Hz + /** + * Sensor operates at nominal rate of 800Hz. + * + * The actual rate is expected to be between 55% to 220% of nominal rate, thus between 440Hz to + * 1760Hz. + * + * @see SensorManager#configureDirectChannel(SensorDirectChannel, Sensor, int) + */ + public static final int RATE_VERY_FAST = 3; // ~800Hz + + /** + * Determine if a channel is still valid. A channel is invalidated after {@link #close()} is + * called. + * + * @return <code>true</code> if channel is valid. + */ + public boolean isValid() { + return !mClosed.get(); + } + + /** + * Close sensor direct channel. + * + * Stop all active sensor in the channel and free sensor system resource related to channel. + * Shared memory used for creating the direct channel need to be closed or freed separately. + * + * @see SensorManager#createDirectChannel(MemoryFile) + * @see SensorManager#createDirectChannel(HardwareBuffer) + */ + @Override + public void close() { + mCloseGuard.close(); + if (mClosed.compareAndSet(false, true)) { + // actual close action + mManager.destroyDirectChannel(this); + } + } + + /** @hide */ + SensorDirectChannel(SensorManager manager, int id, int type, long size) { + mManager = manager; + mNativeHandle = id; + mType = type; + mSize = size; + mCloseGuard.open("SensorDirectChannel"); + } + + /** @hide */ + int getNativeHandle() { + return mNativeHandle; + } + + /** + * This function encode handle information in {@link android.os.Memory} into a long array to be + * passed down to native methods. + * + * @hide */ + static long[] encodeData(MemoryFile ashmem) { + int fd; + try { + fd = ashmem.getFileDescriptor().getInt$(); + } catch (IOException e) { + fd = -1; + } + return new long[] { 1 /*numFds*/, 0 /*numInts*/, fd }; + } + + @Override + protected void finalize() throws Throwable { + try { + mCloseGuard.warnIfOpen(); + close(); + } finally { + super.finalize(); + } + } + + private final AtomicBoolean mClosed = new AtomicBoolean(); + private final CloseGuard mCloseGuard = CloseGuard.get(); + private final SensorManager mManager; + private final int mNativeHandle; + private final long mSize; + private final int mType; +} diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java index 3ccac692be91..cfda2f4fa8b4 100644 --- a/core/java/android/hardware/SensorManager.java +++ b/core/java/android/hardware/SensorManager.java @@ -19,6 +19,7 @@ package android.hardware; import android.annotation.SystemApi; import android.os.Build; import android.os.Handler; +import android.os.MemoryFile; import android.util.Log; import android.util.SparseArray; @@ -881,6 +882,104 @@ public abstract class SensorManager { /** + * Create a sensor direct channel backed by shared memory wrapped by MemoryFile object. + * + * Use the returned {@link android.hardware.SensorDirectChannel} object to configure direct + * report of sensor events. After use, call {@link android.hardware.SensorDirectChannel#close()} + * to free up resource in sensor system associated with the direct channel. + * + * @param mem A {@link android.os.MemoryFile} shared memory object. + * @return A {@link android.hardware.SensorDirectChannel} object if successful, null otherwise. + * @throws IllegalArgumentException when mem is null. + * @see SensorDirectChannel#close() + * @see #configureDirectChannel(SensorDirectChannel, Sensor, int) + */ + public SensorDirectChannel createDirectChannel(MemoryFile mem) { + return createDirectChannelImpl(mem.length(), mem, null); + } + + /** + * Create a sensor direct channel backed by shared memory wrapped by HardwareBuffer object. + * + * Use the returned {@link android.hardware.SensorDirectChannel} object to configure direct + * report of sensor events. After use, call {@link android.hardware.SensorDirectChannel#close()} + * to free up resource in sensor system associated with the direct channel. + * + * @param mem A {@link android.hardware.HardwareBuffer} shared memory object. + * @return A {@link android.hardware.SensorDirectChannel} object if successful, + * null otherwise. + * @throws IllegalArgumentException when mem is null. + * @see SensorDirectChannel#close() + * @see #configureDirectChannel(SensorDirectChannel, Sensor, int) + */ + public SensorDirectChannel createDirectChannel(HardwareBuffer mem) { + return null; + } + + /** @hide */ + protected abstract SensorDirectChannel createDirectChannelImpl(long size, + MemoryFile ashmemFile, HardwareBuffer hardwareBuffer); + + /** @hide */ + void destroyDirectChannel(SensorDirectChannel channel) { + destroyDirectChannelImpl(channel); + } + + /** @hide */ + protected abstract void destroyDirectChannelImpl(SensorDirectChannel channel); + + /** + * Configure sensor rate or stop sensor report on a direct report channel specified. + * + * To start event report of a sensor, or change rate of existing report, call this function with + * rateLevel other than {@link android.hardware.SensorDirectChannel#RATE_STOP}. Sensor events + * will be added into a queue formed by the shared memory used in creation of direction channel. + * Each element of the queue has size of 104 bytes and represents a sensor event. Data + * structure of an element (all fields in little-endian): + * + * offset type name + *- --------------------------------------------- + * 0x0000 int32_t size (always 104) + * 0x0004 int32_t sensor report token + * 0x0008 int32_t type (see SensorType) + * 0x000C uint32_t atomic counter + * 0x0010 int64_t timestamp (see Event) + * 0x0018 float[16]/int64_t[8] data (data type depends on sensor type) + * 0x0058 int32_t[4] reserved (set to zero) + * + * There is no head or tail pointers. The sequence and frontier of new sensor events is + * determined by the atomic counter, which counts from 1 after creation of direct channel and + * increments 1 for each new event. The writer in sensor system will wrap around from to + * start of shared memory region when it reaches the end. If size of memory region is not + * a multiple of size of element (104 bytes), the residual is not used at the end. + * Function returns a positive sensor report token on success. This token can be used for + * differentiate sensor events from multiple sensor of the same type. For example, if there are + * two accelerometer in the system A and B, it is guaranteed different report tokens will be + * returned when starting sensor A and B. + * + * To stop a sensor, call this function with rateLevel equal {@link + * android.hardware.SensorDirectChannel#RATE_STOP}. If the sensor parameter is left to be null, + * this will stop all active sensor report associated with the direct channel specified. + * Function return 1 on success or 0 on failure. + * + * @param channel A {@link android.hardware.SensorDirectChannel} object representing direct + * channel to be configured. + * @param sensor A {@link android.hardware.Sensor} object to denote sensor to be operated. + * @param rateLevel rate level defined in {@link android.hardware.SensorDirectChannel}. + * @return starting report or changing rate: positive sensor report token on success, 0 on failure; + * stopping report: 1 on success, 0 on failure. + * @throws IllegalArgumentException when SensorDirectChannel is null. + */ + public int configureDirectChannel(SensorDirectChannel channel, Sensor sensor, + @SensorDirectChannel.RateLevel int rateLevel) { + return configureDirectChannelImpl(channel, sensor, rateLevel); + } + + /** @hide */ + protected abstract int configureDirectChannelImpl( + SensorDirectChannel channel, Sensor s, int rate); + + /** * Used for receiving notifications from the SensorManager when dynamic sensors are connected or * disconnected. */ diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index 259ca032918b..4992def7de15 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -24,6 +24,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.os.Handler; import android.os.Looper; +import android.os.MemoryFile; import android.os.MessageQueue; import android.util.Log; import android.util.SparseArray; @@ -57,6 +58,13 @@ public class SystemSensorManager extends SensorManager { private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list); private static native boolean nativeIsDataInjectionEnabled(long nativeInstance); + private static native int nativeCreateDirectChannel( + long nativeInstance, long size, int channelType, long [] channelData); + private static native void nativeDestroyDirectChannel( + long nativeInstance, int channelHandle); + private static native int nativeConfigDirectChannel( + long nativeInstance, int channelHandle, int sensorHandle, int rate); + private static final Object sLock = new Object(); @GuardedBy("sLock") private static boolean sNativeClassInited = false; @@ -484,6 +492,71 @@ public class SystemSensorManager extends SensorManager { return changed; } + /** @hide */ + protected int configureDirectChannelImpl( + SensorDirectChannel channel, Sensor sensor, int rate) { + if (channel == null) throw new IllegalArgumentException("channel cannot be null"); + + if (!channel.isValid()) { + throw new IllegalStateException("channel is invalid"); + } + + if (rate < SensorDirectChannel.RATE_STOP + || rate > SensorDirectChannel.RATE_VERY_FAST) { + throw new IllegalArgumentException("rate parameter invalid"); + } + + if (sensor == null && rate != SensorDirectChannel.RATE_STOP) { + // the stop all sensors case + throw new IllegalArgumentException( + "when sensor is null, rate can only be DIRECT_RATE_STOP"); + } + + int sensorHandle = (sensor == null) ? -1 : sensor.getHandle(); + + int ret = nativeConfigDirectChannel( + mNativeInstance, channel.getNativeHandle(), sensorHandle, rate); + + if (rate == SensorDirectChannel.RATE_STOP) { + return (ret == 0) ? 1 : 0; + } else { + return (ret > 0) ? ret : 0; + } + } + + /** @hide */ + protected SensorDirectChannel createDirectChannelImpl(long size, + MemoryFile ashmemFile, HardwareBuffer grallocMemObject) { + SensorDirectChannel ch = null; + + if (size <= 0) throw new IllegalArgumentException("size has to be greater than 0"); + + if (ashmemFile != null) { + if (size != ashmemFile.length()) { + throw new IllegalArgumentException("size has to match MemoryFile.length()"); + } + int id = nativeCreateDirectChannel( + mNativeInstance, size, SensorDirectChannel.TYPE_ASHMEM, + SensorDirectChannel.encodeData(ashmemFile)); + if (id > 0) { + ch = new SensorDirectChannel(this, id, SensorDirectChannel.TYPE_ASHMEM, size); + } + } else if (grallocMemObject != null) { + Log.wtf(TAG, "Implement GRALLOC or remove GRALLOC support entirely"); + } else { + throw new IllegalArgumentException("Invalid parameter"); + } + + return ch; + } + + /** @hide */ + protected void destroyDirectChannelImpl(SensorDirectChannel channel) { + if (channel != null) { + nativeDestroyDirectChannel(mNativeInstance, channel.getNativeHandle()); + } + } + /* * BaseEventQueue is the communication channel with the sensor service, * SensorEventQueue, TriggerEventQueue are subclases and there is one-to-one mapping between diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index 62b7f32c43c0..bcebb7dc594d 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -221,8 +221,8 @@ public abstract class CameraCaptureSession implements AutoCloseable { public abstract void tearDown(@NonNull Surface surface) throws CameraAccessException; /** - * <p>Finish the deferred output configurations where the output Surface was not configured - * before.</p> + * <p>Finalize the output configurations that now have their deferred and/or extra Surfaces + * included.</p> * * <p>For camera use cases where a preview and other output configurations need to be * configured, it can take some time for the preview Surface to be ready. For example, if the @@ -235,22 +235,31 @@ public abstract class CameraCaptureSession implements AutoCloseable { * and defer the preview output configuration until the Surface is ready. After the * {@link CameraCaptureSession} is created successfully with this deferred output and other * normal outputs, the application can start submitting requests as long as they do not include - * deferred output Surfaces. Once a deferred Surface is ready, the application can set the - * Surface on the deferred output configuration with the - * {@link OutputConfiguration#setDeferredSurface} method, and then finish the deferred output + * deferred output Surfaces. Once a deferred Surface is ready, the application can add the + * Surface to the deferred output configuration with the + * {@link OutputConfiguration#addSurface} method, and then update the deferred output * configuration via this method, before it can submit capture requests with this output * target.</p> * - * <p>The output Surfaces included by this list of deferred + * <p>This function can also be called in case where multiple surfaces share the same + * OutputConfiguration, and one of the surfaces becomes available after the {@link + * CameraCaptureSession} is created. In that case, the application must first create the + * OutputConfiguration with the available Surface, then enable furture surface sharing via + * {@link OutputConfiguration#enableSurfaceSharing}, before creating the CameraCaptureSession. + * After the CameraCaptureSession is created, and once the extra Surface becomes available, the + * application must then call {@link OutputConfiguration#addSurface} before finalizing the + * configuration with this method.</p> + * + * <p>The output Surfaces included by this list of * {@link OutputConfiguration OutputConfigurations} can be used as {@link CaptureRequest} * targets as soon as this call returns.</p> * * <p>This method is not supported by * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY}-level devices.</p> * - * @param deferredOutputConfigs a list of {@link OutputConfiguration OutputConfigurations} that - * have had {@link OutputConfiguration#setDeferredSurface setDeferredSurface} invoked - * with a valid output Surface. + * @param outputConfigs a list of {@link OutputConfiguration OutputConfigurations} that + * have had {@link OutputConfiguration#addSurface addSurface} invoked with a valid + * output Surface after {@link CameraDevice#createCaptureSessionByOutputConfigurations}. * @throws CameraAccessException if the camera device is no longer connected or has encountered * a fatal error. * @throws IllegalStateException if this session is no longer active, either because the session @@ -261,8 +270,8 @@ public abstract class CameraCaptureSession implements AutoCloseable { * source. Or if one of the output configuration was already finished with an * included surface in a prior call. */ - public abstract void finishDeferredConfiguration( - List<OutputConfiguration> deferredOutputConfigs) throws CameraAccessException; + public abstract void finalizeOutputConfigurations( + List<OutputConfiguration> outputConfigs) throws CameraAccessException; /** * <p>Submit a request for an image to be captured by the camera device.</p> diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index 4befb29ae075..891df6362d29 100644 --- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -151,9 +151,9 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession } @Override - public void finishDeferredConfiguration( - List<OutputConfiguration> deferredOutputConfigs) throws CameraAccessException { - mDeviceImpl.finishDeferredConfig(deferredOutputConfigs); + public void finalizeOutputConfigurations( + List<OutputConfiguration> outputConfigs) throws CameraAccessException { + mDeviceImpl.finalizeOutputConfigs(outputConfigs); } @Override diff --git a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java index 01e58f450a4d..15dbf2620608 100644 --- a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java @@ -258,9 +258,9 @@ public class CameraConstrainedHighSpeedCaptureSessionImpl } @Override - public void finishDeferredConfiguration(List<OutputConfiguration> deferredOutputConfigs) + public void finalizeOutputConfigurations(List<OutputConfiguration> deferredOutputConfigs) throws CameraAccessException { - mSessionImpl.finishDeferredConfiguration(deferredOutputConfigs); + mSessionImpl.finalizeOutputConfigurations(deferredOutputConfigs); } private class WrapperCallback extends StateCallback { diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index d2aeaea2b37d..2364ebe0f891 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -743,14 +743,14 @@ public class CameraDeviceImpl extends CameraDevice } } - public void finishDeferredConfig(List<OutputConfiguration> deferredConfigs) + public void finalizeOutputConfigs(List<OutputConfiguration> outputConfigs) throws CameraAccessException { - if (deferredConfigs == null || deferredConfigs.size() == 0) { + if (outputConfigs == null || outputConfigs.size() == 0) { throw new IllegalArgumentException("deferred config is null or empty"); } synchronized(mInterfaceLock) { - for (OutputConfiguration config : deferredConfigs) { + for (OutputConfiguration config : outputConfigs) { int streamId = -1; for (int i = 0; i < mConfiguredOutputs.size(); i++) { // Have to use equal here, as createCaptureSessionByOutputConfigurations() and @@ -765,11 +765,11 @@ public class CameraDeviceImpl extends CameraDevice + "session"); } - if (config.getSurface() == null) { - throw new IllegalArgumentException("The deferred config for stream " + streamId - + " must have a non-null surface"); + if (config.getSurfaces().size() == 0) { + throw new IllegalArgumentException("The final config for stream " + streamId + + " must have at least 1 surface"); } - mRemoteDevice.setDeferredConfiguration(streamId, config); + mRemoteDevice.finalizeOutputConfigurations(streamId, config); } } } diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java index d77f60bf0953..d9f666e54330 100644 --- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java +++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java @@ -215,10 +215,10 @@ public class ICameraDeviceUserWrapper { } } - public void setDeferredConfiguration(int streamId, OutputConfiguration deferredConfig) + public void finalizeOutputConfigurations(int streamId, OutputConfiguration deferredConfig) throws CameraAccessException { try { - mRemoteDevice.setDeferredConfiguration(streamId, deferredConfig); + mRemoteDevice.finalizeOutputConfigurations(streamId, deferredConfig); } catch (Throwable t) { CameraManager.throwAsPublicException(t); throw new UnsupportedOperationException("Unexpected exception", t); diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java index 2a9bf6bace95..d8ec4df504bb 100644 --- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java +++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java @@ -578,8 +578,8 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { } @Override - public void setDeferredConfiguration(int steamId, OutputConfiguration config) { - String err = "Set deferred configuration is not supported on legacy devices"; + public void finalizeOutputConfigurations(int steamId, OutputConfiguration config) { + String err = "Finalizing output configuration is not supported on legacy devices"; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); } diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index 4654fc2b2524..2d5c4ce1f16d 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -34,6 +34,7 @@ import android.view.Surface; import java.util.Arrays; import java.util.List; import java.util.Collections; +import java.util.ArrayList; import static com.android.internal.util.Preconditions.*; @@ -116,6 +117,15 @@ public final class OutputConfiguration implements Parcelable { private final int SURFACE_TYPE_SURFACE_TEXTURE = 1; /** + * Maximum number of surfaces supported by one {@link OutputConfiguration}. + * + * <p>The combined number of surfaces added by the constructor and + * {@link OutputConfiguration#addSurface} should not exceed this value.</p> + * + */ + private static final int MAX_SURFACES_COUNT = 2; + + /** * Create a new {@link OutputConfiguration} instance with a {@link Surface}, * with a surface group ID. * @@ -151,50 +161,6 @@ public final class OutputConfiguration implements Parcelable { } /** - * Create a new {@link OutputConfiguration} instance with two surfaces sharing the same stream, - * with a surface group ID. - * - * <p>For advanced use cases, a camera application may require more streams than the combination - * guaranteed by {@link CameraDevice#createCaptureSession}. In this case, two compatible - * surfaces can be attached to one OutputConfiguration so that they map to one camera stream, - * and buffers are reference counted when being consumed by both surfaces. </p> - * - * <p>Two surfaces are compatible in below 2 cases:</p> - * - * <ol> - * <li> Surfaces with the same size, format, dataSpace, and Surface source class. In this case, - * {@link CameraDevice#createCaptureSessionByOutputConfigurations} is guaranteed to succeed. - * - * <li> Surfaces with the same size, format, and dataSpace, but different Surface - * source classes. However, on some devices, the underlying camera device is able to use the - * same buffer layout for both surfaces. The only way to discover if this is the case is to - * create a capture session with that output configuration. For example, if the camera device - * uses the same private buffer format between a SurfaceView/SurfaceTexture and a - * MediaRecorder/MediaCodec, {@link CameraDevice#createCaptureSessionByOutputConfigurations} - * will succeed. Otherwise, it throws {@code IllegalArgumentException}. - * </ol> - * - * @param surfaceGroupId - * A group ID for this output, used for sharing memory between multiple outputs. - * @param surface - * A Surface for camera to output to. - * @param surface2 - * Second surface for camera to output to. - * @throws IllegalArgumentException if the two surfaces have different size, format, or - * dataSpace. - * - * @hide - */ - public OutputConfiguration(int surfaceGroupId, @NonNull Surface surface, - @NonNull Surface surface2) { - this(surfaceGroupId, surface, ROTATION_0, surface2); - - checkNotNull(surface2, "Surface must not be null"); - checkMatchingSurfaces(mConfiguredSize, mConfiguredFormat, mConfiguredDataspace, - mConfiguredGenerationId, surface2); - } - - /** * Create a new {@link OutputConfiguration} instance. * * <p>This constructor takes an argument for desired camera rotation</p> @@ -240,68 +206,19 @@ public final class OutputConfiguration implements Parcelable { */ @SystemApi public OutputConfiguration(int surfaceGroupId, @NonNull Surface surface, int rotation) { - this(surfaceGroupId, surface, rotation, null /*surface2*/); - } - - /** - * Create a new {@link OutputConfiguration} instance, with rotation, a group ID, and a secondary - * surface. - * - * <p>This constructor takes an argument for desired camera rotation, the surface group - * ID, and a secondary surface. See {@link #OutputConfiguration(int, Surface)} for details - * of the group ID.</p> - * - * <p>surface2 should be compatible with surface. See {@link #OutputConfiguration(int, Surface, - * Surface} for details of compatibility between surfaces.</p> - * - * <p>Since the rotation is done by the CameraDevice, both surfaces will receive buffers with - * the same rotation applied. This means that if the application needs two compatible surfaces - * to have different rotations, these surfaces cannot be shared within one OutputConfiguration. - * </p> - * - * @param surfaceGroupId - * A group ID for this output, used for sharing memory between multiple outputs. - * @param surface - * A Surface for camera to output to. - * @param rotation - * The desired rotation to be applied on camera output. Value must be one of - * ROTATION_[0, 90, 180, 270]. Note that when the rotation is 90 or 270 degrees, - * application should make sure corresponding surface size has width and height - * transposed relative to the width and height without rotation. For example, - * if application needs camera to capture 1280x720 picture and rotate it by 90 degree, - * application should set rotation to {@code ROTATION_90} and make sure the - * corresponding Surface size is 720x1280. Note that {@link CameraDevice} might - * throw {@code IllegalArgumentException} if device cannot perform such rotation. - * @param surface2 - * Second surface for camera to output to. - - * @throws IllegalArgumentException if the two surfaces are not compatible to be shared in - * one OutputConfiguration. - * - * @hide - */ - private OutputConfiguration(int surfaceGroupId, @NonNull Surface surface, int rotation, - @Nullable Surface surface2) { checkNotNull(surface, "Surface must not be null"); checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant"); - mSurfaceGroupId = surfaceGroupId; mSurfaceType = SURFACE_TYPE_UNKNOWN; + mSurfaces = new ArrayList<Surface>(); + mSurfaces.add(surface); mRotation = rotation; mConfiguredSize = SurfaceUtils.getSurfaceSize(surface); mConfiguredFormat = SurfaceUtils.getSurfaceFormat(surface); mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(surface); mConfiguredGenerationId = surface.getGenerationId(); mIsDeferredConfig = false; - - if (surface2 == null) { - mSurfaces = new Surface[1]; - mSurfaces[0] = surface; - } else { - mSurfaces = new Surface[MAX_SURFACES_COUNT]; - mSurfaces[0] = surface; - mSurfaces[1] = surface2; - } + mIsShared = false; } /** @@ -309,16 +226,16 @@ public final class OutputConfiguration implements Parcelable { * source class. * <p> * This constructor takes an argument for desired Surface size and the Surface source class - * without providing the actual output Surface. This is used to setup a output configuration + * without providing the actual output Surface. This is used to setup an output configuration * with a deferred Surface. The application can use this output configuration to create a * session. * </p> * <p> - * However, the actual output Surface must be set via {@link #setDeferredSurface} and finish the - * deferred Surface configuration via {@link CameraCaptureSession#finishDeferredConfiguration} - * before submitting a request with this Surface target. The deferred Surface can only be - * obtained from either from {@link android.view.SurfaceView} by calling - * {@link android.view.SurfaceHolder#getSurface}, or from + * However, the actual output Surface must be set via {@link #addSurface} and the deferred + * Surface configuration must be finalized via {@link + * CameraCaptureSession#finalizeOutputConfigurations} before submitting a request with this + * Surface target. The deferred Surface can only be obtained either from {@link + * android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface}, or from * {@link android.graphics.SurfaceTexture} via * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}). * </p> @@ -329,41 +246,68 @@ public final class OutputConfiguration implements Parcelable { * {@link android.graphics.SurfaceTexture SurfaceTexture.class} are supported. */ public <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass) { - this(surfaceSize, klass, true /* dummy */); + checkNotNull(klass, "surfaceSize must not be null"); + checkNotNull(klass, "klass must not be null"); + if (klass == android.view.SurfaceHolder.class) { + mSurfaceType = SURFACE_TYPE_SURFACE_VIEW; + } else if (klass == android.graphics.SurfaceTexture.class) { + mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE; + } else { + mSurfaceType = SURFACE_TYPE_UNKNOWN; + throw new IllegalArgumentException("Unknow surface source class type"); + } - mSurfaces = new Surface[1]; + mSurfaceGroupId = SURFACE_GROUP_ID_NONE; + mSurfaces = new ArrayList<Surface>(); + mRotation = ROTATION_0; + mConfiguredSize = surfaceSize; + mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE); + mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE); + mConfiguredGenerationId = 0; + mIsDeferredConfig = true; + mIsShared = false; } /** - * Create a new {@link OutputConfiguration} instance, with desired Surface size and Surface - * source class for the deferred surface, and a secondary surface. + * Enable multiple surfaces sharing the same OutputConfiguration * - * <p>This constructor takes an argument for desired surface size and surface source class of - * the deferred surface, and a secondary surface. See {@link #OutputConfiguration(Size, Class)} - * for details of the surface size and surface source class.</p> + * <p>For advanced use cases, a camera application may require more streams than the combination + * guaranteed by {@link CameraDevice#createCaptureSession}. In this case, more than one + * compatible surface can be attached to an OutputConfiguration so that they map to one + * camera stream, and the outputs share memory buffers when possible. </p> * - * <p> The deferred surface and secondary surface should be compatible. See - * {@link #OutputConfiguration(int, Surface, Surface)} for details of compatible surfaces. + * <p>Two surfaces are compatible in the below cases:</p> * - * @hide + * <li> Surfaces with the same size, format, dataSpace, and Surface source class. In this case, + * {@link CameraDevice#createCaptureSessionByOutputConfigurations} is guaranteed to succeed. + * + * <li> Surfaces with the same size, format, and dataSpace, but different Surface source classes + * that are generally not compatible. However, on some devices, the underlying camera device is + * able to use the same buffer layout for both surfaces. The only way to discover if this is the + * case is to create a capture session with that output configuration. For example, if the + * camera device uses the same private buffer format between a SurfaceView/SurfaceTexture and a + * MediaRecorder/MediaCodec, {@link CameraDevice#createCaptureSessionByOutputConfigurations} + * will succeed. Otherwise, it throws {@code IllegalArgumentException}. + * </ol> + * + * <p>To enable surface sharing, this function must be called before {@link + * CameraDevice#createCaptureSessionByOutputConfigurations}. Calling this function after {@link + * CameraDevice#createCaptureSessionByOutputConfigurations} has no effect.</p> + * + * <p>Up to 2 surfaces can be shared for an OutputConfiguration. The supported surfaces for + * sharing must be of type SurfaceTexture, SurfaceView, MediaRecorder, MediaCodec, or + * implementation defined ImageReader.</p> */ - public <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass, - @NonNull Surface surface2) { - this(surfaceSize, klass, true /* dummy */); - - checkMatchingSurfaces(mConfiguredSize, mConfiguredFormat, mConfiguredDataspace, - mConfiguredGenerationId, surface2); - - mSurfaces = new Surface[MAX_SURFACES_COUNT]; - mSurfaces[0] = null; - mSurfaces[1] = surface2; + public void enableSurfaceSharing() { + mIsShared = true; } /** * Check if this configuration has deferred configuration. * - * <p>This will return true if the output configuration was constructed with surface deferred. - * It will return true even after the deferred surface is set later.</p> + * <p>This will return true if the output configuration was constructed with surface deferred by + * {@link OutputConfiguration#OutputConfiguration(Size, Class)}. It will return true even after + * the deferred surface is added later by {@link OutputConfiguration#addSurface}.</p> * * @return true if this configuration has deferred surface. * @hide @@ -373,38 +317,58 @@ public final class OutputConfiguration implements Parcelable { } /** - * Set the deferred surface to this OutputConfiguration. + * Add a surface to this OutputConfiguration. * - * <p> - * The deferred surface must be obtained from either from {@link android.view.SurfaceView} by - * calling {@link android.view.SurfaceHolder#getSurface}, or from - * {@link android.graphics.SurfaceTexture} via - * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}). After the deferred - * surface is set, the application must finish the deferred surface configuration via - * {@link CameraCaptureSession#finishDeferredConfiguration} before submitting a request with - * this surface target. + * <p> This function can be called before or after {@link + * CameraDevice#createCaptureSessionByOutputConfigurations}. If it's called after, + * the application must finalize the capture session with + * {@link CameraCaptureSession#finalizeOutputConfigurations}. * </p> * - * @param surface The deferred surface to be set. - * @throws IllegalArgumentException if the Surface is invalid. - * @throws IllegalStateException if a Surface was already set to this deferred - * OutputConfiguration. + * <p> If the OutputConfiguration was constructed with a deferred surface by {@link + * OutputConfiguration#OutputConfiguration(Size, Class)}, the added surface must be obtained + * from {@link android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface}, + * or from {@link android.graphics.SurfaceTexture} via + * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}).</p> + * + * <p> If the OutputConfiguration was constructed by other constructors, the added + * surface must be compatible with the existing surface. See {@link #enableSurfaceSharing} for + * details of compatible surfaces.</p> + * + * <p> If the OutputConfiguration already contains a Surface, {@link #enableSurfaceSharing} must + * be called before calling this function to add a new Surface.</p> + * + * @param surface The surface to be added. + * @throws IllegalArgumentException if the Surface is invalid, the Surface's + * size/dataspace/format doesn't match, or adding the Surface would exceed number of + * shared surfaces supported. + * @throws IllegalStateException if the Surface was already added to this OutputConfiguration, + * or if the OutputConfiguration is not shared and it already has a surface associated + * with it. */ - public void setDeferredSurface(@NonNull Surface surface) { + public void addSurface(@NonNull Surface surface) { checkNotNull(surface, "Surface must not be null"); - if (mSurfaces[0] != null) { - throw new IllegalStateException("Deferred surface is already set!"); + if (mSurfaces.contains(surface)) { + throw new IllegalStateException("Surface is already added!"); + } + if (mSurfaces.size() == 1 && !mIsShared) { + throw new IllegalStateException("Cannot have 2 surfaces for a non-sharing configuration"); + } + if (mSurfaces.size() + 1 > MAX_SURFACES_COUNT) { + throw new IllegalArgumentException("Exceeds maximum number of surfaces"); } - // This will throw IAE is the surface was abandoned. - Size surfaceSize = SurfaceUtils.getSurfaceSize(surface); - if (!surfaceSize.equals(mConfiguredSize)) { - Log.w(TAG, "Deferred surface size " + surfaceSize + - " is different with pre-configured size " + mConfiguredSize + - ", the pre-configured size will be used."); + if (!mConfiguredSize.equals(SurfaceUtils.getSurfaceSize(surface))) { + throw new IllegalArgumentException("The size of added surface doesn't match"); + } + if (mConfiguredDataspace != SurfaceUtils.getSurfaceDataspace(surface)) { + throw new IllegalArgumentException("The dataspace of added surface doesn't match"); + } + if (mConfiguredFormat != SurfaceUtils.getSurfaceFormat(surface)) { + throw new IllegalArgumentException("The format of added surface format doesn't match"); } - mSurfaces[0] = surface; + mSurfaces.add(surface); } /** @@ -432,49 +396,6 @@ public final class OutputConfiguration implements Parcelable { } /** - * Private constructor to initialize Configuration based on surface size and class - */ - private <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass, - boolean dummy) { - checkNotNull(surfaceSize, "surfaceSize must not be null"); - checkNotNull(klass, "klass must not be null"); - if (klass == android.view.SurfaceHolder.class) { - mSurfaceType = SURFACE_TYPE_SURFACE_VIEW; - } else if (klass == android.graphics.SurfaceTexture.class) { - mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE; - } else { - mSurfaceType = SURFACE_TYPE_UNKNOWN; - throw new IllegalArgumentException("Unknow surface source class type"); - } - - mSurfaceGroupId = SURFACE_GROUP_ID_NONE; - mRotation = ROTATION_0; - mConfiguredSize = surfaceSize; - mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE); - mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE); - mConfiguredGenerationId = 0; - mIsDeferredConfig = true; - } - - /** - * Check if the surface properties match that of the given surface. - * - * @return true if the properties and the surface match. - */ - private void checkMatchingSurfaces(Size size, int format, int dataSpace, int generationId, - @NonNull Surface surface) { - if (!size.equals(SurfaceUtils.getSurfaceSize(surface))) { - throw new IllegalArgumentException("Secondary surface size doesn't match"); - } - if (dataSpace != SurfaceUtils.getSurfaceDataspace(surface)) { - throw new IllegalArgumentException("Secondary surface dataspace doesn't match"); - } - if (format != SurfaceUtils.getSurfaceFormat(surface)) { - throw new IllegalArgumentException("Secondary surface format doesn't match"); - } - } - - /** * Create an OutputConfiguration from Parcel. */ private OutputConfiguration(@NonNull Parcel source) { @@ -483,27 +404,9 @@ public final class OutputConfiguration implements Parcelable { int surfaceType = source.readInt(); int width = source.readInt(); int height = source.readInt(); - int surfaceCnt = source.readInt(); - - if (surfaceCnt <= 0) { - throw new IllegalArgumentException( - "Surface count in OutputConfiguration must be greater than 0"); - } - if (surfaceCnt > MAX_SURFACES_COUNT) { - throw new IllegalArgumentException( - "Surface count in OutputConfiguration must not be more than " - + MAX_SURFACES_COUNT); - } - - Surface[] surfaces = new Surface[surfaceCnt]; - for (int i = 0; i < surfaceCnt; i++) { - Surface surface = Surface.CREATOR.createFromParcel(source); - surfaces[i] = surface; - - if (surface == null && i > 0) { - throw new IllegalArgumentException("Only the first surface can be deferred"); - } - } + boolean isDeferred = source.readInt() == 1; + ArrayList<Surface> surfaces = new ArrayList<Surface>(); + source.readTypedList(surfaces, Surface.CREATOR); checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant"); @@ -511,13 +414,13 @@ public final class OutputConfiguration implements Parcelable { mRotation = rotation; mSurfaces = surfaces; mConfiguredSize = new Size(width, height); - // First surface could be null (being deferred). Use last surface to look up surface - // characteristics. - if (mSurfaces[surfaceCnt-1] != null) { + mIsDeferredConfig = isDeferred; + mSurfaces = surfaces; + if (mSurfaces.size() > 0) { mSurfaceType = SURFACE_TYPE_UNKNOWN; - mConfiguredFormat = SurfaceUtils.getSurfaceFormat(mSurfaces[surfaceCnt-1]); - mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(mSurfaces[surfaceCnt-1]); - mConfiguredGenerationId = mSurfaces[surfaceCnt-1].getGenerationId(); + mConfiguredFormat = SurfaceUtils.getSurfaceFormat(mSurfaces.get(0)); + mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(mSurfaces.get(0)); + mConfiguredGenerationId = mSurfaces.get(0).getGenerationId(); } else { mSurfaceType = surfaceType; mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE); @@ -525,38 +428,31 @@ public final class OutputConfiguration implements Parcelable { StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE); mConfiguredGenerationId = 0; } - - if (mSurfaces[0] == null) { - mIsDeferredConfig = true; - } else { - mIsDeferredConfig = false; - } } /** * Get the {@link Surface} associated with this {@link OutputConfiguration}. * - * @return the {@link Surface} associated with this {@link OutputConfiguration}. If more than - * one surface is associated with this {@link OutputConfiguration}, return the first one as - * specified in the constructor. If there is a deferred surface, null will be returned. + * If more than one surface is associated with this {@link OutputConfiguration}, return the + * first one as specified in the constructor or {@link OutputConfiguration#addSurface}. */ public @Nullable Surface getSurface() { - return mSurfaces[0]; + if (mSurfaces.size() == 0) { + return null; + } + + return mSurfaces.get(0); } /** * Get the immutable list of surfaces associated with this {@link OutputConfiguration}. * - * @return the list of surfaces associated with this {@link OutputConfiguration} in the order - * specified in the constructor. If there is a deferred surface in the {@link - * OutputConfiguration}, it is returned as null as first element of the list. The list should - * not be modified. - * - * @hide + * @return the list of surfaces associated with this {@link OutputConfiguration} as specified in + * the constructor and {@link OutputConfiguration#addSurface}. The list should not be modified. */ @NonNull public List<Surface> getSurfaces() { - return Collections.unmodifiableList(Arrays.asList(mSurfaces)); + return Collections.unmodifiableList(mSurfaces); } /** @@ -616,12 +512,9 @@ public final class OutputConfiguration implements Parcelable { dest.writeInt(mSurfaceType); dest.writeInt(mConfiguredSize.getWidth()); dest.writeInt(mConfiguredSize.getHeight()); - dest.writeInt(mSurfaces.length); - for (int i = 0; i < mSurfaces.length; i++) { - if (mSurfaces[i] != null) { - mSurfaces[i].writeToParcel(dest, flags); - } - } + dest.writeInt(mIsDeferredConfig ? 1 : 0); + dest.writeInt(mIsShared ? 1 : 0); + dest.writeTypedList(mSurfaces); } /** @@ -647,16 +540,15 @@ public final class OutputConfiguration implements Parcelable { mSurfaceGroupId != other.mSurfaceGroupId || mSurfaceType != other.mSurfaceType || mIsDeferredConfig != other.mIsDeferredConfig || + mIsShared != other.mIsShared || mConfiguredFormat != other.mConfiguredFormat || mConfiguredDataspace != other.mConfiguredDataspace || - mSurfaces.length != other.mSurfaces.length || mConfiguredGenerationId != other.mConfiguredGenerationId) return false; - // If deferred, skip the first surface of mSurfaces when comparing. - int minIndex = (mIsDeferredConfig ? 1 : 0); - for (int i = minIndex; i < mSurfaces.length; i++) { - if (mSurfaces[i] != other.mSurfaces[i]) + int minLen = Math.min(mSurfaces.size(), other.mSurfaces.size()); + for (int i = 0; i < minLen; i++) { + if (mSurfaces.get(i) != other.mSurfaces.get(i)) return false; } @@ -670,23 +562,23 @@ public final class OutputConfiguration implements Parcelable { */ @Override public int hashCode() { - // Need ensure that the hashcode remains unchanged after set a deferred surface. Otherwise + // Need ensure that the hashcode remains unchanged after adding a deferred surface. Otherwise // the deferred output configuration will be lost in the camera streammap after the deferred // surface is set. - int minIndex = (mIsDeferredConfig ? 1 : 0); - Surface nonDeferredSurfaces[] = Arrays.copyOfRange(mSurfaces, - minIndex, mSurfaces.length); - int surfaceHash = HashCodeHelpers.hashCodeGeneric(nonDeferredSurfaces); + if (mIsDeferredConfig) { + return HashCodeHelpers.hashCode( + mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, + mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0); + } return HashCodeHelpers.hashCode( - mRotation, surfaceHash, mConfiguredGenerationId, + mRotation, mSurfaces.hashCode(), mConfiguredGenerationId, mConfiguredSize.hashCode(), mConfiguredFormat, - mConfiguredDataspace, mSurfaceGroupId); + mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0); } private static final String TAG = "OutputConfiguration"; - private static final int MAX_SURFACES_COUNT = 2; - private Surface mSurfaces[]; + private ArrayList<Surface> mSurfaces; private final int mRotation; private final int mSurfaceGroupId; // Surface source type, this is only used by the deferred surface configuration objects. @@ -700,4 +592,6 @@ public final class OutputConfiguration implements Parcelable { private final int mConfiguredGenerationId; // Flag indicating if this config has deferred surface. private final boolean mIsDeferredConfig; + // Flag indicating if this config has shared surfaces + private boolean mIsShared; } diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java index c9a4e9b6015a..fea730e492c8 100644 --- a/core/java/android/hardware/usb/UsbPort.java +++ b/core/java/android/hardware/usb/UsbPort.java @@ -16,11 +16,12 @@ package android.hardware.usb; -import com.android.internal.util.Preconditions; - +import android.hardware.usb.V1_0.Constants; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.util.Preconditions; + /** * Represents a physical USB port and describes its characteristics. * <p> @@ -33,6 +34,7 @@ public final class UsbPort implements Parcelable { private final String mId; private final int mSupportedModes; + public static final int MODE_NONE = Constants.PortMode.NONE; /** * Mode bit: This USB port can act as a downstream facing port (host). * <p> @@ -40,7 +42,7 @@ public final class UsbPort implements Parcelable { * combination of roles (and possibly others as well). * </p> */ - public static final int MODE_DFP = 1 << 0; + public static final int MODE_DFP = Constants.PortMode.DFP; /** * Mode bit: This USB port can act as an upstream facing port (device). @@ -49,7 +51,7 @@ public final class UsbPort implements Parcelable { * combination of roles (and possibly others as well). * </p> */ - public static final int MODE_UFP = 1 << 1; + public static final int MODE_UFP = Constants.PortMode.UFP; /** * Mode bit: This USB port can act either as an downstream facing port (host) or as @@ -60,29 +62,43 @@ public final class UsbPort implements Parcelable { * combination of roles (and possibly others as well). * </p> */ - public static final int MODE_DUAL = MODE_DFP | MODE_UFP; + public static final int MODE_DUAL = Constants.PortMode.DRP; + + /** + * Power role: This USB port does not have a power role. + */ + public static final int POWER_ROLE_NONE = Constants.PortPowerRole.NONE; /** * Power role: This USB port can act as a source (provide power). */ - public static final int POWER_ROLE_SOURCE = 1; + public static final int POWER_ROLE_SOURCE = Constants.PortPowerRole.SOURCE; /** * Power role: This USB port can act as a sink (receive power). */ - public static final int POWER_ROLE_SINK = 2; + public static final int POWER_ROLE_SINK = Constants.PortPowerRole.SINK; + + /** + * Power role: This USB port does not have a data role. + */ + public static final int DATA_ROLE_NONE = Constants.PortDataRole.NONE; /** * Data role: This USB port can act as a host (access data services). */ - public static final int DATA_ROLE_HOST = 1; + public static final int DATA_ROLE_HOST = Constants.PortDataRole.HOST; /** * Data role: This USB port can act as a device (offer data services). */ - public static final int DATA_ROLE_DEVICE = 2; + public static final int DATA_ROLE_DEVICE = Constants.PortDataRole.DEVICE; - private static final int NUM_DATA_ROLES = 3; + private static final int NUM_DATA_ROLES = Constants.PortDataRole.NUM_DATA_ROLES; + /** + * Points to the first power role in the IUsb HAL. + */ + private static final int POWER_ROLE_OFFSET = Constants.PortPowerRole.NONE; /** @hide */ public UsbPort(String id, int supportedModes) { @@ -126,14 +142,14 @@ public final class UsbPort implements Parcelable { */ public static int combineRolesAsBit(int powerRole, int dataRole) { checkRoles(powerRole, dataRole); - final int index = powerRole * NUM_DATA_ROLES + dataRole; + final int index = ((powerRole - POWER_ROLE_OFFSET) * NUM_DATA_ROLES) + dataRole; return 1 << index; } /** @hide */ public static String modeToString(int mode) { switch (mode) { - case 0: + case MODE_NONE: return "none"; case MODE_DFP: return "dfp"; @@ -149,7 +165,7 @@ public final class UsbPort implements Parcelable { /** @hide */ public static String powerRoleToString(int role) { switch (role) { - case 0: + case POWER_ROLE_NONE: return "no-power"; case POWER_ROLE_SOURCE: return "source"; @@ -163,7 +179,7 @@ public final class UsbPort implements Parcelable { /** @hide */ public static String dataRoleToString(int role) { switch (role) { - case 0: + case DATA_ROLE_NONE: return "no-data"; case DATA_ROLE_HOST: return "host"; @@ -183,7 +199,7 @@ public final class UsbPort implements Parcelable { while (combo != 0) { final int index = Integer.numberOfTrailingZeros(combo); combo &= ~(1 << index); - final int powerRole = index / NUM_DATA_ROLES; + final int powerRole = (index / NUM_DATA_ROLES + POWER_ROLE_OFFSET); final int dataRole = index % NUM_DATA_ROLES; if (first) { first = false; @@ -200,9 +216,28 @@ public final class UsbPort implements Parcelable { } /** @hide */ + public static void checkMode(int powerRole) { + Preconditions.checkArgumentInRange(powerRole, Constants.PortMode.NONE, + Constants.PortMode.NUM_MODES - 1, "portMode"); + } + + /** @hide */ + public static void checkPowerRole(int dataRole) { + Preconditions.checkArgumentInRange(dataRole, Constants.PortPowerRole.NONE, + Constants.PortPowerRole.NUM_POWER_ROLES - 1, "powerRole"); + } + + /** @hide */ + public static void checkDataRole(int mode) { + Preconditions.checkArgumentInRange(mode, Constants.PortDataRole.NONE, + Constants.PortDataRole.NUM_DATA_ROLES - 1, "powerRole"); + } + + /** @hide */ public static void checkRoles(int powerRole, int dataRole) { - Preconditions.checkArgumentInRange(powerRole, 0, POWER_ROLE_SINK, "powerRole"); - Preconditions.checkArgumentInRange(dataRole, 0, DATA_ROLE_DEVICE, "dataRole"); + Preconditions.checkArgumentInRange(powerRole, POWER_ROLE_NONE, POWER_ROLE_SINK, + "powerRole"); + Preconditions.checkArgumentInRange(dataRole, DATA_ROLE_NONE, DATA_ROLE_DEVICE, "dataRole"); } @Override diff --git a/core/java/com/android/internal/logging/LogBuilder.java b/core/java/android/metrics/LogMaker.java index 7eda3da70bba..0aef532cdb37 100644 --- a/core/java/com/android/internal/logging/LogBuilder.java +++ b/core/java/android/metrics/LogMaker.java @@ -13,76 +13,89 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.internal.logging; +package android.metrics; -import android.util.EventLog; +import android.annotation.SystemApi; import android.util.Log; import android.util.SparseArray; -import android.view.View; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + /** * Helper class to assemble more complex logs. * * @hide */ - -public class LogBuilder { +@SystemApi +public class LogMaker { private static final String TAG = "LogBuilder"; + + /** + * Min required eventlog line length. + * See: android/util/cts/EventLogTest.java + * Size checks enforced here are intended only as sanity checks; + * your logs may be truncated earlier. Please log responsibly. + * + * @hide + */ + @VisibleForTesting + public static final int MAX_SERIALIZED_SIZE = 4000; + private SparseArray<Object> entries = new SparseArray(); - public LogBuilder(int mainCategory) { + public LogMaker(int mainCategory) { setCategory(mainCategory); } /* Deserialize from the eventlog */ - public LogBuilder(Object[] items) { + public LogMaker(Object[] items) { deserialize(items); } - public LogBuilder setCategory(int category) { + public LogMaker setCategory(int category) { entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY, category); return this; } - public LogBuilder setType(int type) { + public LogMaker setType(int type) { entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_TYPE, type); return this; } - public LogBuilder setSubtype(int subtype) { + public LogMaker setSubtype(int subtype) { entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_SUBTYPE, subtype); return this; } - public LogBuilder setTimestamp(long timestamp) { + public LogMaker setTimestamp(long timestamp) { entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_TIMESTAMP, timestamp); return this; } - public LogBuilder setPackageName(String packageName) { + public LogMaker setPackageName(String packageName) { entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME, packageName); return this; } - public LogBuilder setCounterName(String name) { + public LogMaker setCounterName(String name) { entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_NAME, name); return this; } - public LogBuilder setCounterBucket(int bucket) { + public LogMaker setCounterBucket(int bucket) { entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET, bucket); return this; } - public LogBuilder setCounterBucket(long bucket) { + public LogMaker setCounterBucket(long bucket) { entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET, bucket); return this; } - public LogBuilder setCounterValue(int value) { + public LogMaker setCounterValue(int value) { entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_VALUE, value); return this; } @@ -92,12 +105,16 @@ public class LogBuilder { * @param value One of Integer, Long, Float, String * @return */ - public LogBuilder addTaggedData(int tag, Object value) { + public LogMaker addTaggedData(int tag, Object value) { if (isValidValue(value)) { throw new IllegalArgumentException( "Value must be loggable type - int, long, float, String"); } - entries.put(tag, value); + if (value.toString().getBytes().length > MAX_SERIALIZED_SIZE) { + Log.i(TAG, "Log value too long, omitted: " + value.toString()); + } else { + entries.put(tag, value); + } return this; } @@ -198,18 +215,23 @@ public class LogBuilder { out[i * 2] = entries.keyAt(i); out[i * 2 + 1] = entries.valueAt(i); } + int size = out.toString().getBytes().length; + if (size > MAX_SERIALIZED_SIZE) { + Log.i(TAG, "Log line too long, did not emit: " + size + " bytes."); + throw new RuntimeException(); + } return out; } public void deserialize(Object[] items) { int i = 0; - while(i < items.length) { + while (i < items.length) { Object key = items[i++]; Object value = i < items.length ? items[i++] : null; if (key instanceof Integer) { entries.put((Integer) key, value); } else { - Log.i(TAG, "Invalid key " + key.toString()); + Log.i(TAG, "Invalid key " + key.toString()); } } } diff --git a/core/java/com/android/internal/logging/MetricsReader.java b/core/java/android/metrics/MetricsReader.java index c4fc963adaa6..079c2c93c05b 100644 --- a/core/java/com/android/internal/logging/MetricsReader.java +++ b/core/java/android/metrics/MetricsReader.java @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.internal.logging; +package android.metrics; + +import android.annotation.SystemApi; import com.android.internal.logging.legacy.LegacyConversionLogger; import com.android.internal.logging.legacy.EventLogCollector; @@ -22,10 +24,12 @@ import java.util.Queue; /** * Read platform logs. + * @hide */ +@SystemApi public class MetricsReader { private EventLogCollector mReader; - private Queue<LogBuilder> mEventQueue; + private Queue<LogMaker> mEventQueue; private long mLastEventMs; private long mCheckpointMs; @@ -57,7 +61,7 @@ public class MetricsReader { } /* Next entry in the current log session. */ - public LogBuilder next() { + public LogMaker next() { return mEventQueue == null ? null : mEventQueue.remove(); } diff --git a/core/java/android/net/INetworkScoreCache.aidl b/core/java/android/net/INetworkScoreCache.aidl index 35601ce8b138..1da7d67d848b 100644 --- a/core/java/android/net/INetworkScoreCache.aidl +++ b/core/java/android/net/INetworkScoreCache.aidl @@ -34,7 +34,7 @@ import android.net.ScoredNetwork; * the current scores for each network for debugging purposes. * @hide */ -interface INetworkScoreCache +oneway interface INetworkScoreCache { void updateScores(in List<ScoredNetwork> networks); diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java index 1a128e05ce6e..e5f0bf000f64 100644 --- a/core/java/android/net/NetworkKey.java +++ b/core/java/android/net/NetworkKey.java @@ -16,10 +16,14 @@ package android.net; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.wifi.ScanResult; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiSsid; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import java.util.Objects; @@ -65,6 +69,27 @@ public class NetworkKey implements Parcelable { } /** + * Constructs a new NetworkKey for the given {@link WifiInfo}. + * + * @param wifiInfo the {@link WifiInfo} to create a {@link NetworkKey} for. + * @return A new {@link NetworkKey} instance or <code>null</code> if the given {@link WifiInfo} + * instance doesn't represent a connected WiFi network. + * @hide + */ + @Nullable + public static NetworkKey createFromWifiInfo(@Nullable WifiInfo wifiInfo) { + if (wifiInfo != null) { + final String ssid = wifiInfo.getSSID(); + final String bssid = wifiInfo.getBSSID(); + if (!TextUtils.isEmpty(ssid) && !ssid.equals(WifiSsid.NONE) + && !TextUtils.isEmpty(bssid)) { + return new NetworkKey(new WifiKey(ssid, bssid)); + } + } + return null; + } + + /** * Construct a new {@link NetworkKey} for a Wi-Fi network. * @param wifiKey the {@link WifiKey} identifying this Wi-Fi network. */ diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 20de370fa344..e4cdbce09796 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -44,7 +44,6 @@ public final class GraphicsEnvironment { // without significantly disrupting other activity launch work. Thread eglInitThread = new Thread( () -> { - Thread.currentThread().setPriority(Thread.MIN_PRIORITY); EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); }, "EGL Init"); diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 1c2588ada181..12f539687247 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -43,6 +43,7 @@ interface IUserManager { void setUserEnabled(int userHandle); void evictCredentialEncryptionKey(int userHandle); boolean removeUser(int userHandle); + boolean removeUserEvenWhenDisallowed(int userHandle); void setUserName(int userHandle, String name); void setUserIcon(int userHandle, in Bitmap icon); ParcelFileDescriptor getUserIcon(int userHandle); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index efacb205c36d..c5c380c1074b 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2082,6 +2082,22 @@ public class UserManager { } /** + * Similar to {@link #removeUser(int)} except bypassing the checking of + * {@link UserManager#DISALLOW_REMOVE_USER} + * or {@link UserManager#DISALLOW_REMOVE_MANAGED_PROFILE}. + * + * @see {@link #removeUser(int)} + * @hide + */ + public boolean removeUserEvenWhenDisallowed(@UserIdInt int userHandle) { + try { + return mService.removeUserEvenWhenDisallowed(userHandle); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Updates the user's name. * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java index 8303bca34bee..089eaeceac55 100644 --- a/core/java/android/preference/Preference.java +++ b/core/java/android/preference/Preference.java @@ -145,6 +145,8 @@ public class Preference implements Comparable<Preference> { private List<Preference> mDependents; + private PreferenceGroup mParentGroup; + private boolean mBaseMethodCalled; /** @@ -1238,6 +1240,16 @@ public class Preference implements Comparable<Preference> { registerDependency(); } + /** + * Assigns a {@link PreferenceGroup} as the parent of this Preference. Set null to remove + * the current parent. + * + * @param parentGroup Parent preference group of this Preference or null if none. + */ + void assignParent(@Nullable PreferenceGroup parentGroup) { + mParentGroup = parentGroup; + } + private void registerDependency() { if (TextUtils.isEmpty(mDependencyKey)) return; @@ -1401,6 +1413,17 @@ public class Preference implements Comparable<Preference> { } /** + * Returns the {@link PreferenceGroup} which is this Preference assigned to or null if this + * preference is not assigned to any group or is a root Preference. + * + * @return The parent PreferenceGroup or null if not attached to any. + */ + @Nullable + public PreferenceGroup getParent() { + return mParentGroup; + } + + /** * Called when this Preference is being removed from the hierarchy. You * should remove any references to this Preference that you know about. Make * sure to call through to the superclass implementation. diff --git a/core/java/android/preference/PreferenceGroup.java b/core/java/android/preference/PreferenceGroup.java index f17506b98a2b..f135b26fc5df 100644 --- a/core/java/android/preference/PreferenceGroup.java +++ b/core/java/android/preference/PreferenceGroup.java @@ -29,14 +29,14 @@ import android.util.AttributeSet; * A container for multiple * {@link Preference} objects. It is a base class for Preference objects that are * parents, such as {@link PreferenceCategory} and {@link PreferenceScreen}. - * + * * <div class="special reference"> * <h3>Developer Guides</h3> * <p>For information about building a settings UI with Preferences, * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a> * guide.</p> * </div> - * + * * @attr ref android.R.styleable#PreferenceGroup_orderingFromXml */ public abstract class PreferenceGroup extends Preference implements GenericInflater.Parent<Preference> { @@ -80,7 +80,7 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla * <p> * If this is called after preferences are added, they will not be * re-ordered in the order they were added, hence call this method early on. - * + * * @param orderingAsAdded Whether to order according to the order added. * @see Preference#setOrder(int) */ @@ -90,7 +90,7 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla /** * Whether this group is ordering preferences in the order they are added. - * + * * @return Whether this group orders based on the order the children are added. * @see #setOrderingAsAdded(boolean) */ @@ -115,7 +115,7 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla /** * Returns the {@link Preference} at a particular index. - * + * * @param index The index of the {@link Preference} to retrieve. * @return The {@link Preference}. */ @@ -126,7 +126,7 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla /** * Adds a {@link Preference} at the correct position based on the * preference's order. - * + * * @param preference The preference to add. * @return Whether the preference is now in this group. */ @@ -135,7 +135,7 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla // Exists return true; } - + if (preference.getOrder() == Preference.DEFAULT_ORDER) { if (mOrderingAsAdded) { preference.setOrder(mCurrentPreferenceOrder++); @@ -161,11 +161,12 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla } preference.onAttachedToHierarchy(getPreferenceManager()); - + preference.assignParent(this); + if (mAttachedToActivity) { preference.onAttachedToActivity(); } - + notifyHierarchyChanged(); return true; @@ -173,7 +174,7 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla /** * Removes a {@link Preference} from this group. - * + * * @param preference The preference to remove. * @return Whether the preference was found and removed. */ @@ -186,10 +187,13 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla private boolean removePreferenceInt(Preference preference) { synchronized(this) { preference.onPrepareForRemoval(); + if (preference.getParent() == this) { + preference.assignParent(null); + } return mPreferenceList.remove(preference); } } - + /** * Removes all {@link Preference Preferences} from this group. */ @@ -202,10 +206,10 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla } notifyHierarchyChanged(); } - + /** * Prepares a {@link Preference} to be added to the group. - * + * * @param preference The preference to add. * @return Whether to allow adding the preference (true), or not (false). */ @@ -223,7 +227,7 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla * <p> * This will recursively search for the preference into children that are * also {@link PreferenceGroup PreferenceGroups}. - * + * * @param key The key of the preference to retrieve. * @return The {@link Preference} with the key, or null. */ @@ -239,7 +243,7 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla if (curKey != null && curKey.equals(key)) { return preference; } - + if (preference instanceof PreferenceGroup) { final Preference returnedPreference = ((PreferenceGroup)preference) .findPreference(key); @@ -255,14 +259,14 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla /** * Whether this preference group should be shown on the same screen as its * contained preferences. - * + * * @return True if the contained preferences should be shown on the same * screen as this preference. */ protected boolean isOnSameScreenAsChildren() { return true; } - + @Override protected void onAttachedToActivity() { super.onAttachedToActivity(); @@ -270,7 +274,7 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla // Mark as attached so if a preference is later added to this group, we // can tell it we are already attached mAttachedToActivity = true; - + // Dispatch to all contained preferences final int preferenceCount = getPreferenceCount(); for (int i = 0; i < preferenceCount; i++) { @@ -281,7 +285,7 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla @Override protected void onPrepareForRemoval() { super.onPrepareForRemoval(); - + // We won't be attached to the activity anymore mAttachedToActivity = false; } @@ -297,7 +301,7 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla getPreference(i).onParentChanged(this, disableDependents); } } - + void sortPreferences() { synchronized (this) { Collections.sort(mPreferenceList); @@ -314,7 +318,7 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla getPreference(i).dispatchSaveInstanceState(container); } } - + @Override protected void dispatchRestoreInstanceState(Bundle container) { super.dispatchRestoreInstanceState(container); diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index 6170eb48444d..f5e558a01c56 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -678,8 +678,18 @@ public abstract class DocumentsProvider extends ContentProvider { throw new UnsupportedOperationException("Pre-Android-O query format not supported."); } + /** + * WARNING: Sub-classes should not override this method. This method is non-final + * solely for the purposes of backwards compatibility. + * + * @see #queryChildDocuments(String, String[], Bundle), + * {@link #queryDocument(String, String[])}, + * {@link #queryRecentDocuments(String, String[])}, + * {@link #queryRoots(String[])}, and + * {@link #querySearchDocuments(String, String, String[])}. + */ @Override - public final Cursor query(Uri uri, String[] projection, String selection, + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary // transport method. We override that, and don't ever delegate to this metohd. diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java new file mode 100644 index 000000000000..90e710f12c08 --- /dev/null +++ b/core/java/android/provider/FontsContract.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.provider; + +import android.app.ActivityThread; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.graphics.fonts.FontRequest; +import android.graphics.fonts.FontResult; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.ResultReceiver; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.io.FileNotFoundException; +import java.util.ArrayList; + +/** + * Utility class to deal with Font ContentProviders. + */ +public class FontsContract { + private static final String TAG = "FontsContract"; + + /** + * Defines the constants used in a response from a Font Provider. The cursor returned from the + * query should have the ID column populated with the content uri ID for the resulting font. + * This should point to a real file or shared memory, as the client will mmap the given file + * descriptor. Pipes, sockets and other non-mmap-able file descriptors will fail to load in the + * client application. + */ + public static final class Columns implements BaseColumns { + /** + * Constant used to request data from a font provider. The cursor returned from the query + * should have this column populated with an int for the ttc index for the resulting font. + */ + public static final String TTC_INDEX = "font_ttc_index"; + /** + * Constant used to request data from a font provider. The cursor returned from the query + * may populate this column with the font variation settings String information for the + * font. + */ + public static final String VARIATION_SETTINGS = "font_variation_settings"; + /** + * Constant used to request data from a font provider. The cursor returned from the query + * should have this column populated with the int style for the resulting font. This should + * be one of {@link android.graphics.Typeface#NORMAL}, + * {@link android.graphics.Typeface#BOLD}, {@link android.graphics.Typeface#ITALIC} or + * {@link android.graphics.Typeface#BOLD_ITALIC} + */ + public static final String STYLE = "font_style"; + } + + /** + * Constant used to identify the List of {@link ParcelFileDescriptor} item in the Bundle + * returned to the ResultReceiver in getFont. + * @hide + */ + public static final String PARCEL_FONT_RESULTS = "font_results"; + + /** @hide */ + public static final int RESULT_CODE_OK = 0; + /** @hide */ + public static final int RESULT_CODE_FONT_NOT_FOUND = 1; + /** @hide */ + public static final int RESULT_CODE_PROVIDER_NOT_FOUND = 2; + + private static final int THREAD_RENEWAL_THRESHOLD_MS = 10000; + + private final Context mContext; + private final PackageManager mPackageManager; + private final Object mLock = new Object(); + @GuardedBy("mLock") + private Handler mHandler; + @GuardedBy("mLock") + private HandlerThread mThread; + + /** @hide */ + public FontsContract() { + // TODO: investigate if the system context is the best option here. ApplicationContext or + // the one passed by developer? + // TODO: Looks like ActivityThread.currentActivityThread() can return null. Check when it + // returns null and check if we need to handle null case. + mContext = ActivityThread.currentActivityThread().getSystemContext(); + mPackageManager = mContext.getPackageManager(); + } + + // We use a background thread to post the content resolving work for all requests on. This + // thread should be quit/stopped after all requests are done. + private final Runnable mReplaceDispatcherThreadRunnable = new Runnable() { + @Override + public void run() { + synchronized (mLock) { + if (mThread != null) { + mThread.quitSafely(); + mThread = null; + mHandler = null; + } + } + } + }; + + /** + * @hide + */ + public void getFont(FontRequest request, ResultReceiver receiver) { + synchronized (mLock) { + if (mHandler == null) { + mThread = new HandlerThread("fonts", Process.THREAD_PRIORITY_BACKGROUND); + mThread.start(); + mHandler = new Handler(mThread.getLooper()); + } + mHandler.post(() -> { + String providerAuthority = request.getProviderAuthority(); + // TODO: Implement cert checking for non-system apps + ProviderInfo providerInfo = mPackageManager.resolveContentProvider( + providerAuthority, PackageManager.MATCH_SYSTEM_ONLY); + if (providerInfo == null) { + receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null); + return; + } + Bundle result = getFontFromProvider(request, receiver, providerInfo); + if (result == null) { + receiver.send(RESULT_CODE_FONT_NOT_FOUND, null); + return; + } + receiver.send(RESULT_CODE_OK, result); + }); + mHandler.removeCallbacks(mReplaceDispatcherThreadRunnable); + mHandler.postDelayed(mReplaceDispatcherThreadRunnable, THREAD_RENEWAL_THRESHOLD_MS); + } + } + + private Bundle getFontFromProvider(FontRequest request, ResultReceiver receiver, + ProviderInfo providerInfo) { + ArrayList<FontResult> result = null; + Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(providerInfo.authority) + .build(); + try (Cursor cursor = mContext.getContentResolver().query(uri, new String[] { Columns._ID, + Columns.TTC_INDEX, Columns.VARIATION_SETTINGS, Columns.STYLE }, + "query = ?", new String[] { request.getQuery() }, null);) { + // TODO: Should we restrict the amount of fonts that can be returned? + // TODO: Write documentation explaining that all results should be from the same family. + if (cursor != null && cursor.getCount() > 0) { + result = new ArrayList<>(); + final int idColumnIndex = cursor.getColumnIndex(Columns._ID); + final int ttcIndexColumnIndex = cursor.getColumnIndex(Columns.TTC_INDEX); + final int vsColumnIndex = cursor.getColumnIndex(Columns.VARIATION_SETTINGS); + final int styleColumnIndex = cursor.getColumnIndex(Columns.STYLE); + while (cursor.moveToNext()) { + long id = cursor.getLong(idColumnIndex); + Uri fileUri = ContentUris.withAppendedId(uri, id); + try { + ParcelFileDescriptor pfd = + mContext.getContentResolver().openFileDescriptor(fileUri, "r"); + final int ttcIndex = cursor.getInt(ttcIndexColumnIndex); + final String variationSettings = cursor.getString(vsColumnIndex); + final int style = cursor.getInt(styleColumnIndex); + result.add(new FontResult(pfd, ttcIndex, variationSettings, style)); + } catch (FileNotFoundException e) { + Log.e(TAG, "FileNotFoundException raised when interacting with content " + + "provider " + providerInfo.authority, e); + } + } + } + } + if (result != null && !result.isEmpty()) { + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList(PARCEL_FONT_RESULTS, result); + return bundle; + } + return null; + } +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 71b9482657b6..9e2d4a7b2370 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1334,6 +1334,19 @@ public final class Settings { = "android.settings.VR_LISTENER_SETTINGS"; /** + * Activity Action: Show Picture-in-picture settings. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_PICTURE_IN_PICTURE_SETTINGS + = "android.settings.PICTURE_IN_PICTURE_SETTINGS"; + + /** * Activity Action: Show Storage Manager settings. * <p> * Input: Nothing. @@ -9501,10 +9514,11 @@ public final class Settings { DOCK_SOUNDS_ENABLED, CHARGING_SOUNDS_ENABLED, USB_MASS_STORAGE_ENABLED, + NETWORK_RECOMMENDATIONS_ENABLED, + CURATE_SAVED_OPEN_NETWORKS, + WIFI_WAKEUP_ENABLED, WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, - WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, - WIFI_NUM_OPEN_NETWORKS_KEPT, EMERGENCY_TONE, CALL_AUTO_RETRY, DOCK_AUDIO_MEDIA_ENABLED, @@ -10015,6 +10029,17 @@ public final class Settings { EPHEMERAL_SETTINGS.add(EPHEMERAL_COOKIE_MAX_SIZE_BYTES); } + /** + * Whether to show the high temperature warning notification. + * @hide + */ + public static final String SHOW_TEMPERATURE_WARNING = "show_temperature_warning"; + + /** + * Temperature at which the high temperature warning notification should be shown. + * @hide + */ + public static final String WARNING_TEMPERATURE = "warning_temperature"; } /** diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java index a4b6807e216f..e48a0d08de7d 100644 --- a/core/java/android/provider/VoicemailContract.java +++ b/core/java/android/provider/VoicemailContract.java @@ -319,7 +319,7 @@ public class VoicemailContract { * * @hide */ - public static final String IS_OMTP_VOICEMAIL = "is_omtp_voicmail"; + public static final String IS_OMTP_VOICEMAIL = "is_omtp_voicemail"; /** * A convenience method to build voicemail URI specific to a source package by appending diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index 22f1bedc6072..5461e6b6d699 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -83,6 +83,12 @@ public final class KeymasterDefs { public static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704; public static final int KM_TAG_UNIQUE_ID = KM_BYTES | 707; public static final int KM_TAG_ATTESTATION_CHALLENGE = KM_BYTES | 708; + public static final int KM_TAG_ATTESTATION_ID_BRAND = KM_BYTES | 710; + public static final int KM_TAG_ATTESTATION_ID_DEVICE = KM_BYTES | 711; + public static final int KM_TAG_ATTESTATION_ID_PRODUCT = KM_BYTES | 712; + public static final int KM_TAG_ATTESTATION_ID_SERIAL = KM_BYTES | 713; + public static final int KM_TAG_ATTESTATION_ID_IMEI = KM_BYTES | 714; + public static final int KM_TAG_ATTESTATION_ID_MEID = KM_BYTES | 715; public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000; public static final int KM_TAG_NONCE = KM_BYTES | 1001; @@ -204,6 +210,7 @@ public final class KeymasterDefs { public static final int KM_ERROR_INVALID_MAC_LENGTH = -57; public static final int KM_ERROR_MISSING_MIN_MAC_LENGTH = -58; public static final int KM_ERROR_UNSUPPORTED_MIN_MAC_LENGTH = -59; + public static final int KM_ERROR_CANNOT_ATTEST_IDS = -66; public static final int KM_ERROR_UNIMPLEMENTED = -100; public static final int KM_ERROR_VERSION_MISMATCH = -101; public static final int KM_ERROR_UNKNOWN_ERROR = -1000; @@ -249,6 +256,7 @@ public final class KeymasterDefs { "Caller-provided IV not permitted"); sErrorCodeToString.put(KM_ERROR_INVALID_MAC_LENGTH, "Invalid MAC or authentication tag length"); + sErrorCodeToString.put(KM_ERROR_CANNOT_ATTEST_IDS, "Unable to attest device ids"); sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented"); sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error"); } diff --git a/core/java/android/service/autofill/AutoFillServiceInfo.java b/core/java/android/service/autofill/AutoFillServiceInfo.java index ab86580f1f3b..fd957f179443 100644 --- a/core/java/android/service/autofill/AutoFillServiceInfo.java +++ b/core/java/android/service/autofill/AutoFillServiceInfo.java @@ -25,15 +25,26 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.os.RemoteException; +import android.util.AndroidException; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import com.android.internal.R; + import java.io.IOException; -/** @hide */ +// TODO(b/33197203 , b/33802548): add CTS tests +/** + * {@link ServiceInfo} and meta-data about an {@link AutoFillService}. + * + * <p>Upon construction, if {@link #getParseError()} is {@code null}, then the service is configured + * correctly. Otherwise, {@link #getParseError()} indicates the parsing error. + * + * @hide + */ public final class AutoFillServiceInfo { static final String TAG = "AutoFillServiceInfo"; @@ -53,67 +64,87 @@ public final class AutoFillServiceInfo { } @Nullable - private String mParseError; + private final String mParseError; + private final ServiceInfo mServiceInfo; @Nullable - private ServiceInfo mServiceInfo; - @Nullable - private String mSettingsActivity; + private final String mSettingsActivity; public AutoFillServiceInfo(PackageManager pm, ComponentName comp, int userHandle) throws PackageManager.NameNotFoundException { this(pm, getServiceInfoOrThrow(comp, userHandle)); } - public AutoFillServiceInfo(PackageManager pm, ServiceInfo si) - throws PackageManager.NameNotFoundException{ - if (si == null) { - mParseError = "Service not available"; + public AutoFillServiceInfo(PackageManager pm, ServiceInfo si) { + mServiceInfo = si; + TypedArray metaDataArray; + try { + metaDataArray = getMetaDataArray(pm, si); + } catch (AndroidException e) { + mParseError = e.getMessage(); + mSettingsActivity = null; + Log.w(TAG, mParseError, e); return; } + + mParseError = null; + if (metaDataArray != null) { + mSettingsActivity = + metaDataArray.getString(R.styleable.AutoFillService_settingsActivity); + metaDataArray.recycle(); + } else { + mSettingsActivity = null; + } + } + + /** + * Gets the meta-data as a TypedArray, or null if not provided, or throws if invalid. + */ + @Nullable + private static TypedArray getMetaDataArray(PackageManager pm, ServiceInfo si) + throws AndroidException { + // Check for permissions. if (!Manifest.permission.BIND_AUTO_FILL.equals(si.permission)) { - mParseError = "Service does not require permission " - + Manifest.permission.BIND_AUTO_FILL; - return; + throw new AndroidException( + "Service does not require permission " + Manifest.permission.BIND_AUTO_FILL); } - XmlResourceParser parser = null; + // Get the AutoFill metadata, if declared. + XmlResourceParser parser = si.loadXmlMetaData(pm, AutoFillService.SERVICE_META_DATA); + if (parser == null) { + return null; + } + + // Parse service info and get the <autofill-service> tag as an AttributeSet. + AttributeSet attrs; try { - parser = si.loadXmlMetaData(pm, AutoFillService.SERVICE_META_DATA); - if (parser == null) { - mParseError = "No " + AutoFillService.SERVICE_META_DATA - + " meta-data for " + si.packageName; - return; + // Move the XML parser to the first tag. + try { + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + } + } catch (XmlPullParserException | IOException e) { + throw new AndroidException("Error parsing auto fill service meta-data: " + e, e); } - Resources res = pm.getResourcesForApplication(si.applicationInfo); - - AttributeSet attrs = Xml.asAttributeSet(parser); - - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && type != XmlPullParser.START_TAG) { + if (!"autofill-service".equals(parser.getName())) { + throw new AndroidException("Meta-data does not start with autofill-service tag"); } - - String nodeName = parser.getName(); - if (!"autofill-service".equals(nodeName)) { - mParseError = "Meta-data does not start with autofill-service tag"; - return; + attrs = Xml.asAttributeSet(parser); + + // Get resources required to read the AttributeSet. + Resources res; + try { + res = pm.getResourcesForApplication(si.applicationInfo); + } catch (PackageManager.NameNotFoundException e) { + throw new AndroidException("Error getting application resources: " + e, e); } - TypedArray array = res.obtainAttributes(attrs, - com.android.internal.R.styleable.AutoFillService); - mSettingsActivity = array.getString( - com.android.internal.R.styleable.AutoFillService_settingsActivity); - array.recycle(); - } catch (XmlPullParserException | IOException | PackageManager.NameNotFoundException e) { - mParseError = "Error parsing auto fill service meta-data: " + e; - Log.w(TAG, "error parsing auto fill service meta-data", e); - return; + return res.obtainAttributes(attrs, R.styleable.AutoFillService); } finally { - if (parser != null) parser.close(); + parser.close(); } - mServiceInfo = si; } @Nullable @@ -121,7 +152,6 @@ public final class AutoFillServiceInfo { return mParseError; } - @Nullable public ServiceInfo getServiceInfo() { return mServiceInfo; } diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index d7a02a8042a1..cecdbee75b00 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -119,6 +119,24 @@ public abstract class NotificationAssistantService extends NotificationListenerS } /** + * Inform the notification manager about un-snoozing a specific notification. + * <p> + * This should only be used for notifications snoozed by this listener using + * {@link #snoozeNotification(String, String)}. Once un-snoozed, you will get a + * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the + * notification. + * @param key The key of the notification to snooze + */ + public final void unsnoozeNotification(String key) { + if (!isBound()) return; + try { + getNotificationInterface().unsnoozeNotificationFromAssistant(mWrapper, key); + } catch (android.os.RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + } + } + + /** * Creates a notification channel that notifications can be posted to for a given package. * * @param pkg The package to create a channel for. diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 694837e53c73..517b305e0a16 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -562,43 +562,6 @@ public abstract class NotificationListenerService extends Service { } } - /** - * Inform the notification manager about snoozing a specific notification. - * <p> - * Use this to snooze a notification for an indeterminate time. Upon being informed, the - * notification manager will actually remove the notification and you will get an - * {@link #onNotificationRemoved(StatusBarNotification)} callback. When the - * snoozing period expires, you will get a - * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the - * notification. Use {@link #unsnoozeNotification(String)} to restore the notification. - * @param key The key of the notification to snooze - */ - public final void snoozeNotification(String key) { - if (!isBound()) return; - try { - getNotificationInterface().snoozeNotificationFromListener(mWrapper, key); - } catch (android.os.RemoteException ex) { - Log.v(TAG, "Unable to contact notification manager", ex); - } - } - - /** - * Inform the notification manager about un-snoozing a specific notification. - * <p> - * This should only be used for notifications snoozed by this listener using - * {@link #snoozeNotification(String)}. Once un-snoozed, you will get a - * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the - * notification. - * @param key The key of the notification to snooze - */ - public final void unsnoozeNotification(String key) { - if (!isBound()) return; - try { - getNotificationInterface().unsnoozeNotificationFromListener(mWrapper, key); - } catch (android.os.RemoteException ex) { - Log.v(TAG, "Unable to contact notification manager", ex); - } - } /** * Inform the notification manager that these notifications have been viewed by the @@ -663,6 +626,26 @@ public abstract class NotificationListenerService extends Service { } /** + * Like {@link #getActiveNotifications()}, but returns the list of currently snoozed + * notifications, for all users this listener has access to. + * + * <p>The service should wait for the {@link #onListenerConnected()} event + * before performing this operation. + * + * @return An array of active notifications, sorted in natural order. + */ + public final StatusBarNotification[] getSnoozedNotifications() { + try { + ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface() + .getSnoozedNotificationsFromListener(mWrapper, TRIM_FULL); + return cleanUpNotificationList(parceledList); + } catch (android.os.RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + } + return null; + } + + /** * Request the list of outstanding notifications (that is, those that are visible to the * current user). Useful when you don't know what's already been posted. * @@ -711,36 +694,41 @@ public abstract class NotificationListenerService extends Service { try { ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface() .getActiveNotificationsFromListener(mWrapper, keys, trim); - List<StatusBarNotification> list = parceledList.getList(); - ArrayList<StatusBarNotification> corruptNotifications = null; - int N = list.size(); - for (int i = 0; i < N; i++) { - StatusBarNotification sbn = list.get(i); - Notification notification = sbn.getNotification(); - try { - // convert icon metadata to legacy format for older clients - createLegacyIconExtras(notification); - // populate remote views for older clients. - maybePopulateRemoteViews(notification); - } catch (IllegalArgumentException e) { - if (corruptNotifications == null) { - corruptNotifications = new ArrayList<>(N); - } - corruptNotifications.add(sbn); - Log.w(TAG, "onNotificationPosted: can't rebuild notification from " + - sbn.getPackageName()); - } - } - if (corruptNotifications != null) { - list.removeAll(corruptNotifications); - } - return list.toArray(new StatusBarNotification[list.size()]); + return cleanUpNotificationList(parceledList); } catch (android.os.RemoteException ex) { Log.v(TAG, "Unable to contact notification manager", ex); } return null; } + private StatusBarNotification[] cleanUpNotificationList( + ParceledListSlice<StatusBarNotification> parceledList) { + List<StatusBarNotification> list = parceledList.getList(); + ArrayList<StatusBarNotification> corruptNotifications = null; + int N = list.size(); + for (int i = 0; i < N; i++) { + StatusBarNotification sbn = list.get(i); + Notification notification = sbn.getNotification(); + try { + // convert icon metadata to legacy format for older clients + createLegacyIconExtras(notification); + // populate remote views for older clients. + maybePopulateRemoteViews(notification); + } catch (IllegalArgumentException e) { + if (corruptNotifications == null) { + corruptNotifications = new ArrayList<>(N); + } + corruptNotifications.add(sbn); + Log.w(TAG, "get(Active/Snoozed)Notifications: can't rebuild notification from " + + sbn.getPackageName()); + } + } + if (corruptNotifications != null) { + list.removeAll(corruptNotifications); + } + return list.toArray(new StatusBarNotification[list.size()]); + } + /** * Gets the set of hints representing current state. * @@ -1166,11 +1154,12 @@ public abstract class NotificationListenerService extends Service { // System specified group key. private String mOverrideGroupKey; // Notification assistant channel override. - private NotificationChannel mOverrideChannel; + private NotificationChannel mChannel; // Notification assistant people override. private ArrayList<String> mOverridePeople; // Notification assistant snooze criteria. private ArrayList<SnoozeCriterion> mSnoozeCriteria; + private boolean mShowBadge; public Ranking() {} @@ -1200,7 +1189,7 @@ public abstract class NotificationListenerService extends Service { } /** - * Returns the user specificed visibility for the package that posted + * Returns the user specified visibility for the package that posted * this notification, or * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if * no such preference has been expressed. @@ -1233,7 +1222,7 @@ public abstract class NotificationListenerService extends Service { * Returns the importance of the notification, which dictates its * modes of presentation, see: {@link NotificationManager#IMPORTANCE_DEFAULT}, etc. * - * @return the rank of the notification + * @return the importance of the notification */ public @NotificationManager.Importance int getImportance() { return mImportance; @@ -1258,12 +1247,11 @@ public abstract class NotificationListenerService extends Service { } /** - * If the {@link NotificationAssistantService} has overridden the channel this notification - * was posted to, then this will not match the channel provided by the posting application - * and this should be used to determine the interruptiveness of the notification instead. + * Returns the notification channel this notification was posted to, which dictates + * notification behavior and presentation. */ public NotificationChannel getChannel() { - return mOverrideChannel; + return mChannel; } /** @@ -1283,11 +1271,20 @@ public abstract class NotificationListenerService extends Service { return mSnoozeCriteria; } + /** + * Returns whether this notification can be displayed as a badge. + * + * @return true if the notification can be displayed as a badge, false otherwise. + */ + public boolean canShowBadge() { + return mShowBadge; + } + private void populate(String key, int rank, boolean matchesInterruptionFilter, int visibilityOverride, int suppressedVisualEffects, int importance, CharSequence explanation, String overrideGroupKey, - NotificationChannel overrideChannel, ArrayList<String> overridePeople, - ArrayList<SnoozeCriterion> snoozeCriteria) { + NotificationChannel channel, ArrayList<String> overridePeople, + ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge) { mKey = key; mRank = rank; mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW; @@ -1297,9 +1294,10 @@ public abstract class NotificationListenerService extends Service { mImportance = importance; mImportanceExplanation = explanation; mOverrideGroupKey = overrideGroupKey; - mOverrideChannel = overrideChannel; + mChannel = channel; mOverridePeople = overridePeople; mSnoozeCriteria = snoozeCriteria; + mShowBadge = showBadge; } /** @@ -1343,9 +1341,10 @@ public abstract class NotificationListenerService extends Service { private ArrayMap<String, Integer> mImportance; private ArrayMap<String, String> mImportanceExplanation; private ArrayMap<String, String> mOverrideGroupKeys; - private ArrayMap<String, NotificationChannel> mOverrideChannels; + private ArrayMap<String, NotificationChannel> mChannels; private ArrayMap<String, ArrayList<String>> mOverridePeople; private ArrayMap<String, ArrayList<SnoozeCriterion>> mSnoozeCriteria; + private ArrayMap<String, Boolean> mShowBadge; private RankingMap(NotificationRankingUpdate rankingUpdate) { mRankingUpdate = rankingUpdate; @@ -1373,7 +1372,8 @@ public abstract class NotificationListenerService extends Service { outRanking.populate(key, rank, !isIntercepted(key), getVisibilityOverride(key), getSuppressedVisualEffects(key), getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key), - getOverrideChannel(key), getOverridePeople(key), getSnoozeCriteria(key)); + getChannel(key), getOverridePeople(key), getSnoozeCriteria(key), + getShowBadge(key)); return rank >= 0; } @@ -1453,13 +1453,13 @@ public abstract class NotificationListenerService extends Service { return mOverrideGroupKeys.get(key); } - private NotificationChannel getOverrideChannel(String key) { + private NotificationChannel getChannel(String key) { synchronized (this) { - if (mOverrideChannels == null) { - buildOverrideChannelsLocked(); + if (mChannels == null) { + buildChannelsLocked(); } } - return mOverrideChannels.get(key); + return mChannels.get(key); } private ArrayList<String> getOverridePeople(String key) { @@ -1480,6 +1480,16 @@ public abstract class NotificationListenerService extends Service { return mSnoozeCriteria.get(key); } + private boolean getShowBadge(String key) { + synchronized (this) { + if (mShowBadge == null) { + buildShowBadgeLocked(); + } + } + Boolean showBadge = mShowBadge.get(key); + return showBadge == null ? false : showBadge.booleanValue(); + } + // Locked by 'this' private void buildRanksLocked() { String[] orderedKeys = mRankingUpdate.getOrderedKeys(); @@ -1544,11 +1554,11 @@ public abstract class NotificationListenerService extends Service { } // Locked by 'this' - private void buildOverrideChannelsLocked() { - Bundle overrideChannels = mRankingUpdate.getOverrideChannels(); - mOverrideChannels = new ArrayMap<>(overrideChannels.size()); - for (String key : overrideChannels.keySet()) { - mOverrideChannels.put(key, overrideChannels.getParcelable(key)); + private void buildChannelsLocked() { + Bundle channels = mRankingUpdate.getChannels(); + mChannels = new ArrayMap<>(channels.size()); + for (String key : channels.keySet()) { + mChannels.put(key, channels.getParcelable(key)); } } @@ -1570,6 +1580,15 @@ public abstract class NotificationListenerService extends Service { } } + // Locked by 'this' + private void buildShowBadgeLocked() { + Bundle showBadge = mRankingUpdate.getShowBadge(); + mShowBadge = new ArrayMap<>(showBadge.size()); + for (String key : showBadge.keySet()) { + mShowBadge.put(key, showBadge.getBoolean(key)); + } + } + // ----------- Parcelable @Override diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java index a2cdeffef2b1..326b212a9417 100644 --- a/core/java/android/service/notification/NotificationRankingUpdate.java +++ b/core/java/android/service/notification/NotificationRankingUpdate.java @@ -31,14 +31,16 @@ public class NotificationRankingUpdate implements Parcelable { private final int[] mImportance; private final Bundle mImportanceExplanation; private final Bundle mOverrideGroupKeys; - private final Bundle mOverrideChannels; + private final Bundle mChannels; private final Bundle mOverridePeople; private final Bundle mSnoozeCriteria; + private final Bundle mShowBadge; public NotificationRankingUpdate(String[] keys, String[] interceptedKeys, Bundle visibilityOverrides, Bundle suppressedVisualEffects, int[] importance, Bundle explanation, Bundle overrideGroupKeys, - Bundle overrideChannels, Bundle overridePeople, Bundle snoozeCriteria) { + Bundle channels, Bundle overridePeople, Bundle snoozeCriteria, + Bundle showBadge) { mKeys = keys; mInterceptedKeys = interceptedKeys; mVisibilityOverrides = visibilityOverrides; @@ -46,9 +48,10 @@ public class NotificationRankingUpdate implements Parcelable { mImportance = importance; mImportanceExplanation = explanation; mOverrideGroupKeys = overrideGroupKeys; - mOverrideChannels = overrideChannels; + mChannels = channels; mOverridePeople = overridePeople; mSnoozeCriteria = snoozeCriteria; + mShowBadge = showBadge; } public NotificationRankingUpdate(Parcel in) { @@ -60,9 +63,10 @@ public class NotificationRankingUpdate implements Parcelable { in.readIntArray(mImportance); mImportanceExplanation = in.readBundle(); mOverrideGroupKeys = in.readBundle(); - mOverrideChannels = in.readBundle(); + mChannels = in.readBundle(); mOverridePeople = in.readBundle(); mSnoozeCriteria = in.readBundle(); + mShowBadge = in.readBundle(); } @Override @@ -79,9 +83,10 @@ public class NotificationRankingUpdate implements Parcelable { out.writeIntArray(mImportance); out.writeBundle(mImportanceExplanation); out.writeBundle(mOverrideGroupKeys); - out.writeBundle(mOverrideChannels); + out.writeBundle(mChannels); out.writeBundle(mOverridePeople); out.writeBundle(mSnoozeCriteria); + out.writeBundle(mShowBadge); } public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR @@ -123,8 +128,8 @@ public class NotificationRankingUpdate implements Parcelable { return mOverrideGroupKeys; } - public Bundle getOverrideChannels() { - return mOverrideChannels; + public Bundle getChannels() { + return mChannels; } public Bundle getOverridePeople() { @@ -134,4 +139,8 @@ public class NotificationRankingUpdate implements Parcelable { public Bundle getSnoozeCriteria() { return mSnoozeCriteria; } + + public Bundle getShowBadge() { + return mShowBadge; + } } diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index 6276af398c43..85baf4edaeca 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -43,21 +43,18 @@ public class StatusBarNotification implements Parcelable { private final Notification notification; private final UserHandle user; private final long postTime; - private final NotificationChannel channel; private Context mContext; // used for inflation & icon expansion /** @hide */ - public StatusBarNotification(String pkg, String opPkg, NotificationChannel channel, int id, + public StatusBarNotification(String pkg, String opPkg, int id, String tag, int uid, int initialPid, Notification notification, UserHandle user, String overrideGroupKey, long postTime) { if (pkg == null) throw new NullPointerException(); if (notification == null) throw new NullPointerException(); - if (channel == null) throw new IllegalArgumentException(); this.pkg = pkg; this.opPkg = opPkg; - this.channel = channel; this.id = id; this.tag = tag; this.uid = uid; @@ -88,7 +85,6 @@ public class StatusBarNotification implements Parcelable { this.postTime = postTime; this.key = key(); this.groupKey = groupKey(); - this.channel = null; } public StatusBarNotification(Parcel in) { @@ -112,7 +108,6 @@ public class StatusBarNotification implements Parcelable { } this.key = key(); this.groupKey = groupKey(); - this.channel = NotificationChannel.CREATOR.createFromParcel(in); } private String key() { @@ -182,7 +177,6 @@ public class StatusBarNotification implements Parcelable { } else { out.writeInt(0); } - this.channel.writeToParcel(out, flags); } public int describeContents() { @@ -209,14 +203,14 @@ public class StatusBarNotification implements Parcelable { public StatusBarNotification cloneLight() { final Notification no = new Notification(); this.notification.cloneInto(no, false); // light copy - return new StatusBarNotification(this.pkg, this.opPkg, this.channel, + return new StatusBarNotification(this.pkg, this.opPkg, this.id, this.tag, this.uid, this.initialPid, no, this.user, this.overrideGroupKey, this.postTime); } @Override public StatusBarNotification clone() { - return new StatusBarNotification(this.pkg, this.opPkg, this.channel, + return new StatusBarNotification(this.pkg, this.opPkg, this.id, this.tag, this.uid, this.initialPid, this.notification.clone(), this.user, this.overrideGroupKey, this.postTime); } @@ -336,13 +330,6 @@ public class StatusBarNotification implements Parcelable { } /** - * Returns the channel this notification was posted to. - */ - public NotificationChannel getNotificationChannel() { - return channel; - } - - /** * @hide */ public Context getPackageContext(Context context) { diff --git a/core/java/android/speech/tts/BlockingAudioTrack.java b/core/java/android/speech/tts/BlockingAudioTrack.java index 9920ea11e35d..be5851c3e64f 100644 --- a/core/java/android/speech/tts/BlockingAudioTrack.java +++ b/core/java/android/speech/tts/BlockingAudioTrack.java @@ -164,7 +164,7 @@ class BlockingAudioTrack { // all data from the audioTrack has been sent to the mixer, so // it's safe to release at this point. if (DBG) Log.d(TAG, "Releasing audio track [" + track.hashCode() + "]"); - synchronized(mAudioTrackLock) { + synchronized (mAudioTrackLock) { mAudioTrack = null; } track.release(); @@ -340,4 +340,25 @@ class BlockingAudioTrack { return value < min ? min : (value < max ? value : max); } + /** + * @see + * AudioTrack#setPlaybackPositionUpdateListener(AudioTrack.OnPlaybackPositionUpdateListener). + */ + public void setPlaybackPositionUpdateListener( + AudioTrack.OnPlaybackPositionUpdateListener listener) { + synchronized (mAudioTrackLock) { + if (mAudioTrack != null) { + mAudioTrack.setPlaybackPositionUpdateListener(listener); + } + } + } + + /** @see AudioTrack#setNotificationMarkerPosition(int). */ + public void setNotificationMarkerPosition(int frames) { + synchronized (mAudioTrackLock) { + if (mAudioTrack != null) { + mAudioTrack.setNotificationMarkerPosition(frames); + } + } + } } diff --git a/core/java/android/speech/tts/ITextToSpeechCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl index 4e3acf6a1993..edb6e482f3d0 100644 --- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl +++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl @@ -83,4 +83,19 @@ oneway interface ITextToSpeechCallback { * callback. */ void onAudioAvailable(String utteranceId, in byte[] audio); + + /** + * Tells the client that the engine is about to speak the specified range of the utterance. + * + * <p> + * Only called if the engine supplies timing information by calling + * {@link SynthesisCallback#rangeStart(int, int, int)} and only when the request is played back + * by the service, not when using {@link android.speech.tts.TextToSpeech#synthesizeToFile}. + * </p> + * + * @param utteranceId Unique id identifying the synthesis request. + * @param start The start character index of the range in the utterance text. + * @param end The end character index of the range (exclusive) in the utterance text. + */ + void onUtteranceRangeStart(String utteranceId, int start, int end); } diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java index 778aa86bcee5..9e24b09e94ad 100644 --- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java +++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java @@ -271,4 +271,12 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback { mStatusCode = errorCode; } } + + public void rangeStart(int markerInFrames, int start, int end) { + if (mItem == null) { + Log.e(TAG, "mItem is null"); + return; + } + mItem.rangeStart(markerInFrames, start, end); + } } diff --git a/core/java/android/speech/tts/SynthesisCallback.java b/core/java/android/speech/tts/SynthesisCallback.java index 2fd84996ece0..8b74ed763c32 100644 --- a/core/java/android/speech/tts/SynthesisCallback.java +++ b/core/java/android/speech/tts/SynthesisCallback.java @@ -142,4 +142,26 @@ public interface SynthesisCallback { * <p>Useful for checking if a fallback from network request is possible. */ boolean hasFinished(); + + /** + * The service may call this method to provide timing information about the spoken text. + * + * <p>Calling this method means that at the given audio frame, the given range of the input is + * about to be spoken. If this method is called the client will receive a callback on the + * listener ({@link UtteranceProgressListener#onUtteranceRangeStart}) at the moment that frame + * has been reached by the playback head. + * + * <p>The markerInFrames is a frame index into the audio for this synthesis request, i.e. into + * the concatenation of the audio bytes sent to audioAvailable for this synthesis request. The + * definition of a frame depends on the format given by {@link #start}. See {@link AudioFormat} + * for more information. + * + * <p>This method should only be called on the synthesis thread, while in {@link + * TextToSpeechService#onSynthesizeText}. + * + * @param markerInFrames The position in frames in the audio where this range is spoken. + * @param start The start index of the range in the input text. + * @param end The end index (exclusive) of the range in the input text. + */ + default void rangeStart(int markerInFrames, int start, int end) {} } diff --git a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java index 742393346288..cb5f2209fa37 100644 --- a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java +++ b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java @@ -17,18 +17,21 @@ package android.speech.tts; import android.speech.tts.TextToSpeechService.AudioOutputParams; import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher; +import android.media.AudioTrack; import android.util.Log; import java.util.LinkedList; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.ConcurrentLinkedQueue; /** - * Manages the playback of a list of byte arrays representing audio data - * that are queued by the engine to an audio track. + * Manages the playback of a list of byte arrays representing audio data that are queued by the + * engine to an audio track. */ -final class SynthesisPlaybackQueueItem extends PlaybackQueueItem { +final class SynthesisPlaybackQueueItem extends PlaybackQueueItem + implements AudioTrack.OnPlaybackPositionUpdateListener { private static final String TAG = "TTS.SynthQueueItem"; private static final boolean DBG = false; @@ -63,6 +66,10 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem { private final BlockingAudioTrack mAudioTrack; private final AbstractEventLogger mLogger; + // Stores a queue of markers. When the marker in front is reached the client is informed and we + // wait for the next one. + private ConcurrentLinkedQueue<ProgressMarker> markerList = new ConcurrentLinkedQueue<>(); + SynthesisPlaybackQueueItem(AudioOutputParams audioParams, int sampleRate, int audioFormat, int channelCount, UtteranceProgressDispatcher dispatcher, Object callerIdentity, AbstractEventLogger logger) { @@ -89,6 +96,8 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem { return; } + mAudioTrack.setPlaybackPositionUpdateListener(this); + try { byte[] buffer = null; @@ -172,6 +181,55 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem { } } + /** Convenience class for passing around TTS markers. */ + private class ProgressMarker { + // The index in frames of this marker. + public final int frames; + // The start index in the text of the utterance. + public final int start; + // The end index (exclusive) in the text of the utterance. + public final int end; + + public ProgressMarker(int frames, int start, int end) { + this.frames = frames; + this.start = start; + this.end = end; + } + } + + /** Set a callback for the first marker in the queue. */ + void updateMarker() { + ProgressMarker marker = markerList.peek(); + if (marker != null) { + // Zero is used to disable the marker. The documentation recommends to use a non-zero + // position near zero such as 1. + int markerInFrames = marker.frames == 0 ? 1 : marker.frames; + mAudioTrack.setNotificationMarkerPosition(markerInFrames); + } + } + + /** Informs us that at markerInFrames, the range between start and end is about to be spoken. */ + void rangeStart(int markerInFrames, int start, int end) { + markerList.add(new ProgressMarker(markerInFrames, start, end)); + updateMarker(); + } + + @Override + public void onMarkerReached(AudioTrack track) { + ProgressMarker marker = markerList.poll(); + if (marker == null) { + Log.e(TAG, "onMarkerReached reached called but no marker in queue"); + return; + } + // Inform the client. + getDispatcher().dispatchOnUtteranceRangeStart(marker.start, marker.end); + // Listen for the next marker. + // It's ok if this marker is in the past, in that case onMarkerReached will be called again. + updateMarker(); + } + + @Override + public void onPeriodicNotification(AudioTrack track) {} void put(byte[] buffer) throws InterruptedException { try { diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 24cad950f648..9a157b7c9b5d 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -2103,55 +2103,69 @@ public class TextToSpeech { private boolean mEstablished; - private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() { - public void onStop(String utteranceId, boolean isStarted) throws RemoteException { - UtteranceProgressListener listener = mUtteranceProgressListener; - if (listener != null) { - listener.onStop(utteranceId, isStarted); - } - }; + private final ITextToSpeechCallback.Stub mCallback = + new ITextToSpeechCallback.Stub() { + public void onStop(String utteranceId, boolean isStarted) + throws RemoteException { + UtteranceProgressListener listener = mUtteranceProgressListener; + if (listener != null) { + listener.onStop(utteranceId, isStarted); + } + }; - @Override - public void onSuccess(String utteranceId) { - UtteranceProgressListener listener = mUtteranceProgressListener; - if (listener != null) { - listener.onDone(utteranceId); - } - } + @Override + public void onSuccess(String utteranceId) { + UtteranceProgressListener listener = mUtteranceProgressListener; + if (listener != null) { + listener.onDone(utteranceId); + } + } - @Override - public void onError(String utteranceId, int errorCode) { - UtteranceProgressListener listener = mUtteranceProgressListener; - if (listener != null) { - listener.onError(utteranceId); - } - } + @Override + public void onError(String utteranceId, int errorCode) { + UtteranceProgressListener listener = mUtteranceProgressListener; + if (listener != null) { + listener.onError(utteranceId); + } + } - @Override - public void onStart(String utteranceId) { - UtteranceProgressListener listener = mUtteranceProgressListener; - if (listener != null) { - listener.onStart(utteranceId); - } - } + @Override + public void onStart(String utteranceId) { + UtteranceProgressListener listener = mUtteranceProgressListener; + if (listener != null) { + listener.onStart(utteranceId); + } + } - @Override - public void onBeginSynthesis(String utteranceId, int sampleRateInHz, int audioFormat, - int channelCount) { - UtteranceProgressListener listener = mUtteranceProgressListener; - if (listener != null) { - listener.onBeginSynthesis(utteranceId, sampleRateInHz, audioFormat, channelCount); - } - } + @Override + public void onBeginSynthesis( + String utteranceId, + int sampleRateInHz, + int audioFormat, + int channelCount) { + UtteranceProgressListener listener = mUtteranceProgressListener; + if (listener != null) { + listener.onBeginSynthesis( + utteranceId, sampleRateInHz, audioFormat, channelCount); + } + } - @Override - public void onAudioAvailable(String utteranceId, byte[] audio) { - UtteranceProgressListener listener = mUtteranceProgressListener; - if (listener != null) { - listener.onAudioAvailable(utteranceId, audio); - } - } - }; + @Override + public void onAudioAvailable(String utteranceId, byte[] audio) { + UtteranceProgressListener listener = mUtteranceProgressListener; + if (listener != null) { + listener.onAudioAvailable(utteranceId, audio); + } + } + + @Override + public void onUtteranceRangeStart(String utteranceId, int start, int end) { + UtteranceProgressListener listener = mUtteranceProgressListener; + if (listener != null) { + listener.onUtteranceRangeStart(utteranceId, start, end); + } + } + }; private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> { private final ComponentName mName; diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index 55da52b028d7..80d3c8a252e3 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -663,6 +663,8 @@ public abstract class TextToSpeechService extends Service { void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount); void dispatchOnAudioAvailable(byte[] audio); + + public void dispatchOnUtteranceRangeStart(int start, int end); } /** Set of parameters affecting audio output. */ @@ -882,6 +884,15 @@ public abstract class TextToSpeechService extends Service { } } + @Override + public void dispatchOnUtteranceRangeStart(int start, int end) { + final String utteranceId = getUtteranceId(); + if (utteranceId != null) { + mCallbacks.dispatchOnUtteranceRangeStart( + getCallerIdentity(), utteranceId, start, end); + } + } + abstract public String getUtteranceId(); String getStringParam(Bundle params, String key, String defaultValue) { @@ -1559,6 +1570,17 @@ public abstract class TextToSpeechService extends Service { } } + public void dispatchOnUtteranceRangeStart( + Object callerIdentity, String utteranceId, int start, int end) { + ITextToSpeechCallback cb = getCallbackFor(callerIdentity); + if (cb == null) return; + try { + cb.onUtteranceRangeStart(utteranceId, start, end); + } catch (RemoteException e) { + Log.e(TAG, "Callback dispatchOnUtteranceRangeStart(String, int, int) failed: " + e); + } + } + @Override public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) { IBinder caller = (IBinder) cookie; diff --git a/core/java/android/speech/tts/UtteranceProgressListener.java b/core/java/android/speech/tts/UtteranceProgressListener.java index 72a522800d65..0ee376948538 100644 --- a/core/java/android/speech/tts/UtteranceProgressListener.java +++ b/core/java/android/speech/tts/UtteranceProgressListener.java @@ -122,8 +122,24 @@ public abstract class UtteranceProgressListener { } /** - * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new - * progress listener. + * This is called when the TTS service is about to speak the specified range of the utterance + * with the given utteranceId. + * + * <p>This method is called when the audio is expected to start playing on the speaker. Note + * that this is different from {@link #onAudioAvailable} which is called as soon as the audio is + * generated. + * + * <p>Only called if the engine supplies timing information by calling {@link + * SynthesisCallback#rangeStart(int, int, int)}. + * + * @param utteranceId Unique id identifying the synthesis request. + * @param start The start index of the range in the utterance text. + * @param end The end index of the range (exclusive) in the utterance text. + */ + public void onUtteranceRangeStart(String utteranceId, int start, int end) {} + + /** + * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new progress listener. * * @hide */ diff --git a/core/java/android/text/LangId.java b/core/java/android/text/LangId.java new file mode 100644 index 000000000000..ed6e9097d669 --- /dev/null +++ b/core/java/android/text/LangId.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.text; + +/** + * Java wrapper for LangId native library interface. + * This class is used to detect languages in text. + * @hide + */ +public final class LangId { + // TODO: Move this to android.view.textclassifier and make it package-private. + // We'll have to update the native library code to do this. + + static { + System.loadLibrary("smart-selection_jni"); + } + + private final long mModelPtr; + + /** + * Creates a new instance of LangId predictor, using the provided model image. + */ + public LangId(int fd) { + mModelPtr = nativeNew(fd); + } + + /** + * Detects the language for given text. + */ + public String findLanguage(String text) { + return nativeFindLanguage(mModelPtr, text); + } + + /** + * Frees up the allocated memory. + */ + public void close() { + nativeClose(mModelPtr); + } + + private static native long nativeNew(int fd); + + private static native String nativeFindLanguage(long context, String text); + + private static native void nativeClose(long context); +} + diff --git a/core/java/android/text/SmartSelection.java b/core/java/android/text/SmartSelection.java new file mode 100644 index 000000000000..97ef5149cf23 --- /dev/null +++ b/core/java/android/text/SmartSelection.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.text; + +/** + * Java wrapper for SmartSelection native library interface. + * This library is used for detecting entities in text. + * @hide + */ +public final class SmartSelection { + // TODO: Move this to android.view.textclassifier and make it package-private. + // We'll have to update the native library code to do this. + + static { + System.loadLibrary("smart-selection_jni"); + } + + private final long mCtx; + + /** + * Creates a new instance of SmartSelect predictor, using the provided model image, + * given as a file descriptor. + */ + public SmartSelection(int fd) { + mCtx = nativeNew(fd); + } + + /** + * Given a string context and current selection, computes the SmartSelection suggestion. + * + * The begin and end are character indices into the context UTF8 string. selectionBegin is the + * character index where the selection begins, and selectionEnd is the index of one character + * past the selection span. + * + * The return value is an array of two ints: suggested selection beginning and end, with the + * same semantics as the input selectionBeginning and selectionEnd. + */ + public int[] suggest(String context, int selectionBegin, int selectionEnd) { + return nativeSuggest(mCtx, context, selectionBegin, selectionEnd); + } + + /** + * Given a string context and current selection, classifies the type of the selected text. + * + * The begin and end params are character indices in the context string. + * + * Returns the type of the selection, e.g. "email", "address", "phone". + */ + public String classifyText(String context, int selectionBegin, int selectionEnd) { + return nativeClassifyText(mCtx, context, selectionBegin, selectionEnd); + } + + /** + * Frees up the allocated memory. + */ + public void close() { + nativeClose(mCtx); + } + + private static native long nativeNew(int fd); + + private static native int[] nativeSuggest( + long context, String text, int selectionBegin, int selectionEnd); + + private static native String nativeClassifyText( + long context, String text, int selectionBegin, int selectionEnd); + + private static native void nativeClose(long context); +} + diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java index 186d96bce8e1..5f01f7b61bf1 100644 --- a/core/java/android/text/SpannableStringBuilder.java +++ b/core/java/android/text/SpannableStringBuilder.java @@ -21,6 +21,7 @@ import android.graphics.BaseCanvas; import android.graphics.Paint; import android.util.Log; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; @@ -73,8 +74,6 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable mSpanFlags = EmptyArray.INT; mSpanMax = EmptyArray.INT; mSpanOrder = EmptyArray.INT; - mPrioSortBuffer = EmptyArray.INT; - mOrderSortBuffer = EmptyArray.INT; if (text instanceof Spanned) { Spanned sp = (Spanned) text; @@ -856,14 +855,14 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable * @param queryStart Start index. * @param queryEnd End index. * @param kind Class type to search for. - * @param sort If true the results are sorted by the insertion order. + * @param sortByInsertionOrder If true the results are sorted by the insertion order. * @param <T> * @return Array of the spans. Empty array if no results are found. * * @hide */ public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind, - boolean sort) { + boolean sortByInsertionOrder) { if (kind == null) return (T[]) ArrayUtils.emptyArray(Object.class); if (mSpanCount == 0) return ArrayUtils.emptyArray(kind); int count = countSpans(queryStart, queryEnd, kind, treeRoot()); @@ -873,13 +872,15 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable // Safe conversion, but requires a suppressWarning T[] ret = (T[]) Array.newInstance(kind, count); - if (sort) { - mPrioSortBuffer = checkSortBuffer(mPrioSortBuffer, count); - mOrderSortBuffer = checkSortBuffer(mOrderSortBuffer, count); + final int[] prioSortBuffer = sortByInsertionOrder ? obtain(count) : EmptyArray.INT; + final int[] orderSortBuffer = sortByInsertionOrder ? obtain(count) : EmptyArray.INT; + getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, prioSortBuffer, + orderSortBuffer, 0, sortByInsertionOrder); + if (sortByInsertionOrder) { + sort(ret, prioSortBuffer, orderSortBuffer); + recycle(prioSortBuffer); + recycle(orderSortBuffer); } - getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, mPrioSortBuffer, - mOrderSortBuffer, 0, sort); - if (sort) sort(ret, mPrioSortBuffer, mOrderSortBuffer); return ret; } @@ -992,15 +993,63 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable } /** + * Obtain a temporary sort buffer. + * + * @param elementCount the size of the int[] to be returned + * @return an int[] with elementCount length + */ + private static int[] obtain(final int elementCount) { + int[] result = null; + synchronized (sCachedIntBuffer) { + // try finding a tmp buffer with length of at least elementCount + // if not get the first available one + int candidateIndex = -1; + for (int i = sCachedIntBuffer.length - 1; i >= 0; i--) { + if (sCachedIntBuffer[i] != null) { + if (sCachedIntBuffer[i].length >= elementCount) { + candidateIndex = i; + break; + } else if (candidateIndex == -1) { + candidateIndex = i; + } + } + } + + if (candidateIndex != -1) { + result = sCachedIntBuffer[candidateIndex]; + sCachedIntBuffer[candidateIndex] = null; + } + } + result = checkSortBuffer(result, elementCount); + return result; + } + + /** + * Recycle sort buffer. + * + * @param buffer buffer to be recycled + */ + private static void recycle(int[] buffer) { + synchronized (sCachedIntBuffer) { + for (int i = 0; i < sCachedIntBuffer.length; i++) { + if (sCachedIntBuffer[i] == null || buffer.length > sCachedIntBuffer[i].length) { + sCachedIntBuffer[i] = buffer; + break; + } + } + } + } + + /** * Check the size of the buffer and grow if required. * - * @param buffer Buffer to be checked. - * @param size Required size. + * @param buffer buffer to be checked. + * @param size required size. * @return Same buffer instance if the current size is greater than required size. Otherwise a * new instance is created and returned. */ - private final int[] checkSortBuffer(int[] buffer, int size) { - if(size > buffer.length) { + private static int[] checkSortBuffer(int[] buffer, int size) { + if (buffer == null || size > buffer.length) { return ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(size)); } return buffer; @@ -1025,16 +1074,19 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable } for (int i = size - 1; i > 0; i--) { - T v = array[0]; - int prio = priority[0]; - int insertOrder = insertionOrder[0]; + final T tmpSpan = array[0]; array[0] = array[i]; + array[i] = tmpSpan; + + final int tmpPriority = priority[0]; priority[0] = priority[i]; + priority[i] = tmpPriority; + + final int tmpOrder = insertionOrder[0]; insertionOrder[0] = insertionOrder[i]; + insertionOrder[i] = tmpOrder; + siftDown(0, array, i, priority, insertionOrder); - array[i] = v; - priority[i] = prio; - insertionOrder[i] = insertOrder; } } @@ -1050,10 +1102,6 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable */ private final <T> void siftDown(int index, T[] array, int size, int[] priority, int[] insertionOrder) { - T v = array[index]; - int prio = priority[index]; - int insertOrder = insertionOrder[index]; - int left = 2 * index + 1; while (left < size) { if (left < size - 1 && compareSpans(left, left + 1, priority, insertionOrder) < 0) { @@ -1062,15 +1110,22 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (compareSpans(index, left, priority, insertionOrder) >= 0) { break; } + + final T tmpSpan = array[index]; array[index] = array[left]; + array[left] = tmpSpan; + + final int tmpPriority = priority[index]; priority[index] = priority[left]; + priority[left] = tmpPriority; + + final int tmpOrder = insertionOrder[index]; insertionOrder[index] = insertionOrder[left]; + insertionOrder[left] = tmpOrder; + index = left; left = 2 * index + 1; } - array[index] = v; - priority[index] = prio; - insertionOrder[index] = insertOrder; } /** @@ -1704,6 +1759,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable } private static final InputFilter[] NO_FILTERS = new InputFilter[0]; + + @GuardedBy("sCachedIntBuffer") + private static final int[][] sCachedIntBuffer = new int[6][0]; + private InputFilter[] mFilters = NO_FILTERS; private char[] mText; @@ -1717,8 +1776,6 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable private int[] mSpanFlags; private int[] mSpanOrder; // store the order of span insertion private int mSpanInsertCount; // counter for the span insertion - private int[] mPrioSortBuffer; // buffer used to sort getSpans result - private int[] mOrderSortBuffer; // buffer used to sort getSpans result private int mSpanCount; private IdentityHashMap<Object, Integer> mIndexOfSpan; diff --git a/core/java/android/text/TextAssistant.java b/core/java/android/text/TextAssistant.java deleted file mode 100644 index b044981cdd95..000000000000 --- a/core/java/android/text/TextAssistant.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.text; - -/** - * Interface for providing text assistant features. - */ -public interface TextAssistant { - - /** - * NO_OP TextAssistant. This will act as the default TextAssistant until we implement a - * TextClassificationManager. - * @hide - */ - TextAssistant NO_OP = new TextAssistant() { - - private final TextSelection mTextSelection = new TextSelection(); - - @Override - public TextSelection suggestSelection( - CharSequence text, int selectionStartIndex, int selectionEndIndex) { - mTextSelection.mStartIndex = selectionStartIndex; - mTextSelection.mEndIndex = selectionEndIndex; - return mTextSelection; - } - - @Override - public void addLinks(Spannable text, int linkMask) {} - }; - - /** - * Returns suggested text selection indices, recognized types and their associated confidence - * scores. The selections are ordered from highest to lowest scoring. - */ - TextSelection suggestSelection( - CharSequence text, int selectionStartIndex, int selectionEndIndex); - - /** - * Adds assistance clickable spans to the provided text. - */ - void addLinks(Spannable text, int linkMask); -} diff --git a/core/java/android/text/TextClassification.java b/core/java/android/text/TextClassification.java deleted file mode 100644 index bb226daaaeb0..000000000000 --- a/core/java/android/text/TextClassification.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.text; - -import java.util.Collections; -import java.util.Map; - -/** - * Information about entities that a specific piece of text is classified as. - */ -public class TextClassification { - - /** @hide */ - public static final TextClassification NO_OP = new TextClassification(); - - private Map<String, Float> mTypeConfidence = Collections.unmodifiableMap(Collections.EMPTY_MAP); - - /** - * Returns a map of text classification types to their respective confidence scores. - * The scores range from 0 (low confidence) to 1 (high confidence). The items are ordered from - * high scoring items to low scoring items. - */ - public Map<String, Float> getTypeConfidence() { - return mTypeConfidence; - } -} diff --git a/core/java/android/text/TextClassificationManager.java b/core/java/android/text/TextClassificationManager.java deleted file mode 100644 index d4548f04e0a0..000000000000 --- a/core/java/android/text/TextClassificationManager.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.text; - -import android.annotation.NonNull; - -import java.util.Collections; -import java.util.List; - -/** - * Interface to the text classification service. - * This class uses machine learning techniques to infer things about text. - * Unless otherwise stated, methods of this class are blocking operations and should most likely not - * be called on the UI thread. - * - * <p> You do not instantiate this class directly; instead, retrieve it through - * {@link android.content.Context#getSystemService}. - * - * The TextClassificationManager serves as the default TextAssistant if none has been set. - * @see android.app.Activity#setTextAssistant(TextAssistant). - */ -public final class TextClassificationManager implements TextAssistant { - // TODO: Consider not making this class implement TextAssistant. - - /** @hide */ - public TextClassificationManager() {} - - /** - * Returns information containing languages that were detected in the provided text. - * This is a blocking operation and should most likely not be called on the UI thread. - */ - public List<TextLanguage> detectLanguages(@NonNull CharSequence text) { - // TODO: Implement this using the cld3 library. - return Collections.emptyList(); - } - - @Override - public TextSelection suggestSelection( - @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex) { - // TODO: Implement. - return TextAssistant.NO_OP.suggestSelection(text, selectionStartIndex, selectionEndIndex); - } - - @Override - public void addLinks(@NonNull Spannable text, int linkMask) { - // TODO: Implement. - } -} diff --git a/core/java/android/text/TextLanguage.java b/core/java/android/text/TextLanguage.java deleted file mode 100644 index eb834f120dee..000000000000 --- a/core/java/android/text/TextLanguage.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.text; - -import android.annotation.NonNull; - -import com.android.internal.util.Preconditions; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * Specifies detected languages for a section of text indicated by a start and end index. - */ -public final class TextLanguage { - - private final int mStartIndex; - private final int mEndIndex; - private final Map<String, Float> mLanguageConfidence; - - /** - * Initializes a TextLanguage object. - * - * @param startIndex the start index of the detected languages in the text provided to generate - * this object. - * @param endIndex the end index of the detected languages in the text provided to generate this - * object. - * @param languageConfidence a map of detected language to confidence score. The language string - * is a BCP-47 language tag. - * @throws NullPointerException if languageConfidence is null or contains a null key or value. - */ - public TextLanguage(int startIndex, int endIndex, - @NonNull Map<String, Float> languageConfidence) { - mStartIndex = startIndex; - mEndIndex = endIndex; - - Map<String, Float> map = new LinkedHashMap<>(); - Preconditions.checkNotNull(languageConfidence).entrySet().stream() - .sorted(Map.Entry.comparingByValue()) - .forEach(entry -> map.put( - Preconditions.checkNotNull(entry.getKey()), - Preconditions.checkNotNull(entry.getValue()))); - mLanguageConfidence = Collections.unmodifiableMap(map); - } - - /** - * Returns the start index of the detected languages in the text provided to generate this - * object. - */ - public int getStartIndex() { - return mStartIndex; - } - - /** - * Returns the end index of the detected languages in the text provided to generate this object. - */ - public int getEndIndex() { - return mEndIndex; - } - - /** - * Returns an unmodifiable map of detected language to confidence score. The map entries are - * ordered from high confidence score (1) to low confidence score (0). The language string is a - * BCP-47 language tag. - */ - @NonNull - public Map<String, Float> getLanguageConfidence() { - return mLanguageConfidence; - } -} diff --git a/core/java/android/text/TextSelection.java b/core/java/android/text/TextSelection.java deleted file mode 100644 index 9400458582c7..000000000000 --- a/core/java/android/text/TextSelection.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.text; - -/** - * Text selection information. - */ -public class TextSelection { - - /** @hide */ - int mStartIndex; - /** @hide */ - int mEndIndex; - - private TextClassification mTextClassification = TextClassification.NO_OP; - - /** - * Returns the start index of the text selection. - */ - public int getSelectionStartIndex() { - return mStartIndex; - } - - /** - * Returns the end index of the text selection. - */ - public int getSelectionEndIndex() { - return mEndIndex; - } - - /** - * Returns information about what the text selection is classified as. - */ - public TextClassification getTextClassification() { - return mTextClassification; - } -} diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 19edb5c830d6..c789f8c16324 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -135,9 +135,6 @@ interface IWindowManager // ids that were affected by the update, ActivityManager should resize these stacks. int[] setNewDisplayOverrideConfiguration(in Configuration overrideConfig, int displayId); - // Retrieves the new bounds after the configuration update evaluated by window manager. - Rect getBoundsForNewConfiguration(int stackId); - void startFreezingScreen(int exitAnim, int enterAnim); void stopFreezingScreen(); diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index b0826a8e3f70..f3ebcb4b326b 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -360,7 +360,6 @@ public final class ThreadedRenderer { void destroy() { mInitialized = false; updateEnabledState(null); - mRootNode.discardDisplayList(); nDestroy(mNativeProxy, mRootNode.mNativeRenderNode); } @@ -492,12 +491,20 @@ public final class ThreadedRenderer { */ void destroyHardwareResources(View view) { destroyResources(view); - mRootNode.discardDisplayList(); nDestroyHardwareResources(mNativeProxy); } private static void destroyResources(View view) { view.destroyHardwareResources(); + + if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup) view; + + int count = group.getChildCount(); + for (int i = 0; i < count; i++) { + destroyResources(group.getChildAt(i)); + } + } } /** diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 13555f47c2c5..547f7d8cb991 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -857,22 +857,39 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static boolean sCascadedDragDrop; + /** @hide */ + @IntDef({NOT_FOCUSABLE, FOCUSABLE, FOCUSABLE_AUTO}) + @Retention(RetentionPolicy.SOURCE) + public @interface Focusable {} + + /** + * This view does not want keystrokes. + * <p> + * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code + * android:focusable}. + */ + public static final int NOT_FOCUSABLE = 0x00000000; + /** - * This view does not want keystrokes. Use with TAKES_FOCUS_MASK when - * calling setFlags. + * This view wants keystrokes. + * <p> + * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code + * android:focusable}. */ - private static final int NOT_FOCUSABLE = 0x00000000; + public static final int FOCUSABLE = 0x00000001; /** - * This view wants keystrokes. Use with TAKES_FOCUS_MASK when calling - * setFlags. + * This view determines focusability automatically. This is the default. + * <p> + * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code + * android:focusable}. */ - private static final int FOCUSABLE = 0x00000001; + public static final int FOCUSABLE_AUTO = 0x00000010; /** * Mask for use with setFlags indicating bits used for focus. */ - private static final int FOCUSABLE_MASK = 0x00000001; + private static final int FOCUSABLE_MASK = 0x00000011; /** * This view will adjust its padding to fit sytem windows (e.g. status bar) @@ -4136,7 +4153,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public View(Context context) { mContext = context; mResources = context != null ? context.getResources() : null; - mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED; + mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | FOCUSABLE_AUTO; // Set some flags defaults mPrivateFlags2 = (LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) | @@ -4322,6 +4339,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; + // Set default values. + viewFlagValues |= FOCUSABLE_AUTO; + viewFlagMasks |= FOCUSABLE_AUTO; + final int N = a.getIndexCount(); for (int i = 0; i < N; i++) { int attr = a.getIndex(i); @@ -4434,8 +4455,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } break; case com.android.internal.R.styleable.View_focusable: - if (a.getBoolean(attr, false)) { - viewFlagValues |= FOCUSABLE; + viewFlagValues = (viewFlagValues & ~FOCUSABLE_MASK) | getFocusableAttribute(a); + if ((viewFlagValues & FOCUSABLE_AUTO) == 0) { viewFlagMasks |= FOCUSABLE_MASK; } break; @@ -5006,7 +5027,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case GONE: out.append('G'); break; default: out.append('.'); break; } - out.append((mViewFlags&FOCUSABLE_MASK) == FOCUSABLE ? 'F' : '.'); + out.append((mViewFlags & FOCUSABLE) == FOCUSABLE ? 'F' : '.'); out.append((mViewFlags&ENABLED_MASK) == ENABLED ? 'E' : '.'); out.append((mViewFlags&DRAW_MASK) == WILL_NOT_DRAW ? '.' : 'D'); out.append((mViewFlags&SCROLLBARS_HORIZONTAL) != 0 ? 'H' : '.'); @@ -8453,20 +8474,39 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Set whether this view can receive the focus. - * + * <p> * Setting this to false will also ensure that this view is not focusable * in touch mode. * * @param focusable If true, this view can receive the focus. * * @see #setFocusableInTouchMode(boolean) + * @see #setFocusable(int) * @attr ref android.R.styleable#View_focusable */ public void setFocusable(boolean focusable) { - if (!focusable) { + setFocusable(focusable ? FOCUSABLE : NOT_FOCUSABLE); + } + + /** + * Sets whether this view can receive focus. + * <p> + * Setting this to {@link #FOCUSABLE_AUTO} tells the framework to determine focusability + * automatically based on the view's interactivity. This is the default. + * <p> + * Setting this to NOT_FOCUSABLE will ensure that this view is also not focusable + * in touch mode. + * + * @param focusable One of {@link #NOT_FOCUSABLE}, {@link #FOCUSABLE}, + * or {@link #FOCUSABLE_AUTO}. + * @see #setFocusableInTouchMode(boolean) + * @attr ref android.R.styleable#View_focusable + */ + public void setFocusable(@Focusable int focusable) { + if ((focusable & (FOCUSABLE_AUTO | FOCUSABLE)) == 0) { setFlags(0, FOCUSABLE_IN_TOUCH_MODE); } - setFlags(focusable ? FOCUSABLE : NOT_FOCUSABLE, FOCUSABLE_MASK); + setFlags(focusable, FOCUSABLE_MASK); } /** @@ -9056,14 +9096,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** - * Returns whether this View is able to take focus. + * Returns whether this View is currently able to take focus. * * @return True if this view can take focus, or false otherwise. - * @attr ref android.R.styleable#View_focusable */ @ViewDebug.ExportedProperty(category = "focus") public final boolean isFocusable() { - return FOCUSABLE == (mViewFlags & FOCUSABLE_MASK); + return FOCUSABLE == (mViewFlags & FOCUSABLE); + } + + /** + * Returns the focusable setting for this view. + * + * @return One of {@link #NOT_FOCUSABLE}, {@link #FOCUSABLE}, or {@link #FOCUSABLE_AUTO}. + * @attr ref android.R.styleable#View_focusable + */ + @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.IntToString(from = NOT_FOCUSABLE, to = "NOT_FOCUSABLE"), + @ViewDebug.IntToString(from = FOCUSABLE, to = "FOCUSABLE"), + @ViewDebug.IntToString(from = FOCUSABLE_AUTO, to = "FOCUSABLE_AUTO") + }) + @Focusable + public int getFocusable() { + return (mViewFlags & FOCUSABLE_AUTO) > 0 ? FOCUSABLE_AUTO : mViewFlags & FOCUSABLE; } /** @@ -9615,8 +9670,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) { // need to be focusable - if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE || - (mViewFlags & VISIBILITY_MASK) != VISIBLE) { + if ((mViewFlags & FOCUSABLE) != FOCUSABLE + || (mViewFlags & VISIBILITY_MASK) != VISIBLE) { return false; } @@ -11970,14 +12025,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } int privateFlags = mPrivateFlags; + // If focusable is auto, update the FOCUSABLE bit. + if (((mViewFlags & FOCUSABLE_AUTO) != 0) + && (changed & (FOCUSABLE_MASK | CLICKABLE | FOCUSABLE_IN_TOUCH_MODE)) != 0) { + int newFocus = NOT_FOCUSABLE; + if ((mViewFlags & (CLICKABLE | FOCUSABLE_IN_TOUCH_MODE)) != 0) { + newFocus = FOCUSABLE; + } else { + mViewFlags = (mViewFlags & ~FOCUSABLE_IN_TOUCH_MODE); + } + mViewFlags = (mViewFlags & ~FOCUSABLE) | newFocus; + int focusChanged = (old & FOCUSABLE) ^ (newFocus & FOCUSABLE); + changed = (changed & ~FOCUSABLE) | focusChanged; + } + /* Check if the FOCUSABLE bit has changed */ - if (((changed & FOCUSABLE_MASK) != 0) && - ((privateFlags & PFLAG_HAS_BOUNDS) !=0)) { - if (((old & FOCUSABLE_MASK) == FOCUSABLE) + if (((changed & FOCUSABLE) != 0) && ((privateFlags & PFLAG_HAS_BOUNDS) != 0)) { + if (((old & FOCUSABLE) == FOCUSABLE) && ((privateFlags & PFLAG_FOCUSED) != 0)) { /* Give up focus if we are no longer focusable */ clearFocus(); - } else if (((old & FOCUSABLE_MASK) == NOT_FOCUSABLE) + } else if (((old & FOCUSABLE) == NOT_FOCUSABLE) && ((privateFlags & PFLAG_FOCUSED) == 0)) { /* * Tell the view system that we are now available to take focus @@ -12120,7 +12188,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if (accessibilityEnabled) { - if ((changed & FOCUSABLE_MASK) != 0 || (changed & VISIBILITY_MASK) != 0 + if ((changed & FOCUSABLE) != 0 || (changed & VISIBILITY_MASK) != 0 || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0 || (changed & CONTEXT_CLICKABLE) != 0) { if (oldIncludeForAccessibility != includeForAccessibility()) { @@ -16530,12 +16598,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // safe to free its copy of the display list as it knows that we will // push an updated DisplayList if we try to draw again resetDisplayList(); - if (mOverlay != null) { - mOverlay.getOverlayView().destroyHardwareResources(); - } - if (mGhostView != null) { - mGhostView.destroyHardwareResources(); - } } /** @@ -16706,9 +16768,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } private void resetDisplayList() { - mRenderNode.discardDisplayList(); + if (mRenderNode.isValid()) { + mRenderNode.discardDisplayList(); + } - if (mBackgroundRenderNode != null) { + if (mBackgroundRenderNode != null && mBackgroundRenderNode.isValid()) { mBackgroundRenderNode.discardDisplayList(); } } @@ -18049,7 +18113,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static String printFlags(int flags) { String output = ""; int numFlags = 0; - if ((flags & FOCUSABLE_MASK) == FOCUSABLE) { + if ((flags & FOCUSABLE) == FOCUSABLE) { output += "TAKES_FOCUS"; numFlags++; } @@ -24695,6 +24759,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, ViewConfiguration.getLongPressTooltipHideTimeout()); } + private int getFocusableAttribute(TypedArray attributes) { + TypedValue val = new TypedValue(); + if (attributes.getValue(com.android.internal.R.styleable.View_focusable, val)) { + if (val.type == TypedValue.TYPE_INT_BOOLEAN) { + return (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE); + } else { + return val.data; + } + } else { + return FOCUSABLE_AUTO; + } + } + /** * @return The content view of the tooltip popup currently being shown, or null if the tooltip * is not showing. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 480741eb988a..ba73c5f1be60 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3433,16 +3433,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager super.dispatchDetachedFromWindow(); } - /** @hide */ - @Override - protected void destroyHardwareResources() { - super.destroyHardwareResources(); - int count = getChildCount(); - for (int i = 0; i < count; i++) { - getChildAt(i).destroyHardwareResources(); - } - } - /** * @hide */ diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 8bc988d8de1a..e2bdd976980a 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -27,6 +27,7 @@ import android.annotation.Nullable; import android.annotation.StyleRes; import android.annotation.SystemApi; import android.content.Context; +import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; @@ -1128,6 +1129,28 @@ public abstract class Window { } /** + * <p>Set the color mode of the window. Setting the color mode might + * override the window's pixel {@link WindowManager.LayoutParams#format format}.</p> + * + * <p>The color mode must be one of {@link ActivityInfo#COLOR_MODE_DEFAULT}, + * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or {@link ActivityInfo#COLOR_MODE_HDR}.</p> + */ + public void setColorMode(@ActivityInfo.ColorMode int colorMode) { + final WindowManager.LayoutParams attrs = getAttributes(); + attrs.setColorMode(colorMode); + dispatchWindowAttributesChanged(attrs); + } + + /** + * Returns the color mode of the window, one of {@link ActivityInfo#COLOR_MODE_DEFAULT}, + * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or {@link ActivityInfo#COLOR_MODE_HDR}. + */ + @ActivityInfo.ColorMode + public int getColorMode() { + return getAttributes().getColorMode(); + } + + /** * Set the amount of dim behind the window when using * {@link WindowManager.LayoutParams#FLAG_DIM_BEHIND}. This overrides * the default dim amount of that is selected by the Window based on diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index e5a6ebdfb9e0..bf840e547079 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -22,7 +22,6 @@ import android.app.KeyguardManager; import android.app.Presentation; import android.content.Context; import android.content.pm.ActivityInfo; -import android.graphics.GraphicBuffer; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.IBinder; @@ -1577,7 +1576,8 @@ public interface WindowManager extends ViewManager { /** * The desired bitmap format. May be one of the constants in - * {@link android.graphics.PixelFormat}. Default is OPAQUE. + * {@link android.graphics.PixelFormat}. The choice of format + * might be overridden by {@link #setColorMode(int)}. Default is OPAQUE. */ public int format; @@ -1833,6 +1833,17 @@ public interface WindowManager extends ViewManager { */ public long hideTimeoutMilliseconds = -1; + /** + * The color mode requested by this window. The target display may + * not be able to honor the request. When the color mode is not set + * to {@link ActivityInfo#COLOR_MODE_DEFAULT}, it might override the + * pixel format specified in {@link #format}. + * + * @hide + */ + @ActivityInfo.ColorMode + private int mColorMode = ActivityInfo.COLOR_MODE_DEFAULT; + public LayoutParams() { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); type = TYPE_APPLICATION; @@ -1912,6 +1923,31 @@ public interface WindowManager extends ViewManager { preservePreviousSurfaceInsets = preservePrevious; } + /** + * <p>Set the color mode of the window. Setting the color mode might + * override the window's pixel {@link WindowManager.LayoutParams#format format}.</p> + * + * <p>The color mode must be one of {@link ActivityInfo#COLOR_MODE_DEFAULT}, + * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or + * {@link ActivityInfo#COLOR_MODE_HDR}.</p> + * + * @see #getColorMode() + */ + public void setColorMode(@ActivityInfo.ColorMode int colorMode) { + mColorMode = colorMode; + } + + /** + * Returns the color mode of the window, one of {@link ActivityInfo#COLOR_MODE_DEFAULT}, + * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or {@link ActivityInfo#COLOR_MODE_HDR}. + * + * @see #setColorMode(int) + */ + @ActivityInfo.ColorMode + public int getColorMode() { + return mColorMode; + } + /** @hide */ @SystemApi public final void setUserActivityTimeout(long timeout) { @@ -2268,9 +2304,11 @@ public interface WindowManager extends ViewManager { sb.append(','); sb.append(y); sb.append(")("); - sb.append((width== MATCH_PARENT ?"fill":(width==WRAP_CONTENT?"wrap":width))); + sb.append((width == MATCH_PARENT ? "fill" : (width == WRAP_CONTENT + ? "wrap" : String.valueOf(width)))); sb.append('x'); - sb.append((height== MATCH_PARENT ?"fill":(height==WRAP_CONTENT?"wrap":height))); + sb.append((height == MATCH_PARENT ? "fill" : (height == WRAP_CONTENT + ? "wrap" : String.valueOf(height)))); sb.append(")"); if (horizontalMargin != 0) { sb.append(" hm="); @@ -2367,6 +2405,7 @@ public interface WindowManager extends ViewManager { sb.append(" needsMenuKey="); sb.append(needsMenuKey); } + sb.append(" colorMode=").append(mColorMode); sb.append('}'); return sb.toString(); } diff --git a/core/java/android/view/textclassifier/EntityConfidence.java b/core/java/android/view/textclassifier/EntityConfidence.java new file mode 100644 index 000000000000..7aab71fac76b --- /dev/null +++ b/core/java/android/view/textclassifier/EntityConfidence.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import android.annotation.FloatRange; +import android.annotation.NonNull; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Helper object for setting and getting entity scores for classified text. + * + * @param <T> the entity type. + * @hide + */ +final class EntityConfidence<T> { + + private final Map<T, Float> mEntityConfidence = new HashMap<>(); + + private final Comparator<T> mEntityComparator = (e1, e2) -> { + float score1 = mEntityConfidence.get(e1); + float score2 = mEntityConfidence.get(e2); + if (score1 > score2) { + return 1; + } + if (score1 < score2) { + return -1; + } + return 0; + }; + + EntityConfidence() {} + + EntityConfidence(@NonNull EntityConfidence<T> source) { + Preconditions.checkNotNull(source); + mEntityConfidence.putAll(source.mEntityConfidence); + } + + /** + * Sets an entity type for the classified text and assigns a confidence score. + * + * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence). + * 0 implies the entity does not exist for the classified text. + * Values greater than 1 are clamped to 1. + */ + public void setEntityType( + @NonNull T type, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) { + Preconditions.checkNotNull(type); + if (confidenceScore > 0) { + mEntityConfidence.put(type, Math.min(1, confidenceScore)); + } else { + mEntityConfidence.remove(type); + } + } + + /** + * Returns an immutable list of entities found in the classified text ordered from + * high confidence to low confidence. + */ + @NonNull + public List<T> getEntities() { + List<T> entities = new ArrayList<>(mEntityConfidence.size()); + entities.addAll(mEntityConfidence.keySet()); + entities.sort(mEntityComparator); + return Collections.unmodifiableList(entities); + } + + /** + * Returns the confidence score for the specified entity. The value ranges from + * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the + * classified text. + */ + @FloatRange(from = 0.0, to = 1.0) + public float getConfidenceScore(T entity) { + if (mEntityConfidence.containsKey(entity)) { + return mEntityConfidence.get(entity); + } + return 0; + } + + @Override + public String toString() { + return mEntityConfidence.toString(); + } +} diff --git a/core/java/android/view/textclassifier/LinksInfo.java b/core/java/android/view/textclassifier/LinksInfo.java new file mode 100644 index 000000000000..3acbdc0261bd --- /dev/null +++ b/core/java/android/view/textclassifier/LinksInfo.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import android.annotation.NonNull; + +/** + * Link information that can be applied to text. See: {@link #apply(CharSequence)}. + * Typical implementations of this interface will annotate spannable text with e.g + * {@link android.text.style.ClickableSpan}s or other annotations. + */ +public interface LinksInfo { + + /** + * @hide + */ + LinksInfo NO_OP = text -> false; + + /** + * Applies link annotations to the specified text. + * These annotations are not guaranteed to be applied. For example, the annotations may not be + * applied if the text has changed from what it was when the link spec was generated for it. + * + * @return Whether or not the link annotations were successfully applied. + */ + boolean apply(@NonNull CharSequence text); +} diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java new file mode 100644 index 000000000000..4673c50cddc9 --- /dev/null +++ b/core/java/android/view/textclassifier/TextClassificationManager.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.ParcelFileDescriptor; +import android.text.LangId; +import android.util.Log; + +import com.android.internal.util.Preconditions; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +/** + * Interface to the text classification service. + * + * <p>You do not instantiate this class directly; instead, retrieve it through + * {@link android.content.Context#getSystemService}. + */ +public final class TextClassificationManager { + + private static final String LOG_TAG = "TextClassificationManager"; + + private final Context mContext; + // TODO: Implement a way to close the file descriptor. + private ParcelFileDescriptor mFd; + private TextClassifier mDefault; + private LangId mLangId; + + /** @hide */ + public TextClassificationManager(Context context) { + mContext = Preconditions.checkNotNull(context); + } + + /** + * Returns the default text classifier. + */ + public TextClassifier getDefaultTextClassifier() { + if (mDefault == null) { + try { + mFd = ParcelFileDescriptor.open( + new File("/etc/assistant/smart-selection.model"), + ParcelFileDescriptor.MODE_READ_ONLY); + mDefault = new TextClassifierImpl(mContext, mFd); + } catch (FileNotFoundException e) { + Log.e(LOG_TAG, "Error accessing 'text classifier selection' model file.", e); + mDefault = TextClassifier.NO_OP; + } + } + return mDefault; + } + + /** + * Returns information containing languages that were detected in the provided text. + * This is a blocking operation you should avoid calling it on the UI thread. + * + * @throws IllegalArgumentException if text is null + */ + public List<TextLanguage> detectLanguages(@NonNull CharSequence text) { + Preconditions.checkArgument(text != null); + try { + if (text.length() > 0) { + final String language = getLanguageDetector().findLanguage(text.toString()); + final Locale locale = new Locale.Builder().setLanguageTag(language).build(); + return Collections.unmodifiableList(Arrays.asList( + new TextLanguage.Builder(0, text.length()) + .setLanguage(locale, 1.0f /* confidence */) + .build())); + } + } catch (Throwable t) { + // Avoid throwing from this method. Log the error. + Log.e(LOG_TAG, "Error detecting languages for text. Returning empty result.", t); + } + // Getting here means something went wrong. Return an empty result. + return Collections.emptyList(); + } + + private LangId getLanguageDetector() { + if (mLangId == null) { + // TODO: Use a file descriptor as soon as we start to depend on a model file + // for language detection. + mLangId = new LangId(0); + } + return mLangId; + } +} diff --git a/core/java/android/view/textclassifier/TextClassificationResult.java b/core/java/android/view/textclassifier/TextClassificationResult.java new file mode 100644 index 000000000000..6af0efb5fd08 --- /dev/null +++ b/core/java/android/view/textclassifier/TextClassificationResult.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.view.View.OnClickListener; +import android.view.textclassifier.TextClassifier.EntityType; + +import com.android.internal.util.Preconditions; + +import java.util.List; + +/** + * Information for generating a widget to handle classified text. + */ +public final class TextClassificationResult { + + /** + * @hide + */ + static final TextClassificationResult EMPTY = new TextClassificationResult.Builder().build(); + + @NonNull private final String mText; + @Nullable private final Drawable mIcon; + @Nullable private final String mLabel; + @Nullable private final Intent mIntent; + @Nullable private final OnClickListener mOnClickListener; + @NonNull private final EntityConfidence<String> mEntityConfidence; + @NonNull private final List<String> mEntities; + + private TextClassificationResult( + @NonNull String text, + Drawable icon, + String label, + Intent intent, + OnClickListener onClickListener, + @NonNull EntityConfidence<String> entityConfidence) { + mText = text; + mIcon = icon; + mLabel = label; + mIntent = intent; + mOnClickListener = onClickListener; + mEntityConfidence = new EntityConfidence<>(entityConfidence); + mEntities = mEntityConfidence.getEntities(); + } + + /** + * Gets the classified text. + */ + @NonNull + public String getText() { + return mText; + } + + /** + * Returns the number of entities found in the classified text. + */ + @IntRange(from = 0) + public int getEntityCount() { + return mEntities.size(); + } + + /** + * Returns the entity at the specified index. Entities are ordered from high confidence + * to low confidence. + * + * @throws IndexOutOfBoundsException if the specified index is out of range. + * @see #getEntityCount() for the number of entities available. + */ + @NonNull + public @EntityType String getEntity(int index) { + return mEntities.get(index); + } + + /** + * Returns the confidence score for the specified entity. The value ranges from + * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the + * classified text. + */ + @FloatRange(from = 0.0, to = 1.0) + public float getConfidenceScore(@EntityType String entity) { + return mEntityConfidence.getConfidenceScore(entity); + } + + /** + * Returns an icon that may be rendered on a widget used to act on the classified text. + */ + @Nullable + public Drawable getIcon() { + return mIcon; + } + + /** + * Returns a label that may be rendered on a widget used to act on the classified text. + */ + @Nullable + public CharSequence getLabel() { + return mLabel; + } + + /** + * Returns an intent that may be fired to act on the classified text. + */ + @Nullable + public Intent getIntent() { + return mIntent; + } + + /** + * Returns an OnClickListener that may be triggered to act on the classified text. + */ + @Nullable + public OnClickListener getOnClickListener() { + return mOnClickListener; + } + + @Override + public String toString() { + return String.format("TextClassificationResult {" + + "text=%s, entities=%s, label=%s, intent=%s}", + mText, mEntityConfidence, mLabel, mIntent); + } + + /** + * Creates an OnClickListener that starts an activity with the specified intent. + * + * @throws IllegalArgumentException if context or intent is null + * @hide + */ + @NonNull + public static OnClickListener createStartActivityOnClick( + @NonNull final Context context, @NonNull final Intent intent) { + Preconditions.checkArgument(context != null); + Preconditions.checkArgument(intent != null); + return v -> context.startActivity(intent); + } + + /** + * Builder for building {@link TextClassificationResult}s. + */ + public static final class Builder { + + @NonNull private String mText; + @Nullable private Drawable mIcon; + @Nullable private String mLabel; + @Nullable private Intent mIntent; + @Nullable private OnClickListener mOnClickListener; + @NonNull private final EntityConfidence<String> mEntityConfidence = + new EntityConfidence<>(); + + /** + * Sets the classified text. + */ + public Builder setText(@NonNull String text) { + mText = Preconditions.checkNotNull(text); + return this; + } + + /** + * Sets an entity type for the classification result and assigns a confidence score. + * + * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence). + * 0 implies the entity does not exist for the classified text. + * Values greater than 1 are clamped to 1. + */ + public Builder setEntityType( + @NonNull @EntityType String type, + @FloatRange(from = 0.0, to = 1.0)float confidenceScore) { + mEntityConfidence.setEntityType(type, confidenceScore); + return this; + } + + /** + * Sets an icon that may be rendered on a widget used to act on the classified text. + */ + public Builder setIcon(@Nullable Drawable icon) { + mIcon = icon; + return this; + } + + /** + * Sets a label that may be rendered on a widget used to act on the classified text. + */ + public Builder setLabel(@Nullable String label) { + mLabel = label; + return this; + } + + /** + * Sets an intent that may be fired to act on the classified text. + */ + public Builder setIntent(@Nullable Intent intent) { + mIntent = intent; + return this; + } + + /** + * Sets an OnClickListener that may be triggered to act on the classified text. + */ + public Builder setOnClickListener(@Nullable OnClickListener onClickListener) { + mOnClickListener = onClickListener; + return this; + } + + /** + * Builds an returns a {@link TextClassificationResult}. + */ + public TextClassificationResult build() { + return new TextClassificationResult( + mText, mIcon, mLabel, mIntent, mOnClickListener, mEntityConfidence); + } + } +} diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java new file mode 100644 index 000000000000..b84e2ae5e4fd --- /dev/null +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.StringDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Interface for providing text classification related features. + * + * <p>Unless otherwise stated, methods of this interface are blocking operations and you should + * avoid calling them on the UI thread. + */ +public interface TextClassifier { + + String TYPE_OTHER = "other"; + String TYPE_EMAIL = "email"; + String TYPE_PHONE = "phone"; + String TYPE_ADDRESS = "address"; + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + TYPE_OTHER, TYPE_EMAIL, TYPE_PHONE, TYPE_ADDRESS + }) + @interface EntityType {} + + /** + * No-op TextClassifier. + * This may be used to turn off TextClassifier features. + */ + TextClassifier NO_OP = new TextClassifier() { + + @Override + public TextSelection suggestSelection( + CharSequence text, int selectionStartIndex, int selectionEndIndex) { + return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build(); + } + + @Override + public TextClassificationResult getTextClassificationResult( + CharSequence text, int startIndex, int endIndex) { + return TextClassificationResult.EMPTY; + } + + @Override + public LinksInfo getLinks(CharSequence text, int linkMask) { + return LinksInfo.NO_OP; + } + }; + + /** + * Returns suggested text selection indices, recognized types and their associated confidence + * scores. The selections are ordered from highest to lowest scoring. + * + * @param text text providing context for the selected text (which is specified + * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex) + * @param selectionStartIndex start index of the selected part of text + * @param selectionEndIndex end index of the selected part of text + * + * @throws IllegalArgumentException if text is null; selectionStartIndex is negative; + * selectionEndIndex is greater than text.length() or less than selectionStartIndex + */ + @NonNull + TextSelection suggestSelection( + @NonNull CharSequence text, + @IntRange(from = 0) int selectionStartIndex, + @IntRange(from = 0) int selectionEndIndex); + + /** + * Returns a {@link TextClassificationResult} object that can be used to generate a widget for + * handling the classified text. + * + * @param text text providing context for the text to classify (which is specified + * by the sub sequence starting at startIndex and ending at endIndex) + * @param startIndex start index of the text to classify + * @param endIndex end index of the text to classify + * + * @throws IllegalArgumentException if text is null; startIndex is negative; + * endIndex is greater than text.length() or less than startIndex + */ + @NonNull + TextClassificationResult getTextClassificationResult( + @NonNull CharSequence text, int startIndex, int endIndex); + + /** + * Returns a {@link LinksInfo} that may be applied to the text to annotate it with links + * information. + * + * @param text the text to generate annotations for + * @param linkMask See {@link android.text.util.Linkify} for a list of linkMasks that may be + * specified. Subclasses of this interface may specify additional linkMasks + * + * @throws IllegalArgumentException if text is null + */ + LinksInfo getLinks(@NonNull CharSequence text, int linkMask); +} diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java new file mode 100644 index 000000000000..72796cf160d0 --- /dev/null +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.text.SmartSelection; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.util.Preconditions; + +import java.io.FileNotFoundException; + +/** + * Default implementation of the {@link TextClassifier} interface. + * + * <p>This class uses machine learning to recognize entities in text. + * Unless otherwise stated, methods of this class are blocking operations and should most + * likely not be called on the UI thread. + * + * @hide + */ +final class TextClassifierImpl implements TextClassifier { + + private static final String LOG_TAG = "TextClassifierImpl"; + + private final Context mContext; + private final ParcelFileDescriptor mFd; + private SmartSelection mSmartSelection; + + TextClassifierImpl(Context context, ParcelFileDescriptor fd) { + mContext = Preconditions.checkNotNull(context); + mFd = Preconditions.checkNotNull(fd); + } + + @Override + public TextSelection suggestSelection( + @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex) { + validateInput(text, selectionStartIndex, selectionEndIndex); + try { + if (text.length() > 0) { + final String string = text.toString(); + final int[] startEnd = getSmartSelection() + .suggest(string, selectionStartIndex, selectionEndIndex); + final int start = startEnd[0]; + final int end = startEnd[1]; + if (start >= 0 && end <= string.length() && start <= end) { + final String type = getSmartSelection().classifyText(string, start, end); + return new TextSelection.Builder(start, end) + .setEntityType(type, 1.0f) + .build(); + } else { + // We can not trust the result. Log the issue and ignore the result. + Log.d(LOG_TAG, "Got bad indices for input text. Ignoring result."); + } + } + } catch (Throwable t) { + // Avoid throwing from this method. Log the error. + Log.e(LOG_TAG, + "Error suggesting selection for text. No changes to selection suggested.", + t); + } + // Getting here means something went wrong, return a NO_OP result. + return TextClassifier.NO_OP.suggestSelection( + text, selectionStartIndex, selectionEndIndex); + } + + @Override + public TextClassificationResult getTextClassificationResult( + @NonNull CharSequence text, int startIndex, int endIndex) { + validateInput(text, startIndex, endIndex); + try { + if (text.length() > 0) { + final CharSequence classified = text.subSequence(startIndex, endIndex); + String type = getSmartSelection() + .classifyText(text.toString(), startIndex, endIndex); + if (!TextUtils.isEmpty(type)) { + type = type.toLowerCase().trim(); + // TODO: Added this log for debug only. Remove before release. + Log.d(LOG_TAG, String.format("Classification type: %s", type)); + final Intent intent; + final String title; + switch (type) { + case TextClassifier.TYPE_EMAIL: + intent = new Intent(Intent.ACTION_SENDTO); + intent.setData(Uri.parse(String.format("mailto:%s", text))); + title = mContext.getString(com.android.internal.R.string.email); + return createClassificationResult(classified, type, intent, title); + case TextClassifier.TYPE_PHONE: + intent = new Intent(Intent.ACTION_DIAL); + intent.setData(Uri.parse(String.format("tel:%s", text))); + title = mContext.getString(com.android.internal.R.string.dial); + return createClassificationResult(classified, type, intent, title); + case TextClassifier.TYPE_ADDRESS: + intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(String.format("geo:0,0?q=%s", text))); + title = mContext.getString(com.android.internal.R.string.map); + return createClassificationResult(classified, type, intent, title); + default: + // No classification type found. Return a no-op result. + break; + // TODO: Add other classification types. + } + } + } + } catch (Throwable t) { + // Avoid throwing from this method. Log the error. + Log.e(LOG_TAG, "Error getting assist info.", t); + } + // Getting here means something went wrong, return a NO_OP result. + return TextClassifier.NO_OP.getTextClassificationResult(text, startIndex, endIndex); + } + + @Override + public LinksInfo getLinks(@NonNull CharSequence text, int linkMask) { + // TODO: Implement + return TextClassifier.NO_OP.getLinks(text, linkMask); + } + + private synchronized SmartSelection getSmartSelection() throws FileNotFoundException { + if (mSmartSelection == null) { + mSmartSelection = new SmartSelection(mFd.getFd()); + } + return mSmartSelection; + } + + private TextClassificationResult createClassificationResult( + CharSequence text, String type, Intent intent, String label) { + TextClassificationResult.Builder builder = new TextClassificationResult.Builder() + .setText(text.toString()) + .setEntityType(type, 1.0f /* confidence */) + .setIntent(intent) + .setOnClickListener(TextClassificationResult.createStartActivityOnClick( + mContext, intent)) + .setLabel(label); + PackageManager pm = mContext.getPackageManager(); + ResolveInfo resolveInfo = pm.resolveActivity(intent, 0); + // TODO: If the resolveInfo is the "chooser", do not set the package name and use a + // default icon for this classification type. + intent.setPackage(resolveInfo.activityInfo.packageName); + Drawable icon = resolveInfo.activityInfo.loadIcon(pm); + if (icon == null) { + icon = resolveInfo.loadIcon(pm); + } + builder.setIcon(icon); + return builder.build(); + } + + /** + * @throws IllegalArgumentException if text is null; startIndex is negative; + * endIndex is greater than text.length() or less than startIndex + */ + private static void validateInput(@NonNull CharSequence text, int startIndex, int endIndex) { + Preconditions.checkArgument(text != null); + Preconditions.checkArgument(startIndex >= 0); + Preconditions.checkArgument(endIndex <= text.length()); + Preconditions.checkArgument(endIndex >= startIndex); + } +} diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java new file mode 100644 index 000000000000..d94d163577e1 --- /dev/null +++ b/core/java/android/view/textclassifier/TextLanguage.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.util.Preconditions; + +import java.util.List; +import java.util.Locale; + +/** + * Specifies detected languages for a section of text indicated by a start and end index. + */ +public final class TextLanguage { + + private final int mStartIndex; + private final int mEndIndex; + @NonNull private final EntityConfidence<Locale> mLanguageConfidence; + @NonNull private final List<Locale> mLanguages; + + private TextLanguage( + int startIndex, int endIndex, @NonNull EntityConfidence<Locale> languageConfidence) { + mStartIndex = startIndex; + mEndIndex = endIndex; + mLanguageConfidence = new EntityConfidence<>(languageConfidence); + mLanguages = mLanguageConfidence.getEntities(); + } + + /** + * Returns the start index of the detected languages in the text provided to generate this + * object. + */ + public int getStartIndex() { + return mStartIndex; + } + + /** + * Returns the end index of the detected languages in the text provided to generate this object. + */ + public int getEndIndex() { + return mEndIndex; + } + + /** + * Returns the number of languages found in the classified text. + */ + @IntRange(from = 0) + public int getLanguageCount() { + return mLanguages.size(); + } + + /** + * Returns the language locale at the specified index. + * Language locales are ordered from high confidence to low confidence. + * + * @throws IndexOutOfBoundsException if the specified index is out of range. + * @see #getLanguageCount() for the number of language locales available. + */ + @NonNull + public Locale getLanguage(int index) { + return mLanguages.get(index); + } + + /** + * Returns the confidence score for the specified language. The value ranges from + * 0 (low confidence) to 1 (high confidence). 0 indicates that the language was + * not found for the classified text. + */ + @FloatRange(from = 0.0, to = 1.0) + public float getConfidenceScore(@Nullable Locale language) { + return mLanguageConfidence.getConfidenceScore(language); + } + + @Override + public String toString() { + return String.format("TextLanguage {%d, %d, %s}", + mStartIndex, mEndIndex, mLanguageConfidence); + } + + /** + * Builder to build {@link TextLanguage} objects. + */ + public static final class Builder { + + private final int mStartIndex; + private final int mEndIndex; + @NonNull private final EntityConfidence<Locale> mLanguageConfidence = + new EntityConfidence<>(); + + /** + * Creates a builder to build {@link TextLanguage} objects. + * + * @param startIndex the start index of the detected languages in the text provided + * to generate the result + * @param endIndex the end index of the detected languages in the text provided + * to generate the result. Must be greater than startIndex + */ + public Builder(@IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex) { + Preconditions.checkArgument(startIndex >= 0); + Preconditions.checkArgument(endIndex > startIndex); + mStartIndex = startIndex; + mEndIndex = endIndex; + } + + /** + * Sets a language locale with the associated confidence score. + */ + public Builder setLanguage( + @NonNull Locale locale, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) { + mLanguageConfidence.setEntityType(locale, confidenceScore); + return this; + } + + /** + * Builds and returns a {@link TextLanguage}. + */ + public TextLanguage build() { + return new TextLanguage(mStartIndex, mEndIndex, mLanguageConfidence); + } + } +} diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java new file mode 100644 index 000000000000..3172c13daa80 --- /dev/null +++ b/core/java/android/view/textclassifier/TextSelection.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.view.textclassifier.TextClassifier.EntityType; + +import com.android.internal.util.Preconditions; + +import java.util.List; + +/** + * Information about where text selection should be. + */ +public final class TextSelection { + + private final int mStartIndex; + private final int mEndIndex; + @NonNull private final EntityConfidence<String> mEntityConfidence; + @NonNull private final List<String> mEntities; + + private TextSelection( + int startIndex, int endIndex, @NonNull EntityConfidence<String> entityConfidence) { + mStartIndex = startIndex; + mEndIndex = endIndex; + mEntityConfidence = new EntityConfidence<>(entityConfidence); + mEntities = mEntityConfidence.getEntities(); + } + + /** + * Returns the start index of the text selection. + */ + public int getSelectionStartIndex() { + return mStartIndex; + } + + /** + * Returns the end index of the text selection. + */ + public int getSelectionEndIndex() { + return mEndIndex; + } + + /** + * Returns the number of entities found in the classified text. + */ + @IntRange(from = 0) + public int getEntityCount() { + return mEntities.size(); + } + + /** + * Returns the entity at the specified index. Entities are ordered from high confidence + * to low confidence. + * + * @throws IndexOutOfBoundsException if the specified index is out of range. + * @see #getEntityCount() for the number of entities available. + */ + @NonNull + public @EntityType String getEntity(int index) { + return mEntities.get(index); + } + + /** + * Returns the confidence score for the specified entity. The value ranges from + * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the + * classified text. + */ + @FloatRange(from = 0.0, to = 1.0) + public float getConfidenceScore(@EntityType String entity) { + return mEntityConfidence.getConfidenceScore(entity); + } + + @Override + public String toString() { + return String.format("TextSelection {%d, %d, %s}", + mStartIndex, mEndIndex, mEntityConfidence); + } + + /** + * Builder used to build {@link TextSelection} objects. + */ + public static final class Builder { + + private final int mStartIndex; + private final int mEndIndex; + @NonNull private final EntityConfidence<String> mEntityConfidence = + new EntityConfidence<>(); + + /** + * Creates a builder used to build {@link TextSelection} objects. + * + * @param startIndex the start index of the text selection. + * @param endIndex the end index of the text selection. Must be greater than startIndex + */ + public Builder(@IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex) { + Preconditions.checkArgument(startIndex >= 0); + Preconditions.checkArgument(endIndex > startIndex); + mStartIndex = startIndex; + mEndIndex = endIndex; + } + + /** + * Sets an entity type for the classified text and assigns a confidence score. + * + * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence). + * 0 implies the entity does not exist for the classified text. + * Values greater than 1 are clamped to 1. + */ + public Builder setEntityType( + @NonNull @EntityType String type, + @FloatRange(from = 0.0, to = 1.0) float confidenceScore) { + mEntityConfidence.setEntityType(type, confidenceScore); + return this; + } + + /** + * Builds and returns {@link TextSelection} object. + */ + public TextSelection build() { + return new TextSelection(mStartIndex, mEndIndex, mEntityConfidence); + } + } +} diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 2fc8ec9c41f2..f7f9a81e45c1 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -60,8 +60,6 @@ import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.StaticLayout; -import android.text.TextClassification; -import android.text.TextSelection; import android.text.TextUtils; import android.text.method.KeyListener; import android.text.method.MetaKeyKeyListener; @@ -108,6 +106,8 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.view.textclassifier.TextClassificationResult; +import android.view.textclassifier.TextSelection; import android.widget.AdapterView.OnItemClickListener; import android.widget.TextView.Drawables; import android.widget.TextView.OnEditorActionListener; @@ -149,7 +149,7 @@ public class Editor { private static final String UNDO_OWNER_TAG = "Editor"; // Ordering constants used to place the Action Mode or context menu items in their menu. - // Reserve 1 for the app that the ASSIST logic suggests as the best app to handle the selection. + private static final int MENU_ITEM_ORDER_ASSIST = 1; private static final int MENU_ITEM_ORDER_UNDO = 2; private static final int MENU_ITEM_ORDER_REDO = 3; private static final int MENU_ITEM_ORDER_SHARE = 4; @@ -238,6 +238,8 @@ public class Editor { private boolean mPreserveSelection; private boolean mRestartActionModeOnNextRefresh; + private TextClassificationResult mTextClassificationResult; + boolean mIsBeingLongClicked; private SuggestionsPopupWindow mSuggestionsPopupWindow; @@ -1889,7 +1891,7 @@ public class Editor { mInsertionPointCursorController.invalidateHandle(); } if (mTextActionMode != null) { - mTextActionMode.invalidate(); + invalidateActionMode(getTextClassifierInfo(false)); } } @@ -1982,12 +1984,12 @@ public class Editor { if (mRestartActionModeOnNextRefresh) { // To avoid distraction, newly start action mode only when selection action // mode is being restarted. - startSelectionActionMode(); + startSelectionActionMode(getTextClassifierInfo(true)); } } else if (selectionController == null || !selectionController.isActive()) { // Insertion action mode is active. Avoid dismissing the selection. stopTextActionModeWithPreservingSelection(); - startSelectionActionMode(); + startSelectionActionMode(getTextClassifierInfo(true)); } else { mTextActionMode.invalidateContentRect(); } @@ -2031,7 +2033,8 @@ public class Editor { * * @return true if the selection mode was actually started. */ - boolean startSelectionActionMode() { + boolean startSelectionActionMode(@Nullable TextClassificationResult textClassificationResult) { + mTextClassificationResult = textClassificationResult; boolean selectionStarted = startSelectionActionModeInternal(); if (selectionStarted) { getSelectionController().show(); @@ -2040,6 +2043,40 @@ public class Editor { return selectionStarted; } + private boolean startSelectionActionModeWithTextAssistant() { + return startSelectionActionMode(getTextClassifierInfo(true)); + } + + private void invalidateActionMode(TextClassificationResult textClassificationResult) { + mTextClassificationResult = textClassificationResult; + mTextActionMode.invalidate(); + } + + // TODO: Make this a non-blocking call. + private TextClassificationResult getTextClassifierInfo(boolean updateSelection) { + // TODO: Trim the text so that only text necessary to provide context of the selected + // text is sent to the assistant. + final int trimStartIndex = 0; + final int trimEndIndex = mTextView.getText().length(); + CharSequence trimmedText = + mTextView.getText().subSequence(trimStartIndex, trimEndIndex); + int startIndex = mTextView.getSelectionStart() - trimStartIndex; + int endIndex = mTextView.getSelectionEnd() - trimStartIndex; + + if (updateSelection) { + TextSelection textSelection = mTextView.getTextClassifier() + .suggestSelection(trimmedText, startIndex, endIndex); + startIndex = Math.max(0, textSelection.getSelectionStartIndex() + trimStartIndex); + endIndex = Math.min(mTextView.getText().length(), + textSelection.getSelectionEndIndex() + trimStartIndex); + Selection.setSelection((Spannable) mTextView.getText(), startIndex, endIndex); + return getTextClassifierInfo(false); + } + + return mTextView.getTextClassifier() + .getTextClassificationResult(trimmedText, startIndex, endIndex); + } + /** * If the TextView allows text selection, selects the current word when no existing selection * was available and starts a drag. @@ -2086,7 +2123,7 @@ public class Editor { } if (mTextActionMode != null) { // Text action mode is already started - mTextActionMode.invalidate(); + invalidateActionMode(getTextClassifierInfo(false)); return false; } @@ -3744,8 +3781,7 @@ public class Editor { private final Path mSelectionPath = new Path(); private final RectF mSelectionBounds = new RectF(); private final boolean mHasSelection; - - private int mHandleHeight; + private final int mHandleHeight; public TextActionModeCallback(boolean hasSelection) { mHasSelection = hasSelection; @@ -3765,18 +3801,19 @@ public class Editor { if (insertionController != null) { insertionController.getHandle(); mHandleHeight = mSelectHandleCenter.getMinimumHeight(); + } else { + mHandleHeight = 0; } } } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { - TextClassification textClassification = updateSelectionWithTextAssistant(); - mode.setTitle(null); mode.setSubtitle(null); mode.setTitleOptionalHint(true); - populateMenuWithItems(menu, textClassification); + populateMenuWithItems(menu); + updateAssistMenuItem(menu, mTextClassificationResult); Callback customCallback = getCustomCallback(); if (customCallback != null) { @@ -3802,30 +3839,13 @@ public class Editor { } } - private TextClassification updateSelectionWithTextAssistant() { - // Trim the text so that only text necessary to provide context of the selected text is - // sent to the assistant. - CharSequence trimmedText = mTextView.getText(); - int textLength = mTextView.getText().length(); - int trimStartIndex = 0; - int startIndex = mTextView.getSelectionStart() - trimStartIndex; - int endIndex = mTextView.getSelectionEnd() - trimStartIndex; - TextSelection textSelection = mTextView.getTextAssistant() - .suggestSelection(trimmedText, startIndex, endIndex); - Selection.setSelection( - (Spannable) mTextView.getText(), - Math.max(0, textSelection.getSelectionStartIndex() + trimStartIndex), - Math.min(textLength, textSelection.getSelectionEndIndex() + trimStartIndex)); - return textSelection.getTextClassification(); - } - private Callback getCustomCallback() { return mHasSelection ? mCustomSelectionActionModeCallback : mCustomInsertionActionModeCallback; } - private void populateMenuWithItems(Menu menu, TextClassification textClassification) { + private void populateMenuWithItems(Menu menu) { if (mTextView.canCut()) { menu.add(Menu.NONE, TextView.ID_CUT, MENU_ITEM_ORDER_CUT, com.android.internal.R.string.cut) @@ -3855,13 +3875,13 @@ public class Editor { updateSelectAllItem(menu); updateReplaceItem(menu); - updateAssistMenuItem(menu, textClassification); } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { updateSelectAllItem(menu); updateReplaceItem(menu); + updateAssistMenuItem(menu, mTextClassificationResult); Callback customCallback = getCustomCallback(); if (customCallback != null) { @@ -3894,10 +3914,16 @@ public class Editor { } } - private void updateAssistMenuItem(Menu menu, TextClassification textClassification) { - // TODO: Find the best app available to handle the selected text based on information in - // the TextClassification object. - // Add app icon + intent to trigger app to the menu. + private void updateAssistMenuItem( + Menu menu, TextClassificationResult textClassificationResult) { + menu.removeItem(TextView.ID_ASSIST); + if (textClassificationResult != null + && textClassificationResult.getIcon() != null + && textClassificationResult.getOnClickListener() != null) { + menu.add(Menu.NONE, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST, null) + .setIcon(textClassificationResult.getIcon()) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + } } @Override @@ -3909,6 +3935,10 @@ public class Editor { if (customCallback != null && customCallback.onActionItemClicked(mode, item)) { return true; } + if (TextView.ID_ASSIST == item.getItemId() && mTextClassificationResult != null) { + mTextClassificationResult.getOnClickListener().onClick(mTextView); + stopTextActionMode(); + } return mTextView.onTextContextMenuItem(item.getItemId()); } @@ -3916,6 +3946,7 @@ public class Editor { public void onDestroyActionMode(ActionMode mode) { // Clear mTextActionMode not to recursively destroy action mode by clearing selection. mTextActionMode = null; + mTextClassificationResult = null; Callback customCallback = getCustomCallback(); if (customCallback != null) { customCallback.onDestroyActionMode(mode); @@ -4733,7 +4764,7 @@ public class Editor { } positionAtCursorOffset(offset, false); if (mTextActionMode != null) { - mTextActionMode.invalidate(); + invalidateActionMode(getTextClassifierInfo(false)); } } @@ -4817,7 +4848,7 @@ public class Editor { } updateDrawable(); if (mTextActionMode != null) { - mTextActionMode.invalidate(); + invalidateActionMode(getTextClassifierInfo(false)); } } @@ -5465,7 +5496,8 @@ public class Editor { resetDragAcceleratorState(); if (mTextView.hasSelection()) { - startSelectionActionMode(); + // TODO: Do not invoke the text assistant if this was a drag selection. + startSelectionActionMode(getTextClassifierInfo(true)); } break; } diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java index 3b7fe86a5fa1..70b70bcb36ea 100644 --- a/core/java/android/widget/RatingBar.java +++ b/core/java/android/widget/RatingBar.java @@ -150,7 +150,11 @@ public class RatingBar extends AbsSeekBar { */ public void setIsIndicator(boolean isIndicator) { mIsUserSeekable = !isIndicator; - setFocusable(!isIndicator); + if (isIndicator) { + setFocusable(FOCUSABLE_AUTO); + } else { + setFocusable(FOCUSABLE); + } } /** diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index 9139361eda7c..38221383df3f 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -357,9 +357,9 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { setInputType(inputType); } - boolean focusable = true; - focusable = a.getBoolean(R.styleable.SearchView_focusable, focusable); - setFocusable(focusable); + if (getFocusable() == FOCUSABLE_AUTO) { + setFocusable(FOCUSABLE); + } a.recycle(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index b1fc67e56d80..a11ece6608d2 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -77,8 +77,6 @@ import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.SpannedString; import android.text.StaticLayout; -import android.text.TextAssistant; -import android.text.TextClassificationManager; import android.text.TextDirectionHeuristic; import android.text.TextDirectionHeuristics; import android.text.TextPaint; @@ -146,6 +144,8 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.view.textclassifier.TextClassificationManager; +import android.view.textclassifier.TextClassifier; import android.view.textservice.SpellCheckerSubtype; import android.view.textservice.TextServicesManager; import android.widget.RemoteViews.RemoteView; @@ -352,6 +352,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private boolean mPreDrawRegistered; private boolean mPreDrawListenerDetached; + private TextClassifier mTextClassifier; + // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is // that if a user is holding down a movement key to traverse text, we shouldn't also traverse // the view hierarchy. On the other hand, if the user is using the movement key to traverse @@ -1513,26 +1515,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (hint != null) setHint(hint); /* - * Views are not normally focusable unless specified to be. + * Views are not normally clickable unless specified to be. * However, TextViews that have input or movement methods *are* - * focusable by default. + * clickable by default. By setting clickable here, we implicitly set focusable as well + * if not overridden by the developer. */ a = context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); - - boolean focusable = mMovement != null || getKeyListener() != null; - boolean clickable = focusable || isClickable(); - boolean longClickable = focusable || isLongClickable(); + boolean canInputOrMove = (mMovement != null || getKeyListener() != null); + boolean clickable = canInputOrMove || isClickable(); + boolean longClickable = canInputOrMove || isLongClickable(); n = a.getIndexCount(); for (int i = 0; i < n; i++) { int attr = a.getIndex(i); switch (attr) { - case com.android.internal.R.styleable.View_focusable: - focusable = a.getBoolean(attr, focusable); - break; - case com.android.internal.R.styleable.View_clickable: clickable = a.getBoolean(attr, clickable); break; @@ -1544,7 +1542,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } a.recycle(); - setFocusable(focusable); setClickable(clickable); setLongClickable(longClickable); @@ -2153,11 +2150,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private void fixFocusableAndClickableSettings() { if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) { - setFocusable(true); + setFocusable(FOCUSABLE); setClickable(true); setLongClickable(true); } else { - setFocusable(false); + setFocusable(FOCUSABLE_AUTO); setClickable(false); setLongClickable(false); } @@ -6124,7 +6121,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mEditor.mTextIsSelectable = selectable; setFocusableInTouchMode(selectable); - setFocusable(selectable); + setFocusable(FOCUSABLE_AUTO); setClickable(selectable); setLongClickable(selectable); @@ -9890,7 +9887,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Selection.setSelection((Spannable) text, start, end); // Make sure selection mode is engaged. if (mEditor != null) { - mEditor.startSelectionActionMode(); + mEditor.startSelectionActionMode(null); } return true; } @@ -10034,6 +10031,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener static final int ID_SHARE = android.R.id.shareText; static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; static final int ID_REPLACE = android.R.id.replaceText; + static final int ID_ASSIST = android.R.id.textAssist; /** * Called when a context menu option for the text view is selected. Currently @@ -10258,33 +10256,30 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; } - private TextAssistant mTextAssistant; - /** - * Sets the {@link TextAssistant} for this TextView. - * If null, this TextView uses the default TextAssistant which comes from the Activity. + * Sets the {@link TextClassifier} for this TextView. */ - public void setTextAssistant(TextAssistant textAssistant) { - mTextAssistant = textAssistant; + public void setTextClassifier(@Nullable TextClassifier textClassifier) { + mTextClassifier = textClassifier; } /** - * Returns the {@link TextAssistant} used by this TextView. - * If no TextAssistant is set, it'll use the one from this TextView's {@link Activity} or - * {@link Context}. If no TextAssistant is found, it'll use a no-op TextAssistant. + * Returns the {@link TextClassifier} used by this TextView. + * If no TextClassifier has been set, this TextView uses the default set by the + * {@link TextClassificationManager}. */ - public TextAssistant getTextAssistant() { - if (mTextAssistant != null) { - return mTextAssistant; - } - if (mContext instanceof Activity) { - mTextAssistant = ((Activity) mContext).getTextAssistant(); - } else { - // The context of this TextView should be an Activity. If it is not and no - // text assistant has been set, return the TextClassificationManager. - mTextAssistant = mContext.getSystemService(TextClassificationManager.class); + @NonNull + public TextClassifier getTextClassifier() { + if (mTextClassifier == null) { + TextClassificationManager tcm = + mContext.getSystemService(TextClassificationManager.class); + if (tcm != null) { + mTextClassifier = tcm.getDefaultTextClassifier(); + } else { + mTextClassifier = TextClassifier.NO_OP; + } } - return mTextAssistant; + return mTextClassifier; } /** diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java index 8d117837df83..a94b1612e8a3 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java @@ -95,39 +95,32 @@ public class InputMethodSubtypeSwitchingController { } } + private static int compareNullableCharSequences(@Nullable CharSequence c1, + @Nullable CharSequence c2) { + // For historical reasons, an empty text needs to put at the last. + final boolean empty1 = TextUtils.isEmpty(c1); + final boolean empty2 = TextUtils.isEmpty(c2); + if (empty1 || empty2) { + return (empty1 ? 1 : 0) - (empty2 ? 1 : 0); + } + return c1.toString().compareTo(c2.toString()); + } + @Override public int compareTo(ImeSubtypeListItem other) { - if (TextUtils.isEmpty(mImeName)) { - return 1; - } - if (TextUtils.isEmpty(other.mImeName)) { - return -1; - } - if (!TextUtils.equals(mImeName, other.mImeName)) { - return mImeName.toString().compareTo(other.mImeName.toString()); - } - if (TextUtils.equals(mSubtypeName, other.mSubtypeName)) { - return 0; - } - if (mIsSystemLocale) { - return -1; - } - if (other.mIsSystemLocale) { - return 1; - } - if (mIsSystemLanguage) { - return -1; - } - if (other.mIsSystemLanguage) { - return 1; + int result = compareNullableCharSequences(mImeName, other.mImeName); + if (result != 0) { + return result; } - if (TextUtils.isEmpty(mSubtypeName)) { - return 1; + result = compareNullableCharSequences(mSubtypeName, other.mSubtypeName); + if (result != 0) { + return result; } - if (TextUtils.isEmpty(other.mSubtypeName)) { - return -1; + result = (mIsSystemLocale ? -1 : 0) - (other.mIsSystemLocale ? -1 : 0); + if (result != 0) { + return result; } - return mSubtypeName.toString().compareTo(other.mSubtypeName.toString()); + return (mIsSystemLanguage ? -1 : 0) - (other.mIsSystemLanguage ? -1 : 0); } @Override diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java index 16c2719c0584..c8bf302e46d5 100644 --- a/core/java/com/android/internal/logging/MetricsLogger.java +++ b/core/java/com/android/internal/logging/MetricsLogger.java @@ -16,6 +16,7 @@ package com.android.internal.logging; import android.content.Context; +import android.metrics.LogMaker; import android.os.Build; import android.view.View; @@ -37,7 +38,7 @@ public class MetricsLogger { } EventLogTags.writeSysuiViewVisibility(category, 100); EventLogTags.writeSysuiMultiAction( - new LogBuilder(category) + new LogMaker(category) .setType(MetricsEvent.TYPE_OPEN) .serialize()); } @@ -48,7 +49,7 @@ public class MetricsLogger { } EventLogTags.writeSysuiViewVisibility(category, 0); EventLogTags.writeSysuiMultiAction( - new LogBuilder(category) + new LogMaker(category) .setType(MetricsEvent.TYPE_CLOSE) .serialize()); } @@ -70,7 +71,7 @@ public class MetricsLogger { public static void action(Context context, int category) { EventLogTags.writeSysuiAction(category, ""); EventLogTags.writeSysuiMultiAction( - new LogBuilder(category) + new LogMaker(category) .setType(MetricsEvent.TYPE_ACTION) .serialize()); } @@ -78,7 +79,7 @@ public class MetricsLogger { public static void action(Context context, int category, int value) { EventLogTags.writeSysuiAction(category, Integer.toString(value)); EventLogTags.writeSysuiMultiAction( - new LogBuilder(category) + new LogMaker(category) .setType(MetricsEvent.TYPE_ACTION) .setSubtype(value) .serialize()); @@ -87,16 +88,13 @@ public class MetricsLogger { public static void action(Context context, int category, boolean value) { EventLogTags.writeSysuiAction(category, Boolean.toString(value)); EventLogTags.writeSysuiMultiAction( - new LogBuilder(category) + new LogMaker(category) .setType(MetricsEvent.TYPE_ACTION) .setSubtype(value ? 1 : 0) .serialize()); } - public static void action(LogBuilder content) { - //EventLog.writeEvent(524292, content.serialize()); - // Below would be the *right* way to do this, using the generated - // EventLogTags method, but that doesn't work. + public static void action(LogMaker content) { if (content.getType() == MetricsEvent.TYPE_UNKNOWN) { content.setType(MetricsEvent.TYPE_ACTION); } @@ -109,7 +107,7 @@ public class MetricsLogger { throw new IllegalArgumentException("Must define metric category"); } EventLogTags.writeSysuiAction(category, pkg); - EventLogTags.writeSysuiMultiAction(new LogBuilder(category) + EventLogTags.writeSysuiMultiAction(new LogMaker(category) .setType(MetricsEvent.TYPE_ACTION) .setPackageName(pkg) .serialize()); @@ -119,7 +117,7 @@ public class MetricsLogger { public static void count(Context context, String name, int value) { EventLogTags.writeSysuiCount(name, value); EventLogTags.writeSysuiMultiAction( - new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER) + new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER) .setCounterName(name) .setCounterValue(value) .serialize()); @@ -129,7 +127,7 @@ public class MetricsLogger { public static void histogram(Context context, String name, int bucket) { EventLogTags.writeSysuiHistogram(name, bucket); EventLogTags.writeSysuiMultiAction( - new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM) + new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM) .setCounterName(name) .setCounterBucket(bucket) .setCounterValue(1) diff --git a/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java b/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java index 7381ff08bbf2..91e968b5b250 100644 --- a/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java +++ b/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java @@ -15,9 +15,7 @@ */ package com.android.internal.logging.legacy; -import android.os.Bundle; - -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.util.HashMap; @@ -34,20 +32,20 @@ public class LegacyConversionLogger implements TronLogger { public static final int TYPE_HISTOGRAM = 2; public static final int TYPE_EVENT = 3; - private final Queue<LogBuilder> mQueue; + private final Queue<LogMaker> mQueue; private HashMap<String, Boolean> mConfig; public LegacyConversionLogger() { mQueue = new LinkedList<>(); } - public Queue<LogBuilder> getEvents() { + public Queue<LogMaker> getEvents() { return mQueue; } @Override public void increment(String counterName) { - LogBuilder b = new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER) + LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER) .setCounterName(counterName) .setCounterValue(1); mQueue.add(b); @@ -55,7 +53,7 @@ public class LegacyConversionLogger implements TronLogger { @Override public void incrementBy(String counterName, int value) { - LogBuilder b = new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER) + LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER) .setCounterName(counterName) .setCounterValue(value); mQueue.add(b); @@ -63,7 +61,7 @@ public class LegacyConversionLogger implements TronLogger { @Override public void incrementIntHistogram(String counterName, int bucket) { - LogBuilder b = new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM) + LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM) .setCounterName(counterName) .setCounterBucket(bucket) .setCounterValue(1); @@ -72,7 +70,7 @@ public class LegacyConversionLogger implements TronLogger { @Override public void incrementLongHistogram(String counterName, long bucket) { - LogBuilder b = new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM) + LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM) .setCounterName(counterName) .setCounterBucket(bucket) .setCounterValue(1); @@ -80,16 +78,16 @@ public class LegacyConversionLogger implements TronLogger { } @Override - public LogBuilder obtain() { - return new LogBuilder(MetricsEvent.VIEW_UNKNOWN); + public LogMaker obtain() { + return new LogMaker(MetricsEvent.VIEW_UNKNOWN); } @Override - public void dispose(LogBuilder proto) { + public void dispose(LogMaker proto) { } @Override - public void addEvent(LogBuilder proto) { + public void addEvent(LogMaker proto) { mQueue.add(proto); } diff --git a/core/java/com/android/internal/logging/legacy/LockscreenGestureParser.java b/core/java/com/android/internal/logging/legacy/LockscreenGestureParser.java index 6bede24d8316..df08ee067087 100644 --- a/core/java/com/android/internal/logging/legacy/LockscreenGestureParser.java +++ b/core/java/com/android/internal/logging/legacy/LockscreenGestureParser.java @@ -17,7 +17,7 @@ package com.android.internal.logging.legacy; import android.util.Log; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; /** @@ -62,7 +62,7 @@ public class LockscreenGestureParser extends TagParser { category = GESTURE_TYPE_MAP[type]; } if (category != MetricsEvent.VIEW_UNKNOWN) { - LogBuilder proto = logger.obtain(); + LogMaker proto = logger.obtain(); proto.setCategory(category); proto.setType(MetricsEvent.TYPE_ACTION); proto.setTimestamp(eventTimeMs); diff --git a/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java b/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java index 67b84e984e80..79f3eb88805d 100644 --- a/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java +++ b/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java @@ -17,7 +17,7 @@ package com.android.internal.logging.legacy; import android.util.Log; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; /** @@ -47,7 +47,7 @@ public class NotificationActionClickedParser extends TagParser { if (mKey.parse((String) operands[0])) { int index = (Integer) operands[1]; parseTimes(operands, 2); - LogBuilder proto = logger.obtain(); + LogMaker proto = logger.obtain(); proto.setCategory(MetricsEvent.NOTIFICATION_ITEM_ACTION); proto.setType(MetricsEvent.TYPE_ACTION); proto.setSubtype(index); diff --git a/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java b/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java index 761197b19d66..9548fb0be452 100644 --- a/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java +++ b/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java @@ -17,8 +17,8 @@ package com.android.internal.logging.legacy; import android.util.Log; +import android.metrics.LogMaker; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.LogBuilder; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; /** @@ -58,7 +58,7 @@ public class NotificationAlertParser extends TagParser { final boolean blink = ((Integer) operands[3]) == 1; if (mKey.parse(keyString)) { - LogBuilder proto = logger.obtain(); + LogMaker proto = logger.obtain(); proto.setCategory(MetricsEvent.NOTIFICATION_ALERT); proto.setType(MetricsEvent.TYPE_OPEN); proto.setSubtype((buzz ? BUZZ : 0) | (beep ? BEEP : 0) | (blink ? BLINK : 0)); diff --git a/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java b/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java index 0cab1a859593..80eb004277f5 100644 --- a/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java +++ b/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java @@ -17,7 +17,7 @@ package com.android.internal.logging.legacy; import android.util.Log; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; /** @@ -78,7 +78,7 @@ public class NotificationCanceledParser extends TagParser { if (mKey.parse(keyString)) { if (intentional) { - LogBuilder proto = logger.obtain(); + LogMaker proto = logger.obtain(); proto.setCategory(MetricsEvent.NOTIFICATION_ITEM); proto.setType(MetricsEvent.TYPE_DISMISS); proto.setSubtype(reason); diff --git a/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java b/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java index eeae0a88feb8..eee4701cc743 100644 --- a/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java +++ b/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java @@ -17,7 +17,7 @@ package com.android.internal.logging.legacy; import android.util.Log; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; /** @@ -46,7 +46,7 @@ public class NotificationClickedParser extends TagParser { try { if (mKey.parse((String) operands[0])) { parseTimes(operands, 1); - LogBuilder proto = logger.obtain(); + LogMaker proto = logger.obtain(); proto.setCategory(MetricsEvent.NOTIFICATION_ITEM); proto.setType(MetricsEvent.TYPE_ACTION); proto.setTimestamp(eventTimeMs); diff --git a/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java b/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java index d44b8b1ae7a2..84cd9993e2e3 100644 --- a/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java +++ b/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java @@ -17,7 +17,7 @@ package com.android.internal.logging.legacy; import android.util.Log; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; /** @@ -52,7 +52,7 @@ public class NotificationExpansionParser extends TagParser { if (!byUser || !expanded) { return; } - LogBuilder proto = logger.obtain(); + LogMaker proto = logger.obtain(); proto.setCategory(MetricsEvent.NOTIFICATION_ITEM); proto.setType(MetricsEvent.TYPE_DETAIL); proto.setTimestamp(eventTimeMs); diff --git a/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java b/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java index 662295b5846d..a064a2ebca46 100644 --- a/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java +++ b/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java @@ -15,7 +15,7 @@ */ package com.android.internal.logging.legacy; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; /** @@ -33,7 +33,7 @@ public class NotificationPanelHiddenParser extends TagParser { @Override public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) { - LogBuilder proto = logger.obtain(); + LogMaker proto = logger.obtain(); proto.setCategory(MetricsEvent.NOTIFICATION_PANEL); proto.setType(MetricsEvent.TYPE_CLOSE); proto.setTimestamp(eventTimeMs); diff --git a/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java b/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java index 0566f0b64769..4d19564e21e3 100644 --- a/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java +++ b/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java @@ -17,7 +17,7 @@ package com.android.internal.logging.legacy; import android.util.Log; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; /** @@ -47,7 +47,7 @@ public class NotificationPanelRevealedParser extends TagParser { } } - LogBuilder proto = logger.obtain(); + LogMaker proto = logger.obtain(); proto.setCategory(MetricsEvent.NOTIFICATION_PANEL); proto.setType(MetricsEvent.TYPE_OPEN); proto.setTimestamp(eventTimeMs); diff --git a/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java b/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java index 9185b91badc7..2d2cd909e2a7 100644 --- a/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java +++ b/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java @@ -17,7 +17,7 @@ package com.android.internal.logging.legacy; import android.util.Log; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; /** @@ -53,7 +53,7 @@ public class NotificationVisibilityParser extends TagParser { } if (mKey.parse(keyString)) { - LogBuilder proto = logger.obtain(); + LogMaker proto = logger.obtain(); proto.setCategory(MetricsEvent.NOTIFICATION_ITEM); proto.setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE); proto.setTimestamp(eventTimeMs); diff --git a/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java b/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java index 3bf0f9d43873..e9baf9baf8da 100644 --- a/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java +++ b/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java @@ -17,7 +17,7 @@ package com.android.internal.logging.legacy; import android.util.Log; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; /** @@ -48,7 +48,7 @@ public class PowerScreenStateParser extends TagParser { boolean state = (((Integer) operands[0]).intValue()) == 1; int why = ((Integer) operands[1]).intValue(); - LogBuilder proto = logger.obtain(); + LogMaker proto = logger.obtain(); proto.setCategory(MetricsEvent.SCREEN); proto.setType(state ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE); proto.setTimestamp(eventTimeMs); diff --git a/core/java/com/android/internal/logging/legacy/StatusBarStateParser.java b/core/java/com/android/internal/logging/legacy/StatusBarStateParser.java index 23abec47b636..226253f3322f 100644 --- a/core/java/com/android/internal/logging/legacy/StatusBarStateParser.java +++ b/core/java/com/android/internal/logging/legacy/StatusBarStateParser.java @@ -17,7 +17,7 @@ package com.android.internal.logging.legacy; import android.util.Log; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; /** @@ -56,7 +56,7 @@ public class StatusBarStateParser extends TagParser { view = MetricsEvent.BOUNCER; } - LogBuilder proto = logger.obtain(); + LogMaker proto = logger.obtain(); proto.setCategory(view); proto.setType(type); proto.setTimestamp(eventTimeMs); diff --git a/core/java/com/android/internal/logging/legacy/SysuiActionParser.java b/core/java/com/android/internal/logging/legacy/SysuiActionParser.java index 7f91f92fc2d2..1148ee5865bb 100644 --- a/core/java/com/android/internal/logging/legacy/SysuiActionParser.java +++ b/core/java/com/android/internal/logging/legacy/SysuiActionParser.java @@ -17,7 +17,7 @@ package com.android.internal.logging.legacy; import android.util.Log; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; /** @@ -60,7 +60,7 @@ public class SysuiActionParser extends TagParser { } if (operands.length > 0) { int category = ((Integer) operands[0]).intValue(); - LogBuilder proto = logger.obtain(); + LogMaker proto = logger.obtain(); proto.setCategory(category); proto.setType(MetricsEvent.TYPE_ACTION); proto.setTimestamp(eventTimeMs); diff --git a/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java b/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java index f9b2f497a0dc..0c77b7a6ccd0 100644 --- a/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java +++ b/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java @@ -17,8 +17,7 @@ package com.android.internal.logging.legacy; import android.util.Log; -import com.android.internal.logging.LogBuilder; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import android.metrics.LogMaker; /** * ...and one parser to rule them all. @@ -39,7 +38,7 @@ public class SysuiMultiActionParser extends TagParser { public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) { final boolean debug = Util.debug(); try { - logger.addEvent(new LogBuilder(operands).setTimestamp(eventTimeMs)); + logger.addEvent(new LogMaker(operands).setTimestamp(eventTimeMs)); } catch (ClassCastException e) { if (debug) { Log.e(TAG, "unexpected operand type: ", e); diff --git a/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java b/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java index 5d5aec04116d..1223b8d216bf 100644 --- a/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java +++ b/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java @@ -17,7 +17,7 @@ package com.android.internal.logging.legacy; import android.util.Log; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; /** @@ -41,7 +41,7 @@ public class SysuiViewVisibilityParser extends TagParser { int category = ((Integer) operands[0]).intValue(); boolean visibility = ((Integer) operands[1]).intValue() != 0; - LogBuilder proto = logger.obtain(); + LogMaker proto = logger.obtain(); proto.setCategory(category); proto.setType(visibility ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE); proto.setTimestamp(eventTimeMs); diff --git a/core/java/com/android/internal/logging/legacy/TagParser.java b/core/java/com/android/internal/logging/legacy/TagParser.java index c62d084f68e3..3bffdd522236 100755 --- a/core/java/com/android/internal/logging/legacy/TagParser.java +++ b/core/java/com/android/internal/logging/legacy/TagParser.java @@ -17,8 +17,8 @@ package com.android.internal.logging.legacy; import android.util.Log; +import android.metrics.LogMaker; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.LogBuilder; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; /** @@ -87,7 +87,7 @@ public abstract class TagParser { } } - public void filltimes(LogBuilder proto) { + public void filltimes(LogMaker proto) { if (mSinceCreationMillis != 0) { proto.addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, mSinceCreationMillis); diff --git a/core/java/com/android/internal/logging/legacy/TronLogger.java b/core/java/com/android/internal/logging/legacy/TronLogger.java index dabe314d4565..ee9341ab2138 100644 --- a/core/java/com/android/internal/logging/legacy/TronLogger.java +++ b/core/java/com/android/internal/logging/legacy/TronLogger.java @@ -15,7 +15,7 @@ */ package com.android.internal.logging.legacy; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; /** * An entity that knows how to log events and counters. @@ -34,12 +34,12 @@ public interface TronLogger { void incrementLongHistogram(String counterName, long bucket); /** Obtain a SystemUiEvent proto, must release this with dispose() or addEvent(). */ - LogBuilder obtain(); + LogMaker obtain(); - void dispose(LogBuilder proto); + void dispose(LogMaker proto); /** Submit an event to be logged. Logger will dispose of proto. */ - void addEvent(LogBuilder proto); + void addEvent(LogMaker proto); /** Get a config flag. */ boolean getConfig(String configName); diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 5fd68e6ff1e6..c34c3794da54 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -9713,7 +9713,7 @@ public class BatteryStatsImpl extends BatteryStats { resetAllStatsLocked(); if (chargeUAh > 0) { // Only use the reported coulomb charge value if it is supported and reported. - mEstimatedBatteryCapacity = (int) ((level / 100.0) * (chargeUAh / 1000)); + mEstimatedBatteryCapacity = (int) ((chargeUAh / 1000) / (level / 100.0)); } mDischargeStartLevel = level; reset = true; diff --git a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java index fb0edea398ce..ebc2c716493a 100644 --- a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java +++ b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java @@ -84,7 +84,8 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_MUTE: { - MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false); + MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent( + event, AudioManager.USE_DEFAULT_STREAM_TYPE, false); return true; } @@ -215,7 +216,8 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_MUTE: { if (!event.isCanceled()) { - MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false); + MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent( + event, AudioManager.USE_DEFAULT_STREAM_TYPE, false); } return true; } diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index e68ebc4342fd..84195b2a270a 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -1856,27 +1856,25 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_MUTE: { - int direction = 0; - switch (keyCode) { - case KeyEvent.KEYCODE_VOLUME_UP: - direction = AudioManager.ADJUST_RAISE; - break; - case KeyEvent.KEYCODE_VOLUME_DOWN: - direction = AudioManager.ADJUST_LOWER; - break; - case KeyEvent.KEYCODE_VOLUME_MUTE: - direction = AudioManager.ADJUST_TOGGLE_MUTE; - break; - } // If we have a session send it the volume command, otherwise // use the suggested stream. if (mMediaController != null) { + int direction = 0; + switch (keyCode) { + case KeyEvent.KEYCODE_VOLUME_UP: + direction = AudioManager.ADJUST_RAISE; + break; + case KeyEvent.KEYCODE_VOLUME_DOWN: + direction = AudioManager.ADJUST_LOWER; + break; + case KeyEvent.KEYCODE_VOLUME_MUTE: + direction = AudioManager.ADJUST_TOGGLE_MUTE; + break; + } mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI); } else { - MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy( - mVolumeControlStreamType, direction, - AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE - | AudioManager.FLAG_FROM_KEY); + MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent( + event, mVolumeControlStreamType, false); } return true; } @@ -1954,15 +1952,15 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: { - final int flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE - | AudioManager.FLAG_FROM_KEY; // If we have a session send it the volume command, otherwise // use the suggested stream. if (mMediaController != null) { + final int flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE + | AudioManager.FLAG_FROM_KEY; mMediaController.adjustVolume(0, flags); } else { - MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy( - mVolumeControlStreamType, 0, flags); + MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent( + event, mVolumeControlStreamType, false); } return true; } @@ -1971,7 +1969,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // doesn't have one of these. In this case, we execute it here and // eat the event instead, because we have mVolumeControlStreamType // and they don't. - getAudioManager().handleKeyUp(event, mVolumeControlStreamType); + MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent( + event, AudioManager.USE_DEFAULT_STREAM_TYPE, false); return true; } // These are all the recognized media key codes in diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp index ac8f413c2677..0c863fd6eca8 100644 --- a/core/jni/android/graphics/FontFamily.cpp +++ b/core/jni/android/graphics/FontFamily.cpp @@ -39,27 +39,60 @@ namespace android { -static jlong FontFamily_create(JNIEnv* env, jobject clazz, jstring lang, jint variant) { - if (lang == NULL) { - return (jlong)new minikin::FontFamily(variant); +struct NativeFamilyBuilder { + uint32_t langId; + int variant; + std::vector<minikin::Font> fonts; +}; + +static jlong FontFamily_initBuilder(JNIEnv* env, jobject clazz, jstring lang, jint variant) { + NativeFamilyBuilder* builder = new NativeFamilyBuilder(); + if (lang != nullptr) { + ScopedUtfChars str(env, lang); + builder->langId = minikin::FontStyle::registerLanguageList(str.c_str()); + } else { + builder->langId = minikin::FontStyle::registerLanguageList(""); + } + builder->variant = variant; + return reinterpret_cast<jlong>(builder); +} + +static jlong FontFamily_create(jlong builderPtr) { + if (builderPtr == 0) { + return 0; } - ScopedUtfChars str(env, lang); - uint32_t langId = minikin::FontStyle::registerLanguageList(str.c_str()); - return (jlong)new minikin::FontFamily(langId, variant); + NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr); + minikin::FontFamily* family = new minikin::FontFamily( + builder->langId, builder->variant, std::move(builder->fonts)); + delete builder; + return reinterpret_cast<jlong>(family); +} + +static void FontFamily_abort(jlong builderPtr) { + NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr); + minikin::Font::clearElementsWithLock(&builder->fonts); + delete builder; } -static void FontFamily_unref(JNIEnv* env, jobject clazz, jlong familyPtr) { +static void FontFamily_unref(jlong familyPtr) { minikin::FontFamily* fontFamily = reinterpret_cast<minikin::FontFamily*>(familyPtr); fontFamily->Unref(); } -static jboolean addSkTypeface(minikin::FontFamily* family, sk_sp<SkTypeface> face, - const void* fontData, size_t fontSize, int ttcIndex) { +static void addSkTypeface(jlong builderPtr, sk_sp<SkTypeface> face, const void* fontData, + size_t fontSize, int ttcIndex) { minikin::MinikinFont* minikinFont = new MinikinFontSkia(std::move(face), fontData, fontSize, ttcIndex); - bool result = family->addFont(minikinFont); + NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr); + int weight; + bool italic; + if (!minikin::FontFamily::analyzeStyle(minikinFont, &weight, &italic)) { + ALOGE("analyzeStyle failed. Using default style"); + weight = 400; + italic = false; + } + builder->fonts.push_back(minikin::Font(minikinFont, minikin::FontStyle(weight / 100, italic))); minikinFont->Unref(); - return result; } static void release_global_ref(const void* /*data*/, void* context) { @@ -85,7 +118,7 @@ static void release_global_ref(const void* /*data*/, void* context) { } } -static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong familyPtr, jobject bytebuf, +static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong builderPtr, jobject bytebuf, jint ttcIndex) { NPE_CHECK_RETURN_ZERO(env, bytebuf); const void* fontPtr = env->GetDirectBufferAddress(bytebuf); @@ -112,8 +145,8 @@ static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong familyPtr, ALOGE("addFont failed to create font"); return false; } - minikin::FontFamily* fontFamily = reinterpret_cast<minikin::FontFamily*>(familyPtr); - return addSkTypeface(fontFamily, std::move(face), fontPtr, (size_t)fontSize, ttcIndex); + addSkTypeface(builderPtr, std::move(face), fontPtr, (size_t)fontSize, ttcIndex); + return true; } static struct { @@ -126,7 +159,7 @@ static struct { jfieldID mStyleValue; } gAxisClassInfo; -static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong familyPtr, +static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong builderPtr, jobject font, jint ttcIndex, jobject listOfAxis, jint weight, jboolean isItalic) { NPE_CHECK_RETURN_ZERO(env, font); @@ -178,10 +211,11 @@ static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong ALOGE("addFont failed to create font, invalid request"); return false; } - minikin::FontFamily* fontFamily = reinterpret_cast<minikin::FontFamily*>(familyPtr); minikin::MinikinFont* minikinFont = - new MinikinFontSkia(std::move(face), fontPtr, (size_t)fontSize, ttcIndex); - fontFamily->addFont(minikinFont, minikin::FontStyle(weight / 100, isItalic)); + new MinikinFontSkia(std::move(face), fontPtr, fontSize, ttcIndex); + NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr); + builder->fonts.push_back(minikin::Font(minikinFont, + minikin::FontStyle(weight / 100, isItalic))); minikinFont->Unref(); return true; } @@ -190,7 +224,7 @@ static void releaseAsset(const void* ptr, void* context) { delete static_cast<Asset*>(context); } -static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong familyPtr, +static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong builderPtr, jobject jassetMgr, jstring jpath, jint cookie, jboolean isAsset) { NPE_CHECK_RETURN_ZERO(env, jassetMgr); NPE_CHECK_RETURN_ZERO(env, jpath); @@ -233,14 +267,17 @@ static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong f ALOGE("addFontFromAsset failed to create font %s", str.c_str()); return false; } - minikin::FontFamily* fontFamily = reinterpret_cast<minikin::FontFamily*>(familyPtr); - return addSkTypeface(fontFamily, std::move(face), buf, bufSize, /* ttcIndex */ 0); + + addSkTypeface(builderPtr, std::move(face), buf, bufSize, 0 /* ttc index */); + return true; } /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gFontFamilyMethods[] = { - { "nCreateFamily", "(Ljava/lang/String;I)J", (void*)FontFamily_create }, + { "nInitBuilder", "(Ljava/lang/String;I)J", (void*)FontFamily_initBuilder }, + { "nCreateFamily", "(J)J", (void*)FontFamily_create }, + { "nAbort", "(J)V", (void*)FontFamily_abort }, { "nUnrefFamily", "(J)V", (void*)FontFamily_unref }, { "nAddFont", "(JLjava/nio/ByteBuffer;I)Z", (void*)FontFamily_addFont }, { "nAddFontWeightStyle", "(JLjava/nio/ByteBuffer;ILjava/util/List;IZ)Z", diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp index 271f24b13052..bc2fc1c39890 100644 --- a/core/jni/android_hardware_SensorManager.cpp +++ b/core/jni/android_hardware_SensorManager.cpp @@ -26,6 +26,7 @@ #include <gui/Sensor.h> #include <gui/SensorEventQueue.h> #include <gui/SensorManager.h> +#include <cutils/native_handle.h> #include <utils/Log.h> #include <utils/Looper.h> #include <utils/Vector.h> @@ -243,6 +244,54 @@ static jboolean nativeIsDataInjectionEnabled(JNIEnv *_env, jclass _this, jlong s return mgr->isDataInjectionEnabled(); } +static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager, + jlong size, jint channelType, jlongArray channelData) { + jint ret = -1; + jsize len = _env->GetArrayLength(channelData); + if (len > 2) { + jlong *data = _env->GetLongArrayElements(channelData, NULL); + if (data != nullptr) { + // construct native handle from jlong* + jlong numFd = data[0]; + jlong numInt = data[1]; + if ((numFd + numInt + 2) == len) { + native_handle_t *nativeHandle = native_handle_create(numFd, numInt); + if (nativeHandle != nullptr) { + const jlong *readPointer = data + 2; + int *writePointer = nativeHandle->data; + size_t n = static_cast<size_t>(numFd + numInt); + while (n--) { + // native type of data is int, jlong is just to ensure Java does not + // truncate data on 64-bit system. The cast here is safe. + *writePointer++ = static_cast<int>(*readPointer++); + } + + SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager); + ret = mgr->createDirectChannel(size, channelType, nativeHandle); + + // do not native_handle_close() here as handle is owned by java + native_handle_delete(nativeHandle); + } + } + // unidirectional parameter passing, thus JNI_ABORT + _env->ReleaseLongArrayElements(channelData, data, JNI_ABORT); + } + } + return ret; +} + +static void nativeDestroyDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager, + jint channelHandle) { + SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager); + mgr->destroyDirectChannel(channelHandle); +} + +static jint nativeConfigDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager, + jint channelHandle, jint sensorHandle, jint rate) { + SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager); + return mgr->configureDirectChannel(channelHandle, sensorHandle, rate); +} + //---------------------------------------------------------------------------- class Receiver : public LooperCallback { @@ -447,7 +496,19 @@ static const JNINativeMethod gSystemSensorManagerMethods[] = { {"nativeIsDataInjectionEnabled", "(J)Z", - (void*)nativeIsDataInjectionEnabled}, + (void*)nativeIsDataInjectionEnabled }, + + {"nativeCreateDirectChannel", + "(JJI[J)I", + (void*)nativeCreateDirectChannel }, + + {"nativeDestroyDirectChannel", + "(JI)V", + (void*)nativeDestroyDirectChannel }, + + {"nativeConfigDirectChannel", + "(JIII)I", + (void*)nativeConfigDirectChannel }, }; static const JNINativeMethod gBaseEventQueueMethods[] = { diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp index 3eccc42df1f5..d75d5c179d3c 100644 --- a/core/jni/android_view_RenderNode.cpp +++ b/core/jni/android_view_RenderNode.cpp @@ -88,7 +88,6 @@ void onRenderNodeRemoved(JNIEnv* env, RenderNode* node) { return; } - node->setStagingDisplayList(nullptr, nullptr); // Update the valid field, since native has already removed // the staging DisplayList env->SetBooleanField(jnode, gRenderNode_validFieldID, false); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 6d48862ea132..bfbea5ba68d7 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2645,6 +2645,21 @@ <permission android:name="android.permission.MEDIA_CONTENT_CONTROL" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi @hide Allows an application to set the volume key long-press listener. + <p>When it's set, the application will receive the volume key long-press event + instead of changing volume.</p> + <p>Not for use by third-party applications</p> --> + <permission android:name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER" + android:protectionLevel="signature|privileged|development" /> + + <!-- @SystemApi @hide Allows an application to set media key event listener. + <p>When it's set, the application will receive the media key event before + any other media sessions. If the event is handled by the listener, other sessions + cannot get the event.</p> + <p>Not for use by third-party applications</p> --> + <permission android:name="android.permission.SET_MEDIA_KEY_LISTENER" + android:protectionLevel="signature|privileged|development" /> + <!-- @SystemApi Required to be able to disable the device (very dangerous!). <p>Not for use by third-party applications. @hide diff --git a/core/res/res/anim/app_starting_exit.xml b/core/res/res/anim/app_starting_exit.xml index aaf7f156883b..dfa42e206c28 100644 --- a/core/res/res/anim/app_starting_exit.xml +++ b/core/res/res/anim/app_starting_exit.xml @@ -21,8 +21,8 @@ <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:detachWallpaper="true" - android:interpolator="@interpolator/decelerate_quad" + android:interpolator="@interpolator/linear" android:fromAlpha="1.0" android:toAlpha="0.0" - android:duration="160" /> + android:duration="150" /> diff --git a/core/res/res/values-mcc214-mnc01/config.xml b/core/res/res/values-mcc214-mnc01/config.xml index 895b770d771c..24150a782d22 100644 --- a/core/res/res/values-mcc214-mnc01/config.xml +++ b/core/res/res/values-mcc214-mnc01/config.xml @@ -40,27 +40,4 @@ <item>INTERNET,airtelnet.es,,,vodafone,vodafone,,,,,214,01,1,DUN</item> </string-array> - <string-array translatable="false" name="config_operatorConsideredNonRoaming"> - <item>21402</item> - <item>21403</item> - <item>21404</item> - <item>21405</item> - <item>21406</item> - <item>21407</item> - <item>21408</item> - <item>21409</item> - <item>21410</item> - <item>21411</item> - <item>21412</item> - <item>21413</item> - <item>21414</item> - <item>21415</item> - <item>21416</item> - <item>21417</item> - <item>21418</item> - <item>21419</item> - <item>21420</item> - <item>21421</item> - </string-array> - </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index df7a5f523caa..c3336298f5c8 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2274,13 +2274,16 @@ <!-- Sets the padding, in pixels, of the end edge; see {@link android.R.attr#padding}. --> <attr name="paddingEnd" format="dimension" /> - <!-- Boolean that controls whether a view can take focus. By default the user can not - move focus to a view; by setting this attribute to true the view is - allowed to take focus. This value does not impact the behavior of + <!-- Controls whether a view can take focus. By default, this is "auto" which lets the + framework determine whether a user can move focus to a view. By setting this attribute + to true the view is allowed to take focus. By setting it to "false" the view will not + take focus. This value does not impact the behavior of directly calling {@link android.view.View#requestFocus}, which will always request focus regardless of this view. It only impacts where focus navigation will try to move focus. --> - <attr name="focusable" format="boolean" /> + <attr name="focusable" format="boolean|enum"> + <enum name="auto" value="0x00000010" /> + </attr> <!-- Boolean that controls whether a view can take focus while in touch mode. If this is true for a view, that view can gain focus when clicked on, and can keep @@ -5836,6 +5839,14 @@ <attr name="color" /> </declare-styleable> + <!-- Drawable used to draw masked icons with foreground and background layers. --> + <declare-styleable name="MaskableIconDrawableLayer"> + <!-- The color to use for the layer, only if drawable is not defined. --> + <attr name="color" /> + <!-- The drawable to use for the layer. --> + <attr name="drawable" /> + </declare-styleable> + <!-- Drawable used to show animated touch feedback. --> <declare-styleable name="RippleDrawable"> <!-- The color to use for ripple effects. This attribute is required. --> @@ -7912,7 +7923,6 @@ <attr name="queryBackground" format="reference" /> <!-- Background for the section containing the action (e.g. voice search) --> <attr name="submitBackground" format="reference" /> - <attr name="focusable" /> </declare-styleable> <declare-styleable name="Switch"> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 5235116f7a08..113ace34ce52 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -864,9 +864,8 @@ <flag name="density" value="0x1000" /> <!-- The layout direction has changed. For example going from LTR to RTL. --> <flag name="layoutDirection" value="0x2000" /> - <!-- The colorimetry capabilities of the screen have changed (color gamut - or dynamic range). --> - <flag name="colorimetry" value="0x4000" /> + <!-- The color mode of the screen has changed (color gamut or dynamic range). --> + <flag name="colorMode" value="0x4000" /> <!-- The font scaling factor has changed, that is the user has selected a new global font size. --> <flag name="fontScale" value="0x40000000" /> @@ -1159,18 +1158,15 @@ resizeable activities when in multi-window mode. --> <attr name="resizeableActivity" format="boolean" /> - <!-- Indicates that the activity supports the picture-in-picture (PiP) form of multi-window. - While it makes sense to be able to resize most activities types in multi-window mode when - {@link android.R.attr#resizeableActivity} is set. It only makes sense to put specific types - of activities in PiP mode of multi-window. For example, activities that play video. When - set the activity will be allowed to enter PiP mode when the system deems it appropriate on - devices that support PiP. + <!-- Indicates that the activity specifically supports the picture-in-picture form of + multi-window. If true, this activity will support entering picture-in-picture, but will + only support split-screen and other forms of multi-window if + {@link android.R.attr#resizeableActivity} is also set to true. - <p>The default value is <code>false</code> for applications with - <code>targetSdkVersion</code> lesser than {@link android.os.Build.VERSION_CODES#N} and - <code>true</code> otherwise. + Note that your activity may still be resized even if this attribute is true and + {@link android.R.attr#resizeableActivity} is false. - <p>NOTE: Attribute is only used if {@link android.R.attr#resizeableActivity} is true. --> + <p>The default value is <code>false</code>. --> <attr name="supportsPictureInPicture" format="boolean" /> <!-- This value indicates how tasks rooted at this activity will behave in lockTask mode. @@ -1240,6 +1236,10 @@ <!-- An XML resource with the application's Network Security Config. --> <attr name="networkSecurityConfig" format="reference" /> + <!-- When an application is partitioned into splits, this is the name of the + split that contains the defined component. --> + <attr name="splitName" format="string" /> + <!-- The <code>manifest</code> tag is the root of an <code>AndroidManifest.xml</code> file, describing the contents of an Android package (.apk) file. One @@ -1823,6 +1823,8 @@ <attr name="singleUser" /> <attr name="directBootAware" /> <attr name="visibleToInstantApps" /> + <!-- The code for this component is located in the given split. --> + <attr name="splitName" /> </declare-styleable> <!-- Attributes that can be supplied in an AndroidManifest.xml @@ -1913,6 +1915,8 @@ must also be {@link android.R.attr#exported} if this flag is set. --> <attr name="externalService" format="boolean" /> <attr name="visibleToInstantApps" /> + <!-- The code for this component is located in the given split. --> + <attr name="splitName" /> </declare-styleable> <!-- The <code>receiver</code> tag declares an @@ -2036,6 +2040,18 @@ <attr name="onTopLauncher" format="boolean" /> <attr name="rotationAnimation" /> <attr name="visibleToInstantApps" /> + <!-- The code for this component is located in the given split. --> + <attr name="splitName" /> + <!-- Specify the color mode the activity desires. The requested color mode may be ignored + depending on the capabilities of the display the activity is displayed on. --> + <attr name="colorMode"> + <!-- The default color mode (typically sRGB, low-dynamic range). --> + <enum name="default" value="0" /> + <!-- Wide color gamut color mode. --> + <enum name="wideColorGamut" value="1" /> + <!-- High dynamic range color mode. --> + <enum name="hdr" value="2" /> + </attr> </declare-styleable> <!-- The <code>activity-alias</code> tag declares a new diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 7de48d3305bb..6a8b556386df 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2090,6 +2090,7 @@ <item>com.android.server.notification.ImportanceExtractor</item> <item>com.android.server.notification.NotificationIntrusivenessExtractor</item> <item>com.android.server.notification.VisibilityExtractor</item> + <item>com.android.server.notification.BadgeExtractor</item> </string-array> <!-- Flag indicating that this device does not rotate and will always remain in its default @@ -2624,8 +2625,14 @@ <!-- Component that is the default launcher when demo mode is enabled. --> <string name="config_demoModeLauncherComponent">com.android.retaildemo/.DemoPlayer</string> - <!-- Hashed password (SHA-256) used to restrict demo mode operation --> - <string name="config_demoModePassword" translatable="false"></string> + <!-- Hashed password (SHA-256) used to restrict carrier demo mode operation. --> + <string name="config_carrierDemoModePassword" translatable="false"></string> + + <!-- Secure setting used to activate carrier demo mode. --> + <string name="config_carrierDemoModeSetting" translatable="false"></string> + + <!-- List of packages to enable in carrier demo mode (comma separated). --> + <string name="config_carrierDemoModePackages" translatable="false"></string> <!-- Flag indicating whether round icons should be parsed from the application manifest. --> <bool name="config_useRoundIcon">false</bool> @@ -2711,6 +2718,9 @@ <!-- Component name of the default cell broadcast receiver --> <string name="config_defaultCellBroadcastReceiverComponent" translatable="false">com.android.cellbroadcastreceiver/.PrivilegedCellBroadcastReceiver</string> + <!-- Specifies the path that is used by MaskableIconDrawable class to crop launcher icons. --> + <string name="config_icon_mask" translatable="false">"M50,0L100,0 100,100 0,100 0,0z"</string> + <!-- The component name, flattened to a string, for the default accessibility service to be enabled by the accessibility shortcut. This service must be trusted, as it can be activated without explicit consent of the user. If no accessibility service with the specified name diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml index f351b7008547..613616fa0ea6 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -97,6 +97,7 @@ <item type="id" name="redo" /> <item type="id" name="replaceText" /> <item type="id" name="shareText" /> + <item type="id" name="textAssist" /> <item type="id" name="selection_start_handle" /> <item type="id" name="selection_end_handle" /> <item type="id" name="insertion_handle" /> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 40d0e45e7d3b..e387650ca2dd 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2787,12 +2787,15 @@ <public name="supportsDismissingWindow" /> <public name="restartOnConfigChanges" /> <public name="certDigest" /> + <public name="splitName" /> + <public name="colorMode" /> </public-group> <public-group type="style" first-id="0x010302e0"> </public-group> <public-group type="id" first-id="0x01020041"> + <public name="textAssist" /> </public-group> <public type="attr" name="primaryContentAlpha" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index d09b19069e77..eece9fc36c30 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2589,6 +2589,15 @@ <!-- Title for EditText context menu [CHAR LIMIT=20] --> <string name="editTextMenuTitle">Text actions</string> + <!-- Label for item in the text selection menu to trigger an Email app [CHAR LIMIT=20] --> + <string name="email">Email</string> + + <!-- Label for item in the text selection menu to trigger a Dialer app [CHAR LIMIT=20] --> + <string name="dial">Dial</string> + + <!-- Label for item in the text selection menu to trigger a Map app [CHAR LIMIT=20] --> + <string name="map">Map</string> + <!-- If the device is getting low on internal storage, a notification is shown to the user. This is the title of that notification. --> <string name="low_internal_storage_view_title">Storage space running out</string> <!-- If the device is getting low on internal storage, a notification is shown to the user. This is the message of that notification. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 6eb3bee67741..58c925f9746d 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -476,6 +476,9 @@ <java-symbol type="string" name="replace" /> <java-symbol type="string" name="undo" /> <java-symbol type="string" name="redo" /> + <java-symbol type="string" name="email" /> + <java-symbol type="string" name="dial" /> + <java-symbol type="string" name="map" /> <java-symbol type="string" name="textSelectionCABTitle" /> <java-symbol type="string" name="BaMmi" /> <java-symbol type="string" name="CLIRDefaultOffNextCallOff" /> @@ -1133,7 +1136,9 @@ <java-symbol type="string" name="config_ethernet_tcp_buffers" /> <java-symbol type="string" name="config_wifi_tcp_buffers" /> <java-symbol type="string" name="config_demoModeLauncherComponent" /> - <java-symbol type="string" name="config_demoModePassword" /> + <java-symbol type="string" name="config_carrierDemoModePassword" /> + <java-symbol type="string" name="config_carrierDemoModeSetting" /> + <java-symbol type="string" name="config_carrierDemoModePackages" /> <java-symbol type="string" name="demo_starting_message" /> <java-symbol type="string" name="demo_restarting_message" /> <java-symbol type="string" name="conference_call" /> @@ -2796,6 +2801,8 @@ <java-symbol type="raw" name="fallback_categories" /> + <java-symbol type="string" name="config_icon_mask" /> + <java-symbol type="attr" name="primaryContentAlpha" /> <!-- Accessibility Shortcut --> diff --git a/core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java b/core/tests/coretests/src/android/metrics/LogMakerTest.java index a3405591182d..0f75cb67795f 100644 --- a/core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java +++ b/core/tests/coretests/src/android/metrics/LogMakerTest.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.internal.logging; +package android.metrics; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import junit.framework.TestCase; -public class LogBuilderTest extends TestCase { +public class LogMakerTest extends TestCase { public void testSerialize() { - LogBuilder builder = new LogBuilder(0); + LogMaker builder = new LogMaker(0); builder.addTaggedData(1, "one"); builder.addTaggedData(2, "two"); Object[] out = builder.serialize(); @@ -41,7 +41,7 @@ public class LogBuilderTest extends TestCase { int bucket = 13; int value = 14; - LogBuilder builder = new LogBuilder(category); + LogMaker builder = new LogMaker(category); builder.setType(type); builder.setSubtype(subtype); builder.setTimestamp(timestamp); @@ -53,7 +53,7 @@ public class LogBuilderTest extends TestCase { builder.addTaggedData(2, "two"); Object[] out = builder.serialize(); - LogBuilder parsed = new LogBuilder(out); + LogMaker parsed = new LogMaker(out); assertEquals(category, parsed.getCategory()); assertEquals(type, parsed.getType()); @@ -68,7 +68,7 @@ public class LogBuilderTest extends TestCase { } public void testIntBucket() { - LogBuilder builder = new LogBuilder(0); + LogMaker builder = new LogMaker(0); builder.setCounterBucket(100); assertEquals(100, builder.getCounterBucket()); assertEquals(false, builder.isLongCounterBucket()); @@ -76,14 +76,14 @@ public class LogBuilderTest extends TestCase { public void testLongBucket() { long longBucket = Long.MAX_VALUE; - LogBuilder builder = new LogBuilder(0); + LogMaker builder = new LogMaker(0); builder.setCounterBucket(longBucket); assertEquals(longBucket, builder.getCounterBucket()); assertEquals(true, builder.isLongCounterBucket()); } public void testInvalidInputThrows() { - LogBuilder builder = new LogBuilder(0); + LogMaker builder = new LogMaker(0); boolean threw = false; try { builder.addTaggedData(0, new Object()); @@ -95,7 +95,7 @@ public class LogBuilderTest extends TestCase { } public void testValidInputTypes() { - LogBuilder builder = new LogBuilder(0); + LogMaker builder = new LogMaker(0); builder.addTaggedData(1, "onetwothree"); builder.addTaggedData(2, 123); builder.addTaggedData(3, 123L); @@ -107,11 +107,21 @@ public class LogBuilderTest extends TestCase { assertEquals(123.0F, out[7]); } - public void testCategoryDefault() { - LogBuilder builder = new LogBuilder(10); + public void testCategoryDefault() { + LogMaker builder = new LogMaker(10); Object[] out = builder.serialize(); assertEquals(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY, out[0]); assertEquals(10, out[1]); } + public void testGiantLogOmitted() { + LogMaker badBuilder = new LogMaker(0); + StringBuilder b = new StringBuilder(); + for (int i = 0; i < 4000; i++) { + b.append("test, " + i); + } + badBuilder.addTaggedData(100, b.toString()); + assertTrue(badBuilder.serialize().length < LogMaker.MAX_SERIALIZED_SIZE); + } + } diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java new file mode 100644 index 000000000000..1afe9da2bfe6 --- /dev/null +++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java @@ -0,0 +1,75 @@ +package android.net; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.when; + +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiSsid; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class NetworkKeyTest { + private static final String VALID_SSID = "\"ssid1\""; + private static final String VALID_BSSID = "00:00:00:00:00:00"; + @Mock private WifiInfo mWifiInfo; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void createFromWifi_nullInput() throws Exception { + assertNull(NetworkKey.createFromWifiInfo(null)); + } + + @Test + public void createFromWifi_nullSsid() throws Exception { + when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID); + assertNull(NetworkKey.createFromWifiInfo(mWifiInfo)); + } + + @Test + public void createFromWifi_emptySsid() throws Exception { + when(mWifiInfo.getSSID()).thenReturn(""); + when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID); + assertNull(NetworkKey.createFromWifiInfo(mWifiInfo)); + } + + @Test + public void createFromWifi_noneSsid() throws Exception { + when(mWifiInfo.getSSID()).thenReturn(WifiSsid.NONE); + when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID); + assertNull(NetworkKey.createFromWifiInfo(mWifiInfo)); + } + + @Test + public void createFromWifi_nullBssid() throws Exception { + when(mWifiInfo.getSSID()).thenReturn(VALID_SSID); + assertNull(NetworkKey.createFromWifiInfo(mWifiInfo)); + } + + @Test + public void createFromWifi_emptyBssid() throws Exception { + when(mWifiInfo.getSSID()).thenReturn(VALID_SSID); + when(mWifiInfo.getBSSID()).thenReturn(""); + assertNull(NetworkKey.createFromWifiInfo(mWifiInfo)); + } + + @Test + public void createFromWifi_validWifiInfo() throws Exception { + when(mWifiInfo.getSSID()).thenReturn(VALID_SSID); + when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID); + + NetworkKey expected = new NetworkKey(new WifiKey(VALID_SSID, VALID_BSSID)); + final NetworkKey actual = NetworkKey.createFromWifiInfo(mWifiInfo); + assertEquals(expected, actual); + } +} diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java index 34c34d7c07bb..dc7541709dac 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java +++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java @@ -33,7 +33,8 @@ import java.util.Arrays; import java.util.List; public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTestCase { - private static final String DUMMY_PACKAGE_NAME = "dymmy package name"; + private static final String DUMMY_PACKAGE_NAME = "dummy package name"; + private static final String DUMMY_IME_LABEL = "dummy ime label"; private static final String DUMMY_SETTING_ACTIVITY_NAME = ""; private static final boolean DUMMY_IS_AUX_IME = false; private static final boolean DUMMY_FORCE_DEFAULT = false; @@ -88,6 +89,35 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe } } + private static ImeSubtypeListItem createDummyItem(String imeName, + String subtypeName, String subtypeLocale, int subtypeIndex, String systemLocale) { + final ResolveInfo ri = new ResolveInfo(); + final ServiceInfo si = new ServiceInfo(); + final ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = DUMMY_PACKAGE_NAME; + ai.enabled = true; + si.applicationInfo = ai; + si.enabled = true; + si.packageName = DUMMY_PACKAGE_NAME; + si.name = imeName; + si.exported = true; + si.nonLocalizedLabel = DUMMY_IME_LABEL; + ri.serviceInfo = si; + ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(); + subtypes.add(new InputMethodSubtypeBuilder() + .setSubtypeNameResId(0) + .setSubtypeIconResId(0) + .setSubtypeLocale(subtypeLocale) + .setIsAsciiCapable(true) + .build()); + final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME, + DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID, + DUMMY_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */, + false /* supportsDismissingWindow */); + return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale, + systemLocale); + } + private static List<ImeSubtypeListItem> createEnabledImeSubtypes() { final List<ImeSubtypeListItem> items = new ArrayList<>(); addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"), @@ -329,4 +359,56 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe assertFalse(item_e.mIsSystemLocale); assertFalse(item_EN_US.mIsSystemLocale); } + + @SmallTest + public void testImeSubtypeListComparator() throws Exception { + { + final List<ImeSubtypeListItem> items = Arrays.asList( + createDummyItem("X", "A", "en_US", 0, "en_US"), + createDummyItem("X", "A", "en", 1, "en_US"), + createDummyItem("X", "A", "ja", 2, "en_US"), + createDummyItem("X", "Z", "en_US", 3, "en_US"), + createDummyItem("X", "Z", "en", 4, "en_US"), + createDummyItem("X", "Z", "ja", 5, "en_US"), + createDummyItem("X", "", "en_US", 6, "en_US"), + createDummyItem("X", "", "en", 7, "en_US"), + createDummyItem("X", "", "ja", 8, "en_US"), + createDummyItem("Y", "A", "en_US", 9, "en_US"), + createDummyItem("Y", "A", "en", 10, "en_US"), + createDummyItem("Y", "A", "ja", 11, "en_US"), + createDummyItem("Y", "Z", "en_US", 12, "en_US"), + createDummyItem("Y", "Z", "en", 13, "en_US"), + createDummyItem("Y", "Z", "ja", 14, "en_US"), + createDummyItem("Y", "", "en_US", 15, "en_US"), + createDummyItem("Y", "", "en", 16, "en_US"), + createDummyItem("Y", "", "ja", 17, "en_US"), + createDummyItem("", "A", "en_US", 18, "en_US"), + createDummyItem("", "A", "en", 19, "en_US"), + createDummyItem("", "A", "ja", 20, "en_US"), + createDummyItem("", "Z", "en_US", 21, "en_US"), + createDummyItem("", "Z", "en", 22, "en_US"), + createDummyItem("", "Z", "ja", 23, "en_US"), + createDummyItem("", "", "en_US", 24, "en_US"), + createDummyItem("", "", "en", 25, "en_US"), + createDummyItem("", "", "ja", 26, "en_US")); + + for (int i = 0; i < items.size(); ++i) { + assertEquals(0, items.get(i).compareTo(items.get(i))); + for (int j = i + 1; j < items.size(); ++j) { + assertTrue(items.get(i).compareTo(items.get(j)) < 0); + assertTrue(items.get(j).compareTo(items.get(i)) > 0); + } + } + } + + { + // Following two items have the same priority. + final ImeSubtypeListItem nonSystemLocale1 = + createDummyItem("X", "A", "ja_JP", 0, "en_us"); + final ImeSubtypeListItem nonSystemLocale2 = + createDummyItem("X", "A", "hi_IN", 1, "en_us"); + assertEquals(0, nonSystemLocale1.compareTo(nonSystemLocale2)); + assertEquals(0, nonSystemLocale2.compareTo(nonSystemLocale1)); + } + } } diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/LockscreenGestureParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/LockscreenGestureParserTest.java index 0bff85075fce..c023b572493f 100644 --- a/core/tests/coretests/src/com/android/internal/logging/legacy/LockscreenGestureParserTest.java +++ b/core/tests/coretests/src/com/android/internal/logging/legacy/LockscreenGestureParserTest.java @@ -19,7 +19,7 @@ import static org.mockito.Matchers.anyObject; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; public class LockscreenGestureParserTest extends ParserTest { @@ -79,7 +79,7 @@ public class LockscreenGestureParserTest extends ParserTest { verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(t, proto.getTimestamp()); assertEquals(view, proto.getCategory()); assertEquals(MetricsEvent.TYPE_ACTION, proto.getType()); @@ -95,6 +95,6 @@ public class LockscreenGestureParserTest extends ParserTest { mParser.parseEvent(mLogger, t, objects); - verify(mLogger, times(1)).addEvent((LogBuilder) anyObject()); + verify(mLogger, times(1)).addEvent((LogMaker) anyObject()); } } diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java index 2119c25d05c2..f05205d8d1ee 100644 --- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java +++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java @@ -20,7 +20,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; public class NotificationActionClickedParserTest extends ParserTest { @@ -49,12 +49,12 @@ public class NotificationActionClickedParserTest extends ParserTest { validateGoodData(t, mTag, index, objects); } - private LogBuilder validateGoodData(int t, String tag, int index, Object[] objects) { + private LogMaker validateGoodData(int t, String tag, int index, Object[] objects) { mParser.parseEvent(mLogger, t, objects); verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(t, proto.getTimestamp()); assertEquals(MetricsEvent.NOTIFICATION_ITEM_ACTION, proto.getCategory()); assertEquals(mKeyPackage, proto.getPackageName()); @@ -69,7 +69,7 @@ public class NotificationActionClickedParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); } public void testWrongType() throws Throwable { @@ -79,7 +79,7 @@ public class NotificationActionClickedParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); } public void testBadKey() throws Throwable { @@ -89,7 +89,7 @@ public class NotificationActionClickedParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); } public void testMncTimestamps() throws Throwable { @@ -102,7 +102,7 @@ public class NotificationActionClickedParserTest extends ParserTest { objects[3] = mSinceUpdateMillis; objects[4] = mSinceVisibleMillis; - LogBuilder proto = validateGoodData(t, "", index, objects); + LogMaker proto = validateGoodData(t, "", index, objects); validateNotificationTimes(proto, mSinceCreationMillis, mSinceUpdateMillis, mSinceVisibleMillis); } diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java index 1e117eee097b..7771e84f3cbd 100644 --- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java +++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java @@ -21,12 +21,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import java.util.Collections; -import java.util.List; - import org.mockito.ArgumentCaptor; public class NotificationAlertParserTest extends ParserTest { @@ -126,7 +123,7 @@ public class NotificationAlertParserTest extends ParserTest { verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(mTime, proto.getTimestamp()); assertEquals(MetricsEvent.NOTIFICATION_ALERT, proto.getCategory()); assertEquals(mKeyPackage, proto.getPackageName()); diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java index de1691953700..77b2ed6924c5 100644 --- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java +++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java @@ -21,11 +21,9 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import java.util.List; - public class NotificationCanceledParserTest extends ParserTest { public NotificationCanceledParserTest() { @@ -57,7 +55,7 @@ public class NotificationCanceledParserTest extends ParserTest { verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(t, proto.getTimestamp()); assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory()); assertEquals(mKeyPackage, proto.getPackageName()); @@ -108,7 +106,7 @@ public class NotificationCanceledParserTest extends ParserTest { verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); validateNotificationTimes(proto, life, freshness, exposure); } @@ -121,7 +119,7 @@ public class NotificationCanceledParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); if (intentional) { - verify(mLogger, times(1)).addEvent((LogBuilder) anyObject()); + verify(mLogger, times(1)).addEvent((LogMaker) anyObject()); } } @@ -164,7 +162,7 @@ public class NotificationCanceledParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); } public void testWrongType() throws Throwable { @@ -174,7 +172,7 @@ public class NotificationCanceledParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); } public void testBadKey() throws Throwable { @@ -184,7 +182,7 @@ public class NotificationCanceledParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); } public void testIgnoreUnexpectedData() throws Throwable { diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java index 2f61dd7593c0..cc6513233fe2 100644 --- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java +++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java @@ -21,7 +21,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; public class NotificationClickedParserTest extends ParserTest { @@ -46,12 +46,12 @@ public class NotificationClickedParserTest extends ParserTest { validateGoodData(t, mTag, objects); } - private LogBuilder validateGoodData(int t, String tag, Object[] objects) { + private LogMaker validateGoodData(int t, String tag, Object[] objects) { mParser.parseEvent(mLogger, t, objects); verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(t, proto.getTimestamp()); assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory()); assertEquals(mKeyPackage, proto.getPackageName()); @@ -66,7 +66,7 @@ public class NotificationClickedParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); } public void testWrongType() throws Throwable { @@ -75,7 +75,7 @@ public class NotificationClickedParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); } public void testBadKey() throws Throwable { @@ -84,7 +84,7 @@ public class NotificationClickedParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); } public void testMncTimestamps() throws Throwable { @@ -95,7 +95,7 @@ public class NotificationClickedParserTest extends ParserTest { objects[2] = mSinceUpdateMillis; objects[3] = mSinceVisibleMillis; - LogBuilder proto = validateGoodData(t, "", objects); + LogMaker proto = validateGoodData(t, "", objects); validateNotificationTimes(proto, mSinceCreationMillis, mSinceUpdateMillis, mSinceVisibleMillis); } diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java index 86b4a4536244..f337f914ba58 100644 --- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java +++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java @@ -21,7 +21,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; public class NotificationExpansionParserTest extends ParserTest { @@ -54,12 +54,12 @@ public class NotificationExpansionParserTest extends ParserTest { validateGoodData(t, mTag, objects); } - private LogBuilder validateGoodData(int t, String tag, Object[] objects) { + private LogMaker validateGoodData(int t, String tag, Object[] objects) { mParser.parseEvent(mLogger, t, objects); verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(t, proto.getTimestamp()); assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory()); assertEquals(mKeyPackage, proto.getPackageName()); @@ -79,7 +79,7 @@ public class NotificationExpansionParserTest extends ParserTest { mParser.parseEvent(mLogger, t, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); } public void testCollapsedByUser() throws Throwable { @@ -93,7 +93,7 @@ public class NotificationExpansionParserTest extends ParserTest { mParser.parseEvent(mLogger, t, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); } public void testAutoCollapsed() throws Throwable { @@ -107,7 +107,7 @@ public class NotificationExpansionParserTest extends ParserTest { mParser.parseEvent(mLogger, t, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); } public void testMissingData() throws Throwable { @@ -115,7 +115,7 @@ public class NotificationExpansionParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); } public void testWrongType() throws Throwable { @@ -126,7 +126,7 @@ public class NotificationExpansionParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); } public void testBadKey() throws Throwable { @@ -137,7 +137,7 @@ public class NotificationExpansionParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); } public void testMncTimestamps() throws Throwable { @@ -152,7 +152,7 @@ public class NotificationExpansionParserTest extends ParserTest { objects[4] = mSinceUpdateMillis; objects[5] = mSinceVisibleMillis; - LogBuilder proto = validateGoodData(t, "", objects); + LogMaker proto = validateGoodData(t, "", objects); validateNotificationTimes(proto, mSinceCreationMillis, mSinceUpdateMillis, mSinceVisibleMillis); } diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java index 3efc48fa828a..ce6f1f4bd531 100644 --- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java +++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java @@ -19,7 +19,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; public class NotificationPanelHiddenParserTest extends ParserTest { @@ -48,7 +48,7 @@ public class NotificationPanelHiddenParserTest extends ParserTest { verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(t, proto.getTimestamp()); assertEquals(MetricsEvent.NOTIFICATION_PANEL, proto.getCategory()); assertEquals(MetricsEvent.TYPE_CLOSE, proto.getType()); diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java index 34dda983cb50..9e15812edb10 100644 --- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java +++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java @@ -20,7 +20,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; public class NotificationPanelRevealedParserTest extends ParserTest { @@ -37,7 +37,7 @@ public class NotificationPanelRevealedParserTest extends ParserTest { verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(t, proto.getTimestamp()); assertEquals(MetricsEvent.NOTIFICATION_PANEL, proto.getCategory()); assertEquals(MetricsEvent.TYPE_OPEN, proto.getType()); @@ -57,7 +57,7 @@ public class NotificationPanelRevealedParserTest extends ParserTest { verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(t, proto.getTimestamp()); assertEquals(MetricsEvent.NOTIFICATION_PANEL, proto.getCategory()); assertEquals(MetricsEvent.TYPE_OPEN, proto.getType()); @@ -69,7 +69,7 @@ public class NotificationPanelRevealedParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, times(1)).addEvent((LogBuilder) anyObject()); + verify(mLogger, times(1)).addEvent((LogMaker) anyObject()); } public void testIgnoreUnexpectedData() throws Throwable { diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java index cc5421c0b422..7fef929e6765 100644 --- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java +++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java @@ -16,17 +16,13 @@ package com.android.internal.logging.legacy; import static junit.framework.Assert.assertTrue; -import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import org.mockito.ArgumentCaptor; - public class NotificationVisibilityParserTest extends ParserTest { private final int mCreationTime = 23124; private final int mUpdateTime = 3412; @@ -84,7 +80,7 @@ public class NotificationVisibilityParserTest extends ParserTest { verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(mTime, proto.getTimestamp()); assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory()); assertEquals(mKeyPackage, proto.getPackageName()); diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/ParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/ParserTest.java index 2e5c6d2a40b0..4adf62991d66 100644 --- a/core/tests/coretests/src/com/android/internal/logging/legacy/ParserTest.java +++ b/core/tests/coretests/src/com/android/internal/logging/legacy/ParserTest.java @@ -15,7 +15,7 @@ */ package com.android.internal.logging.legacy; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import static org.mockito.Matchers.any; @@ -39,8 +39,8 @@ public class ParserTest extends TestCase { protected TagParser mParser; - protected LogBuilder[] mProto; - protected ArgumentCaptor<LogBuilder> mProtoCaptor; + protected LogMaker[] mProto; + protected ArgumentCaptor<LogMaker> mProtoCaptor; protected ArgumentCaptor<String> mNameCaptor; protected ArgumentCaptor<Integer> mCountCaptor; protected String mKey = "0|com.android.example.notificationshowcase|31338|null|10090"; @@ -54,9 +54,9 @@ public class ParserTest extends TestCase { public ParserTest() { - mProto = new LogBuilder[5]; + mProto = new LogMaker[5]; for (int i = 0; i < mProto.length; i++) { - mProto[i] = new LogBuilder(MetricsEvent.VIEW_UNKNOWN); + mProto[i] = new LogMaker(MetricsEvent.VIEW_UNKNOWN); } } @@ -66,19 +66,19 @@ public class ParserTest extends TestCase { MockitoAnnotations.initMocks(this); - mProtoCaptor = ArgumentCaptor.forClass(LogBuilder.class); + mProtoCaptor = ArgumentCaptor.forClass(LogMaker.class); mNameCaptor = ArgumentCaptor.forClass(String.class); mCountCaptor = ArgumentCaptor.forClass(Integer.class); - OngoingStubbing<LogBuilder> stub = when(mLogger.obtain()).thenReturn(mProto[0]); + OngoingStubbing<LogMaker> stub = when(mLogger.obtain()).thenReturn(mProto[0]); for (int i = 1; i < mProto.length; i++) { stub.thenReturn(mProto[i]); } - doNothing().when(mLogger).addEvent(any(LogBuilder.class)); + doNothing().when(mLogger).addEvent(any(LogMaker.class)); doNothing().when(mLogger).incrementBy(anyString(), anyInt()); } - protected void validateNotificationTimes(LogBuilder proto, int life, int freshness, + protected void validateNotificationTimes(LogMaker proto, int life, int freshness, int exposure) { validateNotificationTimes(proto, life, freshness); if (exposure != 0) { @@ -89,7 +89,7 @@ public class ParserTest extends TestCase { } } - protected void validateNotificationTimes(LogBuilder proto, int life, int freshness) { + protected void validateNotificationTimes(LogMaker proto, int life, int freshness) { if (life != 0) { assertEquals(life, proto.getTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS)); @@ -104,7 +104,7 @@ public class ParserTest extends TestCase { } } - protected void validateNotificationIdAndTag(LogBuilder proto, int id, String tag) { + protected void validateNotificationIdAndTag(LogMaker proto, int id, String tag) { assertEquals(tag, proto.getTaggedData(MetricsEvent.NOTIFICATION_TAG)); assertEquals(id, proto.getTaggedData(MetricsEvent.NOTIFICATION_ID)); } diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/PowerScreenStateParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/PowerScreenStateParserTest.java index be918cd91921..b480e61e1ac1 100644 --- a/core/tests/coretests/src/com/android/internal/logging/legacy/PowerScreenStateParserTest.java +++ b/core/tests/coretests/src/com/android/internal/logging/legacy/PowerScreenStateParserTest.java @@ -19,7 +19,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; public class PowerScreenStateParserTest extends ParserTest { @@ -60,7 +60,7 @@ public class PowerScreenStateParserTest extends ParserTest { verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(t, proto.getTimestamp()); assertEquals(type, proto.getType()); assertEquals(MetricsEvent.SCREEN, proto.getCategory()); diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/StatusBarStateParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/StatusBarStateParserTest.java index 906ec0403d86..def9628e7fbd 100644 --- a/core/tests/coretests/src/com/android/internal/logging/legacy/StatusBarStateParserTest.java +++ b/core/tests/coretests/src/com/android/internal/logging/legacy/StatusBarStateParserTest.java @@ -19,7 +19,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; public class StatusBarStateParserTest extends ParserTest { @@ -64,7 +64,7 @@ public class StatusBarStateParserTest extends ParserTest { verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(t, proto.getTimestamp()); assertEquals(view, proto.getCategory()); assertEquals(type, proto.getType()); diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java index 111909f67912..2ad76c13e76b 100644 --- a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java +++ b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java @@ -23,7 +23,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; public class SysuiActionParserTest extends ParserTest { @@ -47,7 +47,7 @@ public class SysuiActionParserTest extends ParserTest { verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); verify(mLogger, never()).incrementBy(anyString(), anyInt()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(t, proto.getTimestamp()); assertEquals(view, proto.getCategory()); assertEquals(MetricsEvent.TYPE_ACTION, proto.getType()); @@ -66,7 +66,7 @@ public class SysuiActionParserTest extends ParserTest { verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); verify(mLogger, never()).incrementBy(anyString(), anyInt()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(t, proto.getTimestamp()); assertEquals(view, proto.getCategory()); assertEquals(packageName, proto.getPackageName()); @@ -117,7 +117,7 @@ public class SysuiActionParserTest extends ParserTest { verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); verify(mLogger, never()).incrementBy(anyString(), anyInt()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(t, proto.getTimestamp()); assertEquals(view, proto.getCategory()); assertEquals(expectedValue, proto.getSubtype()); @@ -130,7 +130,7 @@ public class SysuiActionParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); verify(mLogger, never()).incrementBy(anyString(), anyInt()); } @@ -141,7 +141,7 @@ public class SysuiActionParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); verify(mLogger, never()).incrementBy(anyString(), anyInt()); } @@ -151,7 +151,7 @@ public class SysuiActionParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); verify(mLogger, never()).incrementBy(anyString(), anyInt()); } diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiMultiActionParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiMultiActionParserTest.java index 7853f775c200..e7a05d88f430 100644 --- a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiMultiActionParserTest.java +++ b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiMultiActionParserTest.java @@ -15,16 +15,11 @@ */ package com.android.internal.logging.legacy; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import com.android.internal.logging.LogBuilder; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import android.metrics.LogMaker; public class SysuiMultiActionParserTest extends ParserTest { @@ -41,7 +36,7 @@ public class SysuiMultiActionParserTest extends ParserTest { String counterName = "sheep"; int bucket = 13; int value = 14; - LogBuilder builder = new LogBuilder(category); + LogMaker builder = new LogMaker(category); builder.setType(type); builder.setSubtype(subtype); builder.setPackageName(packageName); @@ -57,7 +52,7 @@ public class SysuiMultiActionParserTest extends ParserTest { verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(category, proto.getCategory()); assertEquals(type, proto.getType()); assertEquals(subtype, proto.getSubtype()); diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java index 1291508663cc..64d69a499901 100644 --- a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java +++ b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java @@ -23,7 +23,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; public class SysuiViewVisibilityParserTest extends ParserTest { @@ -47,7 +47,7 @@ public class SysuiViewVisibilityParserTest extends ParserTest { verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(t, proto.getTimestamp()); assertEquals(view, proto.getCategory()); assertEquals(MetricsEvent.TYPE_OPEN, proto.getType()); @@ -64,7 +64,7 @@ public class SysuiViewVisibilityParserTest extends ParserTest { verify(mLogger, times(1)).addEvent(mProtoCaptor.capture()); - LogBuilder proto = mProtoCaptor.getValue(); + LogMaker proto = mProtoCaptor.getValue(); assertEquals(MetricsEvent.TYPE_CLOSE, proto.getType()); } @@ -73,7 +73,7 @@ public class SysuiViewVisibilityParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); verify(mLogger, never()).incrementBy(anyString(), anyInt()); } @@ -84,7 +84,7 @@ public class SysuiViewVisibilityParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); verify(mLogger, never()).incrementBy(anyString(), anyInt()); } @@ -95,7 +95,7 @@ public class SysuiViewVisibilityParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); verify(mLogger, never()).incrementBy(anyString(), anyInt()); } @@ -105,7 +105,7 @@ public class SysuiViewVisibilityParserTest extends ParserTest { mParser.parseEvent(mLogger, 0, objects); - verify(mLogger, never()).addEvent((LogBuilder) anyObject()); + verify(mLogger, never()).addEvent((LogMaker) anyObject()); verify(mLogger, never()).incrementBy(anyString(), anyInt()); } diff --git a/data/etc/framework-sysconfig.xml b/data/etc/framework-sysconfig.xml index 2f18de0b1797..62ef25b1af0d 100644 --- a/data/etc/framework-sysconfig.xml +++ b/data/etc/framework-sysconfig.xml @@ -21,5 +21,6 @@ delivery restrictions. --> <allow-implicit-broadcast action="android.intent.action.SIM_STATE_CHANGED" /> <allow-implicit-broadcast action="android.intent.action.PACKAGE_CHANGED" /> + <allow-implicit-broadcast action="android.intent.action.MEDIA_SCANNER_SCAN_FILE" /> </config> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index f1be4a97d403..c5961abc1523 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -303,6 +303,7 @@ applications that come with the platform <permission name="android.permission.BIND_APPWIDGET"/> <permission name="android.permission.BLUETOOTH_PRIVILEGED"/> <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/> + <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/> <permission name="android.permission.CONNECTIVITY_INTERNAL"/> <permission name="android.permission.CONTROL_VPN"/> <permission name="android.permission.DUMP"/> diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index 8673e0bb6a9c..2c93c325f5fc 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -19,6 +19,7 @@ package android.graphics; import android.content.res.AssetManager; import android.text.FontConfig; import android.util.Log; +import dalvik.annotation.optimization.CriticalNative; import java.io.FileInputStream; import java.io.IOException; @@ -40,11 +41,11 @@ public class FontFamily { */ public long mNativePtr; + // Points native font family builder. Must be zero after freezing this family. + private long mBuilderPtr; + public FontFamily() { - mNativePtr = nCreateFamily(null, 0); - if (mNativePtr == 0) { - throw new IllegalStateException("error creating native FontFamily"); - } + mBuilderPtr = nInitBuilder(null, 0); } public FontFamily(String lang, String variant) { @@ -54,27 +55,48 @@ public class FontFamily { } else if ("elegant".equals(variant)) { varEnum = 2; } - mNativePtr = nCreateFamily(lang, varEnum); - if (mNativePtr == 0) { - throw new IllegalStateException("error creating native FontFamily"); + mBuilderPtr = nInitBuilder(lang, varEnum); + } + + public void freeze() { + if (mBuilderPtr == 0) { + throw new IllegalStateException("This FontFamily is already frozen"); + } + mNativePtr = nCreateFamily(mBuilderPtr); + mBuilderPtr = 0; + } + + public void abortCreation() { + if (mBuilderPtr == 0) { + throw new IllegalStateException("This FontFamily is already frozen or abandoned"); } + nAbort(mBuilderPtr); + mBuilderPtr = 0; } @Override protected void finalize() throws Throwable { try { - nUnrefFamily(mNativePtr); + if (mNativePtr != 0) { + nUnrefFamily(mNativePtr); + } + if (mBuilderPtr != 0) { + nAbort(mBuilderPtr); + } } finally { super.finalize(); } } public boolean addFont(String path, int ttcIndex) { + if (mBuilderPtr == 0) { + throw new IllegalStateException("Unable to call addFont after freezing."); + } try (FileInputStream file = new FileInputStream(path)) { FileChannel fileChannel = file.getChannel(); long fontSize = fileChannel.size(); ByteBuffer fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize); - return nAddFont(mNativePtr, fontBuffer, ttcIndex); + return nAddFont(mBuilderPtr, fontBuffer, ttcIndex); } catch (IOException e) { Log.e(TAG, "Error mapping font file " + path); return false; @@ -83,20 +105,34 @@ public class FontFamily { public boolean addFontWeightStyle(ByteBuffer font, int ttcIndex, List<FontConfig.Axis> axes, int weight, boolean style) { - return nAddFontWeightStyle(mNativePtr, font, ttcIndex, axes, weight, style); + if (mBuilderPtr == 0) { + throw new IllegalStateException("Unable to call addFontWeightStyle after freezing."); + } + return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, axes, weight, style); } public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie, boolean isAsset) { - return nAddFontFromAssetManager(mNativePtr, mgr, path, cookie, isAsset); + if (mBuilderPtr == 0) { + throw new IllegalStateException("Unable to call addFontFromAsset after freezing."); + } + return nAddFontFromAssetManager(mBuilderPtr, mgr, path, cookie, isAsset); } - private static native long nCreateFamily(String lang, int variant); + private static native long nInitBuilder(String lang, int variant); + + @CriticalNative + private static native long nCreateFamily(long mBuilderPtr); + + @CriticalNative + private static native void nAbort(long mBuilderPtr); + + @CriticalNative private static native void nUnrefFamily(long nativePtr); - private static native boolean nAddFont(long nativeFamily, ByteBuffer font, int ttcIndex); - private static native boolean nAddFontWeightStyle(long nativeFamily, ByteBuffer font, + private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex); + private static native boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font, int ttcIndex, List<FontConfig.Axis> listOfAxis, int weight, boolean isItalic); - private static native boolean nAddFontFromAssetManager(long nativeFamily, AssetManager mgr, + private static native boolean nAddFontFromAssetManager(long builderPtr, AssetManager mgr, String path, int cookie, boolean isAsset); } diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 0a349e91ad1e..7a7c7afb21f1 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -16,20 +16,34 @@ package android.graphics; +import android.annotation.IntDef; import android.annotation.NonNull; import android.content.res.AssetManager; +import android.graphics.fonts.FontRequest; +import android.graphics.fonts.FontResult; +import android.os.Bundle; +import android.os.Handler; +import android.os.ParcelFileDescriptor; +import android.os.ResultReceiver; +import android.provider.FontsContract; import android.text.FontConfig; import android.util.Log; import android.util.LongSparseArray; import android.util.LruCache; import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; + +import libcore.io.IoUtils; + import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; @@ -64,7 +78,11 @@ public class Typeface { static Typeface[] sDefaults; private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache = - new LongSparseArray<SparseArray<Typeface>>(3); + new LongSparseArray<>(3); + @GuardedBy("sLock") + private static FontsContract sFontsContract; + @GuardedBy("sLock") + private static Handler mHandler; /** * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16. @@ -74,6 +92,7 @@ public class Typeface { static Typeface sDefaultTypeface; static Map<String, Typeface> sSystemFontMap; static FontFamily[] sFallbackFonts; + private static final Object sLock = new Object(); static final String FONTS_CONFIG = "fonts.xml"; @@ -124,7 +143,7 @@ public class Typeface { FontFamily fontFamily = new FontFamily(); if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */)) { - FontFamily[] families = { fontFamily }; + FontFamily[] families = {fontFamily}; typeface = createFromFamiliesWithDefault(families); sDynamicTypefaceCache.put(key, typeface); return typeface; @@ -135,6 +154,138 @@ public class Typeface { } /** + * Create a typeface object given a font request. The font will be asynchronously fetched, + * therefore the result is delivered to the given callback. See {@link FontRequest}. + * Only one of the methods in callback will be invoked, depending on whether the request + * succeeds or fails. These calls will happen on the main thread. + * @param request A {@link FontRequest} object that identifies the provider and query for the + * request. May not be null. + * @param callback A callback that will be triggered when results are obtained. May not be null. + */ + public static void create(@NonNull FontRequest request, @NonNull FontRequestCallback callback) { + synchronized (sLock) { + if (sFontsContract == null) { + sFontsContract = new FontsContract(); + mHandler = new Handler(); + } + final ResultReceiver receiver = new ResultReceiver(null) { + @Override + public void onReceiveResult(int resultCode, Bundle resultData) { + mHandler.post(new Runnable() { + @Override + public void run() { + receiveResult(request, callback, resultCode, resultData); + } + }); + } + }; + sFontsContract.getFont(request, receiver); + } + } + + private static void receiveResult(FontRequest request, FontRequestCallback callback, + int resultCode, Bundle resultData) { + if (resultCode == FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND) { + callback.onTypefaceRequestFailed( + FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND); + return; + } + if (resultCode == FontsContract.RESULT_CODE_FONT_NOT_FOUND + || resultData == null) { + callback.onTypefaceRequestFailed( + FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND); + return; + } + List<FontResult> resultList = + resultData.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS); + if (resultList == null || resultList.isEmpty()) { + callback.onTypefaceRequestFailed( + FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND); + return; + } + FontFamily fontFamily = new FontFamily(); + for (int i = 0; i < resultList.size(); ++i) { + FontResult result = resultList.get(i); + ParcelFileDescriptor fd = result.getFileDescriptor(); + if (fd == null) { + callback.onTypefaceRequestFailed( + FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); + return; + } + try (FileInputStream is = new FileInputStream(fd.getFileDescriptor())) { + FileChannel fileChannel = is.getChannel(); + long fontSize = fileChannel.size(); + ByteBuffer fontBuffer = fileChannel.map( + FileChannel.MapMode.READ_ONLY, 0, fontSize); + int style = result.getStyle(); + int weight = (style & BOLD) != 0 ? 700 : 400; + // TODO: this method should be + // create(fd, ttcIndex, fontVariationSettings, style). + if (!fontFamily.addFontWeightStyle(fontBuffer, result.getTtcIndex(), + null, weight, (style & ITALIC) != 0)) { + Log.e(TAG, "Error creating font " + request.getQuery()); + callback.onTypefaceRequestFailed( + FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); + return; + } + } catch (IOException e) { + Log.e(TAG, "Error reading font " + request.getQuery(), e); + callback.onTypefaceRequestFailed( + FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); + return; + } finally { + IoUtils.closeQuietly(fd); + } + } + callback.onTypefaceRetrieved(Typeface.createFromFamiliesWithDefault( + new FontFamily[] {fontFamily})); + } + + /** + * Interface used to receive asynchronously fetched typefaces. + */ + public interface FontRequestCallback { + /** + * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given + * provider was not found on the device. + */ + int FAIL_REASON_PROVIDER_NOT_FOUND = 0; + /** + * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font + * returned by the provider was not loaded properly. + */ + int FAIL_REASON_FONT_LOAD_ERROR = 1; + /** + * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given + * provider did not return any results for the given query. + */ + int FAIL_REASON_FONT_NOT_FOUND = 2; + + /** @hide */ + @IntDef({FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR, + FAIL_REASON_FONT_NOT_FOUND}) + @Retention(RetentionPolicy.SOURCE) + @interface FontRequestFailReason {} + + /** + * Called then a Typeface request done via {@link Typeface#create(FontRequest, + * FontRequestCallback)} is complete. Note that this method will not be called if + * {@link #onTypefaceRequestFailed(int)} is called instead. + * @param typeface The Typeface object retrieved. + */ + void onTypefaceRetrieved(Typeface typeface); + + /** + * Called when a Typeface request done via {@link Typeface#create(FontRequest, + * FontRequestCallback)} fails. + * @param reason One of {@link #FAIL_REASON_PROVIDER_NOT_FOUND}, + * {@link #FAIL_REASON_FONT_NOT_FOUND} or + * {@link #FAIL_REASON_FONT_LOAD_ERROR}. + */ + void onTypefaceRequestFailed(@FontRequestFailReason int reason); + } + + /** * Create a typeface object given a family name, and option style information. * If null is passed for the name, then the "default" font will be chosen. * The resulting typeface object can be queried (getStyle()) to discover what @@ -222,10 +373,13 @@ public class Typeface { FontFamily fontFamily = new FontFamily(); if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */)) { + fontFamily.freeze(); FontFamily[] families = { fontFamily }; typeface = createFromFamiliesWithDefault(families); sDynamicTypefaceCache.put(key, typeface); return typeface; + } else { + fontFamily.abortCreation(); } } } @@ -271,8 +425,11 @@ public class Typeface { if (sFallbackFonts != null) { FontFamily fontFamily = new FontFamily(); if (fontFamily.addFont(path, 0 /* ttcIndex */)) { + fontFamily.freeze(); FontFamily[] families = { fontFamily }; return createFromFamiliesWithDefault(families); + } else { + fontFamily.abortCreation(); } } throw new RuntimeException("Font not found " + path); @@ -341,6 +498,7 @@ public class Typeface { Log.e(TAG, "Error creating font " + font.getFontName() + "#" + font.getTtcIndex()); } } + fontFamily.freeze(); return fontFamily; } diff --git a/graphics/java/android/graphics/drawable/DrawableInflater.java b/graphics/java/android/graphics/drawable/DrawableInflater.java index 348af70d5568..6d0bbdf549f2 100644 --- a/graphics/java/android/graphics/drawable/DrawableInflater.java +++ b/graphics/java/android/graphics/drawable/DrawableInflater.java @@ -147,6 +147,8 @@ public final class DrawableInflater { return new TransitionDrawable(); case "ripple": return new RippleDrawable(); + case "maskable-icon": + return new MaskableIconDrawable(); case "color": return new ColorDrawable(); case "shape": diff --git a/graphics/java/android/graphics/drawable/MaskableIconDrawable.java b/graphics/java/android/graphics/drawable/MaskableIconDrawable.java new file mode 100644 index 000000000000..3467b1a789e7 --- /dev/null +++ b/graphics/java/android/graphics/drawable/MaskableIconDrawable.java @@ -0,0 +1,1001 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics.drawable; + +import static android.graphics.drawable.Drawable.obtainAttributes; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.ActivityInfo.Config; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Outline; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff.Mode; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.Shader; +import android.graphics.Shader.TileMode; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.PathParser; + +import com.android.internal.R; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * This drawable supports two layers: foreground and background. + * + * <p>The layers are clipped when rendering using the mask path defined in the device configuration. + * + * <p>This class can also be created via XML inflation using <code><maskable-icon></code> tag + * in addition to dynamic creation. + */ +public class MaskableIconDrawable extends Drawable implements Drawable.Callback { + + /** + * Mask path is defined inside device configuration in following dimension: [100 x 100] + */ + public static final float MASK_SIZE = 100f; + + /** + * The view port of the layers is smaller than their intrinsic width and height by this factor. + * + * It is part of the API contract that all four sides of the layers are padded so as to provide + * extra content to reveal within the clip path when performing affine transformations on the + * layers. + */ + public static final float DEFAULT_VIEW_PORT_SCALE = 2f / 3f; + + /** + * Clip path defined in {@link com.android.internal.R.string.config_icon_mask}. + */ + private static Path sMask; + + /** + * Scaled mask based on the view bounds. + */ + private final Path mMask; + private final Matrix mMaskMatrix; + private final Region mTransparentRegion; + + private Bitmap mMaskBitmap; + + /** + * Indices used to access {@link #mLayerState.mChildDrawable} array for foreground and + * background layer. + */ + private static final int BACKGROUND_ID = 0; + private static final int FOREGROUND_ID = 1; + + /** + * State variable that maintains the {@link ChildDrawable} array. + */ + LayerState mLayerState; + + private Shader mLayersShader; + private Bitmap mLayersBitmap; + + private final Rect mTmpOutRect = new Rect(); + private Rect mHotspotBounds; + private boolean mMutated; + + private boolean mSuspendChildInvalidation; + private boolean mChildRequestedInvalidation; + private final Canvas mCanvas; + private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | + Paint.FILTER_BITMAP_FLAG); + + /** + * Constructor used for xml inflation. + */ + MaskableIconDrawable() { + this((LayerState) null, null); + } + + /** + * The one constructor to rule them all. This is called by all public + * constructors to set the state and initialize local properties. + */ + MaskableIconDrawable(@Nullable LayerState state, @Nullable Resources res) { + mLayerState = createConstantState(state, res); + + if (sMask == null) { + sMask = PathParser.createPathFromPathData( + Resources.getSystem().getString(com.android.internal.R.string.config_icon_mask)); + } + mMask = new Path(); + mMaskMatrix = new Matrix(); + mCanvas = new Canvas(); + mTransparentRegion = new Region(); + } + + private ChildDrawable createChildDrawable(Drawable drawable) { + final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity); + layer.mDrawable = drawable; + layer.mDrawable.setCallback(this); + mLayerState.mChildrenChangingConfigurations |= + layer.mDrawable.getChangingConfigurations(); + return layer; + } + + LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) { + return new LayerState(state, this, res); + } + + /** + * Constructor used to dynamically create this drawable. + * + * @param backgroundDrawable drawable that should be rendered in the background + * @param foregroundDrawable drawable that should be rendered in the foreground + */ + public MaskableIconDrawable(Drawable backgroundDrawable, + Drawable foregroundDrawable) { + this((LayerState)null, null); + addLayer(BACKGROUND_ID, createChildDrawable(backgroundDrawable)); + addLayer(FOREGROUND_ID, createChildDrawable(foregroundDrawable)); + } + + /** + * Sets the layer to the {@param index} and invalidates cache. + * + * @param index The index of the layer. + * @param layer The layer to add. + */ + private void addLayer(int index, @NonNull ChildDrawable layer) { + mLayerState.mChildren[index] = layer; + mLayerState.invalidateCache(); + } + + @Override + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { + super.inflate(r, parser, attrs, theme); + + final LayerState state = mLayerState; + if (state == null) { + return; + } + + // The density may have changed since the last update. This will + // apply scaling to any existing constant state properties. + final int density = Drawable.resolveDensity(r, 0); + state.setDensity(density); + + final ChildDrawable[] array = state.mChildren; + for (int i = 0; i < state.mChildren.length; i++) { + final ChildDrawable layer = array[i]; + layer.setDensity(density); + } + inflateLayers(r, parser, attrs, theme); + } + + /** + * @return the mask path object used to clip the drawable + */ + public Path getIconMask() { + return mMask; + } + + /** + * @return the foreground drawable managed by this drawable + */ + public Drawable getForeground() { + return mLayerState.mChildren[FOREGROUND_ID].mDrawable; + } + + /** + * @return the background drawable managed by this drawable + */ + public Drawable getBackground() { + return mLayerState.mChildren[BACKGROUND_ID].mDrawable; + } + + @Override + protected void onBoundsChange(Rect bounds) { + if (bounds.isEmpty()) { + return; + } + updateLayerBounds(bounds); + } + + private void updateLayerBounds(Rect bounds) { + try { + suspendChildInvalidation(); + updateLayerBoundsInternal(bounds); + updateMaskBoundsInternal(bounds); + } finally { + resumeChildInvalidation(); + } + } + + private void updateLayerBoundsInternal(Rect bounds) { + int cX = bounds.centerX(); + int cY = bounds.centerY(); + + for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) { + int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2)); + int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2)); + final Rect outRect = mTmpOutRect; + outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight); + + final ChildDrawable r = mLayerState.mChildren[i]; + final Drawable d = r.mDrawable; + d.setBounds(outRect); + } + } + + private void updateMaskBoundsInternal(Rect b) { + mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE); + sMask.transform(mMaskMatrix, mMask); + + mMaskBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ALPHA_8); + mCanvas.setBitmap(mMaskBitmap); + mPaint.setShader(null); + mCanvas.drawPath(mMask, mPaint); + + // reset everything that depends on the view bounds + mTransparentRegion.setEmpty(); + mLayersShader = null; + mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888); + } + + @Override + public void draw(Canvas canvas) { + if (mLayersShader == null) { + mCanvas.setBitmap(mLayersBitmap); + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final Drawable dr = mLayerState.mChildren[i].mDrawable; + if (dr != null) { + dr.draw(mCanvas); + } + } + mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP); + mPaint.setShader(mLayersShader); + } + canvas.drawBitmap(mMaskBitmap, 0.0f, 0.0f, mPaint); + } + + @Override + public void invalidateSelf() { + mLayersShader = null; + super.invalidateSelf(); + } + + @Override + public void getOutline(@NonNull Outline outline) { + outline.setConvexPath(mMask); + } + + @Override + public @Nullable Region getTransparentRegion() { + if (mTransparentRegion.isEmpty()) { + mMask.toggleInverseFillType(); + mTransparentRegion.set(getBounds()); + mTransparentRegion.setPath(mMask, mTransparentRegion); + mMask.toggleInverseFillType(); + } + return mTransparentRegion; + } + + @Override + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); + + final LayerState state = mLayerState; + if (state == null) { + return; + } + + final int density = Drawable.resolveDensity(t.getResources(), 0); + state.setDensity(density); + + final ChildDrawable[] array = state.mChildren; + for (int i = 0; i < state.N_CHILDREN; i++) { + final ChildDrawable layer = array[i]; + layer.setDensity(density); + + if (layer.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes( + layer.mThemeAttrs, R.styleable.MaskableIconDrawableLayer); + updateLayerFromTypedArray(layer, a); + a.recycle(); + } + + final Drawable d = layer.mDrawable; + if (d != null && d.canApplyTheme()) { + d.applyTheme(t); + + // Update cached mask of child changing configurations. + state.mChildrenChangingConfigurations |= d.getChangingConfigurations(); + } + } + } + + /** + * Inflates child layers using the specified parser. + */ + void inflateLayers(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { + final LayerState state = mLayerState; + + final int innerDepth = parser.getDepth() + 1; + int type; + int depth; + int childIndex = 0; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + + if (depth > innerDepth) { + continue; + } + String tagName = parser.getName(); + if (tagName.equals("background")) { + childIndex = BACKGROUND_ID; + } else if (tagName.equals("foreground")) { + childIndex = FOREGROUND_ID; + } else { + continue; + } + + final ChildDrawable layer = new ChildDrawable(state.mDensity); + final TypedArray a = obtainAttributes(r, theme, attrs, + R.styleable.MaskableIconDrawableLayer); + updateLayerFromTypedArray(layer, a); + a.recycle(); + + // If the layer doesn't have a drawable or unresolved theme + // attribute for a drawable, attempt to parse one from the child + // element. If multiple child elements exist, we'll only use the + // first one. + if (layer.mDrawable == null && (layer.mThemeAttrs == null)) { + while ((type = parser.next()) == XmlPullParser.TEXT) { + } + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException(parser.getPositionDescription() + + ": <foreground> or <background> tag requires a 'color' or 'drawable'" + + "attribute or child tag defining a drawable"); + } + + // We found a child drawable. Take ownership. + layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme); + layer.mDrawable.setCallback(this); + state.mChildrenChangingConfigurations |= + layer.mDrawable.getChangingConfigurations(); + } + addLayer(childIndex, layer); + } + } + + private void updateLayerFromTypedArray(@NonNull ChildDrawable layer, @NonNull TypedArray a) { + final LayerState state = mLayerState; + + // Account for any configuration changes. + state.mChildrenChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + layer.mThemeAttrs = a.extractThemeAttrs(); + + Drawable dr = a.getDrawable(R.styleable.MaskableIconDrawableLayer_drawable); + if (dr == null) { + int color = a.getColor(R.styleable.MaskableIconDrawableLayer_color, Color.TRANSPARENT); + if (color != Color.TRANSPARENT) { + dr = new ColorDrawable(color); + } + } + if (dr != null) { + if (layer.mDrawable != null) { + // It's possible that a drawable was already set, in which case + // we should clear the callback. We may have also integrated the + // drawable's changing configurations, but we don't have enough + // information to revert that change. + layer.mDrawable.setCallback(null); + } + + // Take ownership of the new drawable. + layer.mDrawable = dr; + layer.mDrawable.setCallback(this); + state.mChildrenChangingConfigurations |= + layer.mDrawable.getChangingConfigurations(); + } + } + + @Override + public boolean canApplyTheme() { + return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme(); + } + + /** + * @hide + */ + @Override + public boolean isProjected() { + if (super.isProjected()) { + return true; + } + + final ChildDrawable[] layers = mLayerState.mChildren; + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + if (layers[i].mDrawable.isProjected()) { + return true; + } + } + return false; + } + + /** + * Temporarily suspends child invalidation. + * + * @see #resumeChildInvalidation() + */ + private void suspendChildInvalidation() { + mSuspendChildInvalidation = true; + } + + /** + * Resumes child invalidation after suspension, immediately performing an + * invalidation if one was requested by a child during suspension. + * + * @see #suspendChildInvalidation() + */ + private void resumeChildInvalidation() { + mSuspendChildInvalidation = false; + + if (mChildRequestedInvalidation) { + mChildRequestedInvalidation = false; + invalidateSelf(); + } + } + + @Override + public void invalidateDrawable(@NonNull Drawable who) { + if (mSuspendChildInvalidation) { + mChildRequestedInvalidation = true; + } else { + invalidateSelf(); + } + } + + @Override + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { + scheduleSelf(what, when); + } + + @Override + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { + unscheduleSelf(what); + } + + @Override + public @Config int getChangingConfigurations() { + return super.getChangingConfigurations() | mLayerState.getChangingConfigurations(); + } + + @Override + public void setHotspot(float x, float y) { + final ChildDrawable[] array = mLayerState.mChildren; + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null) { + dr.setHotspot(x, y); + } + } + } + + @Override + public void setHotspotBounds(int left, int top, int right, int bottom) { + final ChildDrawable[] array = mLayerState.mChildren; + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null) { + dr.setHotspotBounds(left, top, right, bottom); + } + } + + if (mHotspotBounds == null) { + mHotspotBounds = new Rect(left, top, right, bottom); + } else { + mHotspotBounds.set(left, top, right, bottom); + } + } + + @Override + public void getHotspotBounds(Rect outRect) { + if (mHotspotBounds != null) { + outRect.set(mHotspotBounds); + } else { + super.getHotspotBounds(outRect); + } + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + final boolean changed = super.setVisible(visible, restart); + final ChildDrawable[] array = mLayerState.mChildren; + + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null) { + dr.setVisible(visible, restart); + } + } + + return changed; + } + + @Override + public void setDither(boolean dither) { + final ChildDrawable[] array = mLayerState.mChildren; + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null) { + dr.setDither(dither); + } + } + } + + @Override + public void setAlpha(int alpha) { + final ChildDrawable[] array = mLayerState.mChildren; + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null) { + dr.setAlpha(alpha); + } + } + } + + @Override + public int getAlpha() { + final Drawable dr = getFirstNonNullDrawable(); + if (dr != null) { + return dr.getAlpha(); + } else { + return super.getAlpha(); + } + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + final ChildDrawable[] array = mLayerState.mChildren; + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null) { + dr.setColorFilter(colorFilter); + } + } + } + + @Override + public void setTintList(ColorStateList tint) { + final ChildDrawable[] array = mLayerState.mChildren; + final int N = mLayerState.N_CHILDREN; + for (int i = 0; i < N; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null) { + dr.setTintList(tint); + } + } + } + + @Override + public void setTintMode(Mode tintMode) { + final ChildDrawable[] array = mLayerState.mChildren; + final int N = mLayerState.N_CHILDREN; + for (int i = 0; i < N; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null) { + dr.setTintMode(tintMode); + } + } + } + + private Drawable getFirstNonNullDrawable() { + final ChildDrawable[] array = mLayerState.mChildren; + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null) { + return dr; + } + } + return null; + } + + public void setOpacity(int opacity) { + mLayerState.mOpacityOverride = opacity; + } + + @Override + public int getOpacity() { + if (mLayerState.mOpacityOverride != PixelFormat.UNKNOWN) { + return mLayerState.mOpacityOverride; + } + return mLayerState.getOpacity(); + } + + @Override + public void setAutoMirrored(boolean mirrored) { + mLayerState.mAutoMirrored = mirrored; + + final ChildDrawable[] array = mLayerState.mChildren; + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null) { + dr.setAutoMirrored(mirrored); + } + } + } + + @Override + public boolean isAutoMirrored() { + return mLayerState.mAutoMirrored; + } + + @Override + public void jumpToCurrentState() { + final ChildDrawable[] array = mLayerState.mChildren; + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null) { + dr.jumpToCurrentState(); + } + } + } + + @Override + public boolean isStateful() { + return mLayerState.isStateful(); + } + + @Override + protected boolean onStateChange(int[] state) { + boolean changed = false; + + final ChildDrawable[] array = mLayerState.mChildren; + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null && dr.isStateful() && dr.setState(state)) { + changed = true; + } + } + + if (changed) { + updateLayerBounds(getBounds()); + } + + return changed; + } + + @Override + protected boolean onLevelChange(int level) { + boolean changed = false; + + final ChildDrawable[] array = mLayerState.mChildren; + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null && dr.setLevel(level)) { + changed = true; + } + } + + if (changed) { + updateLayerBounds(getBounds()); + } + + return changed; + } + + @Override + public int getIntrinsicWidth() { + return (int)(getMaxIntrinsicWidth() * DEFAULT_VIEW_PORT_SCALE); + } + + private int getMaxIntrinsicWidth() { + int width = -1; + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final ChildDrawable r = mLayerState.mChildren[i]; + final int w = r.mDrawable.getIntrinsicWidth(); + if (w > width) { + width = w; + } + } + return width; + } + + @Override + public int getIntrinsicHeight() { + return (int)(getMaxIntrinsicHeight() * DEFAULT_VIEW_PORT_SCALE); + } + + private int getMaxIntrinsicHeight() { + int height = -1; + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final ChildDrawable r = mLayerState.mChildren[i]; + final int h = r.mDrawable.getIntrinsicHeight(); + if (h > height) { + height = h; + } + } + return height; + } + + @Override + public ConstantState getConstantState() { + if (mLayerState.canConstantState()) { + mLayerState.mChangingConfigurations = getChangingConfigurations(); + return mLayerState; + } + return null; + } + + @Override + public Drawable mutate() { + if (!mMutated && super.mutate() == this) { + mLayerState = createConstantState(mLayerState, null); + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final Drawable dr = mLayerState.mChildren[i].mDrawable; + if (dr != null) { + dr.mutate(); + } + } + mMutated = true; + } + return this; + } + + /** + * @hide + */ + public void clearMutated() { + super.clearMutated(); + final ChildDrawable[] array = mLayerState.mChildren; + for (int i = 0; i < mLayerState.N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null) { + dr.clearMutated(); + } + } + mMutated = false; + } + + static class ChildDrawable { + public Drawable mDrawable; + public int[] mThemeAttrs; + public int mDensity = DisplayMetrics.DENSITY_DEFAULT; + + ChildDrawable(int density) { + mDensity = density; + } + + ChildDrawable(@NonNull ChildDrawable orig, @NonNull MaskableIconDrawable owner, + @Nullable Resources res) { + + final Drawable dr = orig.mDrawable; + final Drawable clone; + if (dr != null) { + final ConstantState cs = dr.getConstantState(); + if (cs == null) { + clone = dr; + } else if (res != null) { + clone = cs.newDrawable(res); + } else { + clone = cs.newDrawable(); + } + clone.setCallback(owner); + clone.setBounds(dr.getBounds()); + clone.setLevel(dr.getLevel()); + } else { + clone = null; + } + + mDrawable = clone; + mThemeAttrs = orig.mThemeAttrs; + + mDensity = Drawable.resolveDensity(res, orig.mDensity); + } + + public boolean canApplyTheme() { + return mThemeAttrs != null + || (mDrawable != null && mDrawable.canApplyTheme()); + } + + public final void setDensity(int targetDensity) { + if (mDensity != targetDensity) { + mDensity = targetDensity; + } + } + } + + static class LayerState extends ConstantState { + private int[] mThemeAttrs; + + final static int N_CHILDREN = 2; + ChildDrawable[] mChildren; + + int mDensity; + int mOpacityOverride = PixelFormat.UNKNOWN; + + @Config int mChangingConfigurations; + @Config int mChildrenChangingConfigurations; + + private boolean mCheckedOpacity; + private int mOpacity; + + private boolean mCheckedStateful; + private boolean mIsStateful; + private boolean mAutoMirrored = false; + + LayerState(@Nullable LayerState orig, @NonNull MaskableIconDrawable owner, + @Nullable Resources res) { + mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0); + mChildren = new ChildDrawable[N_CHILDREN]; + if (orig != null) { + final ChildDrawable[] origChildDrawable = orig.mChildren; + + mChangingConfigurations = orig.mChangingConfigurations; + mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; + + for (int i = 0; i < N_CHILDREN; i++) { + final ChildDrawable or = origChildDrawable[i]; + mChildren[i] = new ChildDrawable(or, owner, res); + } + + mCheckedOpacity = orig.mCheckedOpacity; + mOpacity = orig.mOpacity; + mCheckedStateful = orig.mCheckedStateful; + mIsStateful = orig.mIsStateful; + mAutoMirrored = orig.mAutoMirrored; + mThemeAttrs = orig.mThemeAttrs; + mOpacityOverride = orig.mOpacityOverride; + } else { + for (int i = 0; i < N_CHILDREN; i++) { + mChildren[i] = new ChildDrawable(mDensity); + } + } + } + + public final void setDensity(int targetDensity) { + if (mDensity != targetDensity) { + mDensity = targetDensity; + } + } + + @Override + public boolean canApplyTheme() { + if (mThemeAttrs != null || super.canApplyTheme()) { + return true; + } + + final ChildDrawable[] array = mChildren; + for (int i = 0; i < N_CHILDREN; i++) { + final ChildDrawable layer = array[i]; + if (layer.canApplyTheme()) { + return true; + } + } + return false; + } + + @Override + public Drawable newDrawable() { + return new MaskableIconDrawable(this, null); + } + + @Override + public Drawable newDrawable(@Nullable Resources res) { + return new MaskableIconDrawable(this, res); + } + + @Override + public @Config int getChangingConfigurations() { + return mChangingConfigurations + | mChildrenChangingConfigurations; + } + + public final int getOpacity() { + if (mCheckedOpacity) { + return mOpacity; + } + + final ChildDrawable[] array = mChildren; + + // Seek to the first non-null drawable. + int firstIndex = -1; + for (int i = 0; i < N_CHILDREN; i++) { + if (array[i].mDrawable != null) { + firstIndex = i; + break; + } + } + + int op; + if (firstIndex >= 0) { + op = array[firstIndex].mDrawable.getOpacity(); + } else { + op = PixelFormat.TRANSPARENT; + } + + // Merge all remaining non-null drawables. + for (int i = firstIndex + 1; i < N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null) { + op = Drawable.resolveOpacity(op, dr.getOpacity()); + } + } + + mOpacity = op; + mCheckedOpacity = true; + return op; + } + + public final boolean isStateful() { + if (mCheckedStateful) { + return mIsStateful; + } + + final ChildDrawable[] array = mChildren; + boolean isStateful = false; + for (int i = 0; i < N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null && dr.isStateful()) { + isStateful = true; + break; + } + } + + mIsStateful = isStateful; + mCheckedStateful = true; + return isStateful; + } + + public final boolean canConstantState() { + final ChildDrawable[] array = mChildren; + for (int i = 0; i < N_CHILDREN; i++) { + final Drawable dr = array[i].mDrawable; + if (dr != null && dr.getConstantState() == null) { + return false; + } + } + + // Don't cache the result, this method is not called very often. + return true; + } + + public void invalidateCache() { + mCheckedOpacity = false; + mCheckedStateful = false; + } + } +}
\ No newline at end of file diff --git a/graphics/java/android/graphics/fonts/FontRequest.java b/graphics/java/android/graphics/fonts/FontRequest.java new file mode 100644 index 000000000000..e50df6faa38e --- /dev/null +++ b/graphics/java/android/graphics/fonts/FontRequest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.graphics.fonts; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * Information about a font request that may be sent to a Font Provider. + */ +public final class FontRequest implements Parcelable { + private final String mProviderAuthority; + private final String mQuery; + + /** + * @param providerAuthority The authority of the Font Provider to be used for the request. + * @param query The query to be sent over to the provider. Refer to your font provider's + * documentation on the format of this string. + */ + public FontRequest(@NonNull String providerAuthority, @NonNull String query) { + mProviderAuthority = Preconditions.checkNotNull(providerAuthority); + mQuery = Preconditions.checkNotNull(query); + } + + /** + * Returns the selected font provider's authority. This tells the system what font provider + * it should request the font from. + */ + public String getProviderAuthority() { + return mProviderAuthority; + } + + /** + * Returns the query string. Refer to your font provider's documentation on the format of this + * string. + */ + public String getQuery() { + return mQuery; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mProviderAuthority); + dest.writeString(mQuery); + } + + private FontRequest(Parcel in) { + mProviderAuthority = in.readString(); + mQuery = in.readString(); + } + + public static final Parcelable.Creator<FontRequest> CREATOR = + new Parcelable.Creator<FontRequest>() { + @Override + public FontRequest createFromParcel(Parcel in) { + return new FontRequest(in); + } + + @Override + public FontRequest[] newArray(int size) { + return new FontRequest[size]; + } + }; + + @Override + public String toString() { + return "FontRequest {" + + "mProviderAuthority: " + mProviderAuthority + + ", mQuery: " + mQuery + + "}"; + } +} diff --git a/graphics/java/android/graphics/fonts/FontResult.java b/graphics/java/android/graphics/fonts/FontResult.java new file mode 100644 index 000000000000..3ef99fdd62b7 --- /dev/null +++ b/graphics/java/android/graphics/fonts/FontResult.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.graphics.fonts; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Paint; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.io.FileDescriptor; +import java.io.IOException; + +/** + * Results returned from a Font Provider to the system. + * @hide + */ +public final class FontResult implements Parcelable { + private final ParcelFileDescriptor mFileDescriptor; + private final int mTtcIndex; + private final String mFontVariationSettings; + private final int mStyle; + + /** + * Creates a FontResult with all the information needed about a provided font. + * @param fileDescriptor A ParcelFileDescriptor pointing to the font file. This shoult point to + * a real file or shared memory, as the client will mmap the given file + * descriptor. Pipes, sockets and other non-mmap-able file descriptors + * will fail to load in the client application. + * @param ttcIndex If providing a TTC_INDEX file, the index to point to. Otherwise, 0. + * @param fontVariationSettings If providing a variation font, the settings for it. May be null. + * @param style One of {@link android.graphics.Typeface#NORMAL}, + * {@link android.graphics.Typeface#BOLD}, {@link android.graphics.Typeface#ITALIC} + * or {@link android.graphics.Typeface#BOLD_ITALIC} + */ + public FontResult(@NonNull ParcelFileDescriptor fileDescriptor, int ttcIndex, + @Nullable String fontVariationSettings, int style) { + mFileDescriptor = Preconditions.checkNotNull(fileDescriptor); + mTtcIndex = ttcIndex; + mFontVariationSettings = fontVariationSettings; + mStyle = style; + } + + public ParcelFileDescriptor getFileDescriptor() { + return mFileDescriptor; + } + + public int getTtcIndex() { + return mTtcIndex; + } + + public String getFontVariationSettings() { + return mFontVariationSettings; + } + + public int getStyle() { + return mStyle; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mFileDescriptor, flags); + dest.writeInt(mTtcIndex); + dest.writeString(mFontVariationSettings); + dest.writeInt(mStyle); + } + + private FontResult(Parcel in) { + mFileDescriptor = in.readParcelable(null); + mTtcIndex = in.readInt(); + mFontVariationSettings = in.readString(); + mStyle = in.readInt(); + } + + public static final Parcelable.Creator<FontResult> CREATOR = + new Parcelable.Creator<FontResult>() { + @Override + public FontResult createFromParcel(Parcel in) { + return new FontResult(in); + } + + @Override + public FontResult[] newArray(int size) { + return new FontResult[size]; + } + }; +} diff --git a/graphics/java/android/graphics/fonts/FontSpec.aidl b/graphics/java/android/graphics/fonts/FontSpec.aidl new file mode 100644 index 000000000000..dddea2560d3d --- /dev/null +++ b/graphics/java/android/graphics/fonts/FontSpec.aidl @@ -0,0 +1,18 @@ +/* Copyright 2017, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.graphics.fonts; + +parcelable FontSpec;
\ No newline at end of file diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java new file mode 100644 index 000000000000..e36632a591eb --- /dev/null +++ b/keystore/java/android/security/keystore/AttestationUtils.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.content.Context; +import android.os.Build; +import android.os.Process; +import android.security.KeyStore; +import android.security.KeyStoreException; +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterCertificateChain; +import android.security.keymaster.KeymasterDefs; +import android.telephony.TelephonyManager; +import android.util.ArraySet; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.RSAKeyGenParameterSpec; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Utilities for attesting the device's hardware identifiers. + * + * @hide + */ +@SystemApi +@TestApi +public abstract class AttestationUtils { + private static AtomicInteger sSequenceNumber = new AtomicInteger(0); + + private AttestationUtils() { + } + + /** + * Specifies that the device should attest its serial number. For use with + * {@link #attestDeviceIds}. + * + * @see #attestDeviceIds + */ + public static final int ID_TYPE_SERIAL = 1; + + /** + * Specifies that the device should attest its IMEIs. For use with {@link #attestDeviceIds}. + * + * @see #attestDeviceIds + */ + public static final int ID_TYPE_IMEI = 2; + + /** + * Specifies that the device should attest its MEIDs. For use with {@link #attestDeviceIds}. + * + * @see #attestDeviceIds + */ + public static final int ID_TYPE_MEID = 3; + + /** + * Performs attestation of the device's identifiers. This method returns a certificate chain + * whose first element contains the requested device identifiers in an extension. The device's + * brand, device and product are always also included in the attestation. If the device supports + * attestation in secure hardware, the chain will be rooted at a trustworthy CA key. Otherwise, + * the chain will be rooted at an untrusted certificate. See + * <a href="https://developer.android.com/training/articles/security-key-attestation.html"> + * Key Attestation</a> for the format of the certificate extension. + * <p> + * Attestation will only be successful when all of the following are true: + * 1) The device has been set up to support device identifier attestation at the factory. + * 2) The user has not permanently disabled device identifier attestation. + * 3) You have permission to access the device identifiers you are requesting attestation for. + * <p> + * For privacy reasons, you cannot distinguish between (1) and (2). If attestation is + * unsuccessful, the device may not support it in general or the user may have permanently + * disabled it. + * <p> + * The caller must hold {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} + * permission. + * + * @param context the context to use for retrieving device identifiers. + * @param idTypes the types of device identifiers to attest. + * @param attestationChallenge a blob to include in the certificate alongside the device + * identifiers. + * + * @return a certificate chain containing the requested device identifiers in the first element + * + * @exception SecurityException if you are not permitted to obtain an attestation of the + * device's identifiers. + * @exception DeviceIdAttestationException if the attestation operation fails. + */ + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @NonNull public static X509Certificate[] attestDeviceIds(Context context, + @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws + DeviceIdAttestationException { + // Check method arguments, retrieve requested device IDs and prepare attestation arguments. + if (idTypes == null) { + throw new NullPointerException("Missing id types"); + } + if (attestationChallenge == null) { + throw new NullPointerException("Missing attestation challenge"); + } + final KeymasterArguments attestArgs = new KeymasterArguments(); + attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, attestationChallenge); + final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length); + for (int idType : idTypes) { + idTypesSet.add(idType); + } + TelephonyManager telephonyService = null; + if (idTypesSet.contains(ID_TYPE_IMEI) || idTypesSet.contains(ID_TYPE_MEID)) { + telephonyService = (TelephonyManager) context.getSystemService( + Context.TELEPHONY_SERVICE); + if (telephonyService == null) { + throw new DeviceIdAttestationException("Unable to access telephony service"); + } + } + for (final Integer idType : idTypesSet) { + switch (idType) { + case ID_TYPE_SERIAL: + attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL, + Build.getSerial().getBytes(StandardCharsets.UTF_8)); + break; + case ID_TYPE_IMEI: { + final String imei = telephonyService.getImei(0); + if (imei == null) { + throw new DeviceIdAttestationException("Unable to retrieve IMEI"); + } + attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI, + imei.getBytes(StandardCharsets.UTF_8)); + break; + } + case ID_TYPE_MEID: { + final String meid = telephonyService.getDeviceId(); + if (meid == null) { + throw new DeviceIdAttestationException("Unable to retrieve MEID"); + } + attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID, + meid.getBytes(StandardCharsets.UTF_8)); + break; + } + default: + throw new IllegalArgumentException("Unknown device ID type " + idType); + } + } + attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND, + Build.BRAND.getBytes(StandardCharsets.UTF_8)); + attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE, + Build.DEVICE.getBytes(StandardCharsets.UTF_8)); + attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT, + Build.PRODUCT.getBytes(StandardCharsets.UTF_8)); + + final KeyStore keyStore = KeyStore.getInstance(); + final String keyAlias = "android_internal_device_id_attestation-" + + Process.myPid() + "-" + sSequenceNumber.incrementAndGet(); + // Clear any leftover temporary key. + if (!keyStore.delete(keyAlias)) { + throw new DeviceIdAttestationException("Unable to remove temporary key"); + } + try { + // Generate a temporary key. + final KeymasterArguments generateArgs = new KeymasterArguments(); + generateArgs.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_VERIFY); + generateArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA); + generateArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE); + generateArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE); + generateArgs.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); + generateArgs.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048); + generateArgs.addUnsignedLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, + RSAKeyGenParameterSpec.F4); + int errorCode = keyStore.generateKey(keyAlias, generateArgs, null, 0, + new KeyCharacteristics()); + if (errorCode != KeyStore.NO_ERROR) { + throw new DeviceIdAttestationException("Unable to create temporary key", + KeyStore.getKeyStoreException(errorCode)); + } + + // Perform attestation. + final KeymasterCertificateChain outChain = new KeymasterCertificateChain(); + errorCode = keyStore.attestKey(keyAlias, attestArgs, outChain); + if (errorCode != KeyStore.NO_ERROR) { + throw new DeviceIdAttestationException("Unable to perform attestation", + KeyStore.getKeyStoreException(errorCode)); + } + + // Extract certificate chain. + final Collection<byte[]> rawChain = outChain.getCertificates(); + if (rawChain.size() < 2) { + throw new DeviceIdAttestationException("Attestation certificate chain contained " + + rawChain.size() + " entries. At least two are required."); + } + final ByteArrayOutputStream concatenatedRawChain = new ByteArrayOutputStream(); + try { + for (final byte[] cert : rawChain) { + concatenatedRawChain.write(cert); + } + return CertificateFactory.getInstance("X.509").generateCertificates( + new ByteArrayInputStream(concatenatedRawChain.toByteArray())) + .toArray(new X509Certificate[0]); + } catch (Exception e) { + throw new DeviceIdAttestationException("Unable to construct certificate chain", e); + } + } finally { + // Remove temporary key. + keyStore.delete(keyAlias); + } + } +} diff --git a/keystore/java/android/security/keystore/DeviceIdAttestationException.java b/keystore/java/android/security/keystore/DeviceIdAttestationException.java new file mode 100644 index 000000000000..e18d193b043b --- /dev/null +++ b/keystore/java/android/security/keystore/DeviceIdAttestationException.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +/** + * Thrown when {@link AttestationUtils} is unable to attest the given device ids. + * + * @hide + */ +public class DeviceIdAttestationException extends Exception { + /** + * Constructs a new {@code DeviceIdAttestationException} with the current stack trace and the + * specified detail message. + * + * @param detailMessage the detail message for this exception. + */ + public DeviceIdAttestationException(String detailMessage) { + super(detailMessage); + } + + /** + * Constructs a new {@code DeviceIdAttestationException} with the current stack trace, the + * specified detail message and the specified cause. + * + * @param message the detail message for this exception. + * @param cause the cause of this exception, may be {@code null}. + */ + public DeviceIdAttestationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index e0689006d5dd..acacd7654cf1 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -288,22 +288,34 @@ bool AssetManager::createIdmap(const char* targetApkPath, const char* overlayApk { AutoMutex _l(mLock); const String8 paths[2] = { String8(targetApkPath), String8(overlayApkPath) }; - ResTable tables[2]; - - for (int i = 0; i < 2; ++i) { - asset_path ap; - ap.type = kFileTypeRegular; - ap.path = paths[i]; - Asset* ass = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap); - if (ass == NULL) { - ALOGW("failed to find resources.arsc in %s\n", ap.path.string()); - return false; + Asset* assets[2] = {NULL, NULL}; + bool ret = false; + { + ResTable tables[2]; + + for (int i = 0; i < 2; ++i) { + asset_path ap; + ap.type = kFileTypeRegular; + ap.path = paths[i]; + assets[i] = openNonAssetInPathLocked("resources.arsc", + Asset::ACCESS_BUFFER, ap); + if (assets[i] == NULL) { + ALOGW("failed to find resources.arsc in %s\n", ap.path.string()); + goto exit; + } + if (tables[i].add(assets[i]) != NO_ERROR) { + ALOGW("failed to add %s to resource table", paths[i].string()); + goto exit; + } } - tables[i].add(ass); + ret = tables[0].createIdmap(tables[1], targetCrc, overlayCrc, + targetApkPath, overlayApkPath, (void**)outData, outSize) == NO_ERROR; } - return tables[0].createIdmap(tables[1], targetCrc, overlayCrc, - targetApkPath, overlayApkPath, (void**)outData, outSize) == NO_ERROR; +exit: + delete assets[0]; + delete assets[1]; + return ret; } bool AssetManager::addDefaultAssets() diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index a4bcc624ef31..763a178ed43b 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -1907,7 +1907,7 @@ int ResTable_config::compare(const ResTable_config& o) const { if (diff != 0) return diff; diff = (int32_t)(screenLayout2 - o.screenLayout2); if (diff != 0) return diff; - diff = (int32_t)(colorimetry - o.colorimetry); + diff = (int32_t)(colorMode - o.colorMode); if (diff != 0) return diff; diff = (int32_t)(uiMode - o.uiMode); if (diff != 0) return diff; @@ -1969,8 +1969,8 @@ int ResTable_config::compareLogical(const ResTable_config& o) const { if (screenLayout2 != o.screenLayout2) { return screenLayout2 < o.screenLayout2 ? -1 : 1; } - if (colorimetry != o.colorimetry) { - return colorimetry < o.colorimetry ? -1 : 1; + if (colorMode != o.colorMode) { + return colorMode < o.colorMode ? -1 : 1; } if (uiMode != o.uiMode) { return uiMode < o.uiMode ? -1 : 1; @@ -1997,8 +1997,8 @@ int ResTable_config::diff(const ResTable_config& o) const { if ((screenLayout & MASK_LAYOUTDIR) != (o.screenLayout & MASK_LAYOUTDIR)) diffs |= CONFIG_LAYOUTDIR; if ((screenLayout & ~MASK_LAYOUTDIR) != (o.screenLayout & ~MASK_LAYOUTDIR)) diffs |= CONFIG_SCREEN_LAYOUT; if ((screenLayout2 & MASK_SCREENROUND) != (o.screenLayout2 & MASK_SCREENROUND)) diffs |= CONFIG_SCREEN_ROUND; - if ((colorimetry & MASK_WIDE_COLOR_GAMUT) != (o.colorimetry & MASK_WIDE_COLOR_GAMUT)) diffs |= CONFIG_COLORIMETRY; - if ((colorimetry & MASK_HDR) != (o.colorimetry & MASK_HDR)) diffs |= CONFIG_COLORIMETRY; + if ((colorMode & MASK_WIDE_COLOR_GAMUT) != (o.colorMode & MASK_WIDE_COLOR_GAMUT)) diffs |= CONFIG_COLOR_MODE; + if ((colorMode & MASK_HDR) != (o.colorMode & MASK_HDR)) diffs |= CONFIG_COLOR_MODE; if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE; if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE; if (screenSizeDp != o.screenSizeDp) diffs |= CONFIG_SCREEN_SIZE; @@ -2110,14 +2110,14 @@ bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const { } } - if (colorimetry || o.colorimetry) { - if (((colorimetry^o.colorimetry) & MASK_HDR) != 0) { - if (!(colorimetry & MASK_HDR)) return false; - if (!(o.colorimetry & MASK_HDR)) return true; + if (colorMode || o.colorMode) { + if (((colorMode^o.colorMode) & MASK_HDR) != 0) { + if (!(colorMode & MASK_HDR)) return false; + if (!(o.colorMode & MASK_HDR)) return true; } - if (((colorimetry^o.colorimetry) & MASK_WIDE_COLOR_GAMUT) != 0) { - if (!(colorimetry & MASK_WIDE_COLOR_GAMUT)) return false; - if (!(o.colorimetry & MASK_WIDE_COLOR_GAMUT)) return true; + if (((colorMode^o.colorMode) & MASK_WIDE_COLOR_GAMUT) != 0) { + if (!(colorMode & MASK_WIDE_COLOR_GAMUT)) return false; + if (!(o.colorMode & MASK_WIDE_COLOR_GAMUT)) return true; } } @@ -2408,14 +2408,14 @@ bool ResTable_config::isBetterThan(const ResTable_config& o, } } - if (colorimetry || o.colorimetry) { - if (((colorimetry^o.colorimetry) & MASK_WIDE_COLOR_GAMUT) != 0 && - (requested->colorimetry & MASK_WIDE_COLOR_GAMUT)) { - return colorimetry & MASK_WIDE_COLOR_GAMUT; + if (colorMode || o.colorMode) { + if (((colorMode^o.colorMode) & MASK_WIDE_COLOR_GAMUT) != 0 && + (requested->colorMode & MASK_WIDE_COLOR_GAMUT)) { + return colorMode & MASK_WIDE_COLOR_GAMUT; } - if (((colorimetry^o.colorimetry) & MASK_HDR) != 0 && - (requested->colorimetry & MASK_HDR)) { - return colorimetry & MASK_HDR; + if (((colorMode^o.colorMode) & MASK_HDR) != 0 && + (requested->colorMode & MASK_HDR)) { + return colorMode & MASK_HDR; } } @@ -2669,14 +2669,14 @@ bool ResTable_config::match(const ResTable_config& settings) const { return false; } - const int hdr = colorimetry & MASK_HDR; - const int setHdr = settings.colorimetry & MASK_HDR; + const int hdr = colorMode & MASK_HDR; + const int setHdr = settings.colorMode & MASK_HDR; if (hdr != 0 && hdr != setHdr) { return false; } - const int wideColorGamut = colorimetry & MASK_WIDE_COLOR_GAMUT; - const int setWideColorGamut = settings.colorimetry & MASK_WIDE_COLOR_GAMUT; + const int wideColorGamut = colorMode & MASK_WIDE_COLOR_GAMUT; + const int setWideColorGamut = settings.colorMode & MASK_WIDE_COLOR_GAMUT; if (wideColorGamut != 0 && wideColorGamut != setWideColorGamut) { return false; } @@ -3000,9 +3000,9 @@ String8 ResTable_config::toString() const { break; } } - if ((colorimetry&MASK_HDR) != 0) { + if ((colorMode&MASK_HDR) != 0) { if (res.size() > 0) res.append("-"); - switch (colorimetry&MASK_HDR) { + switch (colorMode&MASK_HDR) { case ResTable_config::HDR_NO: res.append("lowdr"); break; @@ -3010,13 +3010,13 @@ String8 ResTable_config::toString() const { res.append("highdr"); break; default: - res.appendFormat("hdr=%d", dtohs(colorimetry&MASK_HDR)); + res.appendFormat("hdr=%d", dtohs(colorMode&MASK_HDR)); break; } } - if ((colorimetry&MASK_WIDE_COLOR_GAMUT) != 0) { + if ((colorMode&MASK_WIDE_COLOR_GAMUT) != 0) { if (res.size() > 0) res.append("-"); - switch (colorimetry&MASK_WIDE_COLOR_GAMUT) { + switch (colorMode&MASK_WIDE_COLOR_GAMUT) { case ResTable_config::WIDE_COLOR_GAMUT_NO: res.append("nowidecg"); break; @@ -3024,7 +3024,7 @@ String8 ResTable_config::toString() const { res.append("widecg"); break; default: - res.appendFormat("wideColorGamut=%d", dtohs(colorimetry&MASK_WIDE_COLOR_GAMUT)); + res.appendFormat("wideColorGamut=%d", dtohs(colorMode&MASK_WIDE_COLOR_GAMUT)); break; } } diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 1e4aee9d18e7..86ab123ff064 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1147,25 +1147,25 @@ struct ResTable_config }; enum { - // colorimetry bits for wide-color gamut/narrow-color gamut. + // colorMode bits for wide-color gamut/narrow-color gamut. MASK_WIDE_COLOR_GAMUT = 0x03, WIDE_COLOR_GAMUT_ANY = ACONFIGURATION_WIDE_COLOR_GAMUT_ANY, WIDE_COLOR_GAMUT_NO = ACONFIGURATION_WIDE_COLOR_GAMUT_NO, WIDE_COLOR_GAMUT_YES = ACONFIGURATION_WIDE_COLOR_GAMUT_YES, - // colorimetry bits for HDR/LDR. + // colorMode bits for HDR/LDR. MASK_HDR = 0x0c, - SHIFT_COLORIMETRY_HDR = 2, - HDR_ANY = ACONFIGURATION_HDR_ANY << SHIFT_COLORIMETRY_HDR, - HDR_NO = ACONFIGURATION_HDR_NO << SHIFT_COLORIMETRY_HDR, - HDR_YES = ACONFIGURATION_HDR_YES << SHIFT_COLORIMETRY_HDR, + SHIFT_COLOR_MODE_HDR = 2, + HDR_ANY = ACONFIGURATION_HDR_ANY << SHIFT_COLOR_MODE_HDR, + HDR_NO = ACONFIGURATION_HDR_NO << SHIFT_COLOR_MODE_HDR, + HDR_YES = ACONFIGURATION_HDR_YES << SHIFT_COLOR_MODE_HDR, }; // An extension of screenConfig. union { struct { uint8_t screenLayout2; // Contains round/notround qualifier. - uint8_t colorimetry; // Wide-gamut, HDR, etc. + uint8_t colorMode; // Wide-gamut, HDR, etc. uint16_t screenConfigPad2; // Reserved padding. }; uint32_t screenConfig2; @@ -1208,7 +1208,7 @@ struct ResTable_config CONFIG_UI_MODE = ACONFIGURATION_UI_MODE, CONFIG_LAYOUTDIR = ACONFIGURATION_LAYOUTDIR, CONFIG_SCREEN_ROUND = ACONFIGURATION_SCREEN_ROUND, - CONFIG_COLORIMETRY = ACONFIGURATION_COLORIMETRY, + CONFIG_COLOR_MODE = ACONFIGURATION_COLOR_MODE, }; // Compare two configuration, returning CONFIG_* flags set for each value diff --git a/libs/androidfw/tests/Config_test.cpp b/libs/androidfw/tests/Config_test.cpp index 3e5aca7ab655..b54915f03c29 100644 --- a/libs/androidfw/tests/Config_test.cpp +++ b/libs/androidfw/tests/Config_test.cpp @@ -187,9 +187,9 @@ TEST(ConfigTest, ScreenIsWideGamut) { memset(&defaultConfig, 0, sizeof(defaultConfig)); ResTable_config wideGamutConfig = defaultConfig; - wideGamutConfig.colorimetry = ResTable_config::WIDE_COLOR_GAMUT_YES; + wideGamutConfig.colorMode = ResTable_config::WIDE_COLOR_GAMUT_YES; - EXPECT_EQ(defaultConfig.diff(wideGamutConfig), ResTable_config::CONFIG_COLORIMETRY); + EXPECT_EQ(defaultConfig.diff(wideGamutConfig), ResTable_config::CONFIG_COLOR_MODE); } TEST(ConfigTest, ScreenIsHdr) { @@ -197,9 +197,9 @@ TEST(ConfigTest, ScreenIsHdr) { memset(&defaultConfig, 0, sizeof(defaultConfig)); ResTable_config hdrConfig = defaultConfig; - hdrConfig.colorimetry = ResTable_config::HDR_YES; + hdrConfig.colorMode = ResTable_config::HDR_YES; - EXPECT_EQ(defaultConfig.diff(hdrConfig), ResTable_config::CONFIG_COLORIMETRY); + EXPECT_EQ(defaultConfig.diff(hdrConfig), ResTable_config::CONFIG_COLOR_MODE); } } // namespace android. diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp index ca43156e88a1..9041b44db849 100644 --- a/libs/hwui/hwui/Typeface.cpp +++ b/libs/hwui/hwui/Typeface.cpp @@ -130,9 +130,9 @@ void Typeface::setRobotoTypefaceForTest() { sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(fontData.release()); LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont); - minikin::FontFamily* family = new minikin::FontFamily(); minikin::MinikinFont* font = new MinikinFontSkia(std::move(typeface), data, st.st_size, 0); - family->addFont(font); + minikin::FontFamily* family = new minikin::FontFamily( + std::vector<minikin::Font>({ minikin::Font(font, minikin::FontStyle()) })); font->Unref(); std::vector<minikin::FontFamily*> typefaces = { family }; diff --git a/libs/hwui/tests/common/LeakChecker.cpp b/libs/hwui/tests/common/LeakChecker.cpp index 8a0b64b2f1cb..d935382cc9a4 100644 --- a/libs/hwui/tests/common/LeakChecker.cpp +++ b/libs/hwui/tests/common/LeakChecker.cpp @@ -67,12 +67,6 @@ static void logUnreachable(initializer_list<UnreachableMemoryInfo> infolist) { } void LeakChecker::checkForLeaks() { - // TODO: Re-enable, disabled to workaround b/34586922 - if ((true)) { - cout << "checkForLeaks disabled, see b/34586922" << endl; - return; - } - // TODO: Until we can shutdown the RT thread we need to do this in // two passes as GetUnreachableMemory has limited insight into // thread-local caches so some leaks will not be properly tagged as leaks diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 0b8c2a98fab5..5bb0b6db06b5 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -170,9 +170,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderOverdraw) { ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE); // 1 Overdraw, should be blue blended onto white. - renderNodes.push_back(whiteNode); //this is the "content" node - renderNodes.push_back(whiteNode); //the "content" node above does not cause an overdraw, because - //it clips the first "background" node + renderNodes.push_back(whiteNode); pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffd0d0ff); diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 7c603853a3ba..bcae71c6dd4f 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -730,61 +730,6 @@ public class AudioManager { } /** - * @hide - */ - public void handleKeyDown(KeyEvent event, int stream) { - int keyCode = event.getKeyCode(); - switch (keyCode) { - case KeyEvent.KEYCODE_VOLUME_UP: - case KeyEvent.KEYCODE_VOLUME_DOWN: - /* - * Adjust the volume in on key down since it is more - * responsive to the user. - */ - adjustSuggestedStreamVolume( - keyCode == KeyEvent.KEYCODE_VOLUME_UP - ? ADJUST_RAISE - : ADJUST_LOWER, - stream, - FLAG_SHOW_UI | FLAG_VIBRATE); - break; - case KeyEvent.KEYCODE_VOLUME_MUTE: - if (event.getRepeatCount() == 0) { - MediaSessionLegacyHelper.getHelper(getContext()) - .sendVolumeKeyEvent(event, false); - } - break; - } - } - - /** - * @hide - */ - public void handleKeyUp(KeyEvent event, int stream) { - int keyCode = event.getKeyCode(); - switch (keyCode) { - case KeyEvent.KEYCODE_VOLUME_UP: - case KeyEvent.KEYCODE_VOLUME_DOWN: - /* - * Play a sound. This is done on key up since we don't want the - * sound to play when a user holds down volume down to mute. - */ - if (mUseVolumeKeySounds) { - adjustSuggestedStreamVolume( - ADJUST_SAME, - stream, - FLAG_PLAY_SOUND); - } - mVolumeKeyUpTime = SystemClock.uptimeMillis(); - break; - case KeyEvent.KEYCODE_VOLUME_MUTE: - MediaSessionLegacyHelper.getHelper(getContext()) - .sendVolumeKeyEvent(event, false); - break; - } - } - - /** * Indicates if the device implements a fixed volume policy. * <p>Some devices may not have volume control and may operate at a fixed volume, * and may not enable muting or changing the volume of audio streams. diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 432e77ccd720..a76a328e2274 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -192,5 +192,7 @@ interface IAudioService { oneway void releasePlayer(in int piid); + void disableRingtoneSync(); + // WARNING: read warning at top of file, it is recommended to add new methods at the end } diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 7614999074c6..8a1027b83033 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -33,8 +33,11 @@ import android.database.Cursor; import android.media.MediaScannerConnection.MediaScannerConnectionClient; import android.net.Uri; import android.os.Environment; +import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.provider.MediaStore; @@ -850,6 +853,18 @@ public class RingtoneManager { public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) { final ContentResolver resolver = context.getContentResolver(); + if (Settings.Secure.getString(resolver, Settings.Secure.SYNC_PARENT_SOUNDS).equals("1")) { + // Sync is enabled, so we need to disable it + IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); + IAudioService audioService = IAudioService.Stub.asInterface(b); + try { + audioService.disableRingtoneSync(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to disable ringtone sync."); + return; + } + } + String setting = getSettingForType(type); if (setting == null) return; if(!isInternalRingtoneUri(ringtoneUri)) { diff --git a/media/java/android/media/session/IOnMediaKeyListener.aidl b/media/java/android/media/session/IOnMediaKeyListener.aidl new file mode 100644 index 000000000000..7752357a7d97 --- /dev/null +++ b/media/java/android/media/session/IOnMediaKeyListener.aidl @@ -0,0 +1,28 @@ +/* Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.session; + +import android.os.ResultReceiver; +import android.view.KeyEvent; + +/** + * Listener to handle media key. + * @hide + */ +interface IOnMediaKeyListener { + void onMediaKey(in KeyEvent event, in ResultReceiver result); +} + diff --git a/media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl b/media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl new file mode 100644 index 000000000000..07b83471d166 --- /dev/null +++ b/media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl @@ -0,0 +1,27 @@ +/* Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.session; + +import android.view.KeyEvent; + +/** + * Listener to handle volume key long-press. + * @hide + */ +oneway interface IOnVolumeKeyLongPressListener { + void onVolumeKeyLongPress(in KeyEvent event); +} + diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index bb59e5b4be35..8a987733ee64 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -18,6 +18,8 @@ package android.media.session; import android.content.ComponentName; import android.media.IRemoteVolumeController; import android.media.session.IActiveSessionsListener; +import android.media.session.IOnVolumeKeyLongPressListener; +import android.media.session.IOnMediaKeyListener; import android.media.session.ISession; import android.media.session.ISessionCallback; import android.os.Bundle; @@ -31,6 +33,7 @@ interface ISessionManager { ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId); List<IBinder> getSessions(in ComponentName compName, int userId); void dispatchMediaKeyEvent(in KeyEvent keyEvent, boolean needWakeLock); + void dispatchVolumeKeyEvent(in KeyEvent keyEvent, int stream, boolean musicOnly); void dispatchAdjustVolume(int suggestedStream, int delta, int flags); void addSessionsListener(in IActiveSessionsListener listener, in ComponentName compName, int userId); @@ -41,4 +44,8 @@ interface ISessionManager { // For PhoneWindowManager to precheck media keys boolean isGlobalPriorityActive(); -}
\ No newline at end of file + + void setOnVolumeKeyLongPressListener(in IOnVolumeKeyLongPressListener listener); + void setOnMediaKeyListener(in IOnMediaKeyListener listener); +} + diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java index 95cb8aee6ac5..7c3af31a04d7 100644 --- a/media/java/android/media/session/MediaSessionLegacyHelper.java +++ b/media/java/android/media/session/MediaSessionLegacyHelper.java @@ -176,54 +176,12 @@ public class MediaSessionLegacyHelper { } } - public void sendVolumeKeyEvent(KeyEvent keyEvent, boolean musicOnly) { + public void sendVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) { if (keyEvent == null) { Log.w(TAG, "Tried to send a null key event. Ignoring."); return; } - boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN; - boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP; - int direction = 0; - boolean isMute = false; - switch (keyEvent.getKeyCode()) { - case KeyEvent.KEYCODE_VOLUME_UP: - direction = AudioManager.ADJUST_RAISE; - break; - case KeyEvent.KEYCODE_VOLUME_DOWN: - direction = AudioManager.ADJUST_LOWER; - break; - case KeyEvent.KEYCODE_VOLUME_MUTE: - isMute = true; - break; - } - if (down || up) { - int flags = AudioManager.FLAG_FROM_KEY; - if (musicOnly) { - // This flag is used when the screen is off to only affect - // active media - flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY; - } else { - // These flags are consistent with the home screen - if (up) { - flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE; - } else { - flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE; - } - } - if (direction != 0) { - // If this is action up we want to send a beep for non-music events - if (up) { - direction = 0; - } - mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE, - direction, flags); - } else if (isMute) { - if (down && keyEvent.getRepeatCount() == 0) { - mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE, - AudioManager.ADJUST_TOGGLE_MUTE, flags); - } - } - } + mSessionManager.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly); } public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) { diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 2364a13e6385..80d2a0c2149e 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -18,6 +18,7 @@ package android.media.session; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; import android.media.AudioManager; @@ -26,6 +27,7 @@ import android.media.session.ISessionManager; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.UserHandle; import android.service.notification.NotificationListenerService; @@ -50,6 +52,18 @@ import java.util.List; public final class MediaSessionManager { private static final String TAG = "SessionManager"; + /** + * Used by IOnMediaKeyListener to indicate that the media key event isn't handled. + * @hide + */ + public static final int RESULT_MEDIA_KEY_NOT_HANDLED = 0; + + /** + * Used by IOnMediaKeyListener to indicate that the media key event is handled. + * @hide + */ + public static final int RESULT_MEDIA_KEY_HANDLED = 1; + private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners = new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>(); private final Object mLock = new Object(); @@ -57,6 +71,9 @@ public final class MediaSessionManager { private Context mContext; + private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener; + private OnMediaKeyListenerImpl mOnMediaKeyListener; + /** * @hide */ @@ -278,6 +295,21 @@ public final class MediaSessionManager { } /** + * Send a volume key event. The receiver will be selected automatically. + * + * @param keyEvent The volume KeyEvent to send. + * @param needWakeLock True if a wake lock should be held while sending the key. + * @hide + */ + public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int stream, boolean musicOnly) { + try { + mService.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly); + } catch (RemoteException e) { + Log.e(TAG, "Failed to send volume key event.", e); + } + } + + /** * Dispatch an adjust volume request to the system. It will be sent to the * most relevant audio stream or media session. The direction must be one of * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE}, @@ -313,6 +345,72 @@ public final class MediaSessionManager { } /** + * Set the volume key long-press listener. While the listener is set, the listener + * gets the volume key long-presses instead of changing volume. + * + * <p>System can only have a single volume key long-press listener. + * + * @param listener The volume key long-press listener. {@code null} to reset. + * @param handler The handler on which the listener should be invoked, or {@code null} + * if the listener should be invoked on the calling thread's looper. + * @hide + */ + @SystemApi + public void setOnVolumeKeyLongPressListener( + OnVolumeKeyLongPressListener listener, @Nullable Handler handler) { + synchronized (mLock) { + try { + if (listener == null) { + mOnVolumeKeyLongPressListener = null; + mService.setOnVolumeKeyLongPressListener(null); + } else { + if (handler == null) { + handler = new Handler(); + } + mOnVolumeKeyLongPressListener = + new OnVolumeKeyLongPressListenerImpl(listener, handler); + mService.setOnVolumeKeyLongPressListener(mOnVolumeKeyLongPressListener); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to set volume key long press listener", e); + } + } + } + + /** + * Set the media key listener. While the listener is set, the listener + * gets the media key before any other media sessions but after the global priority session. + * If the listener handles the key (i.e. returns {@code true}), + * other sessions will not get the event. + * + * <p>System can only have a single media key listener. + * + * @param listener The media key listener. {@code null} to reset. + * @param handler The handler on which the listener should be invoked, or {@code null} + * if the listener should be invoked on the calling thread's looper. + * @hide + */ + @SystemApi + public void setOnMediaKeyListener(OnMediaKeyListener listener, @Nullable Handler handler) { + synchronized (mLock) { + try { + if (listener == null) { + mOnMediaKeyListener = null; + mService.setOnMediaKeyListener(null); + } else { + if (handler == null) { + handler = new Handler(); + } + mOnMediaKeyListener = new OnMediaKeyListenerImpl(listener, handler); + mService.setOnMediaKeyListener(mOnMediaKeyListener); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to set media key listener", e); + } + } + } + + /** * Listens for changes to the list of active sessions. This can be added * using {@link #addOnActiveSessionsChangedListener}. */ @@ -320,6 +418,33 @@ public final class MediaSessionManager { public void onActiveSessionsChanged(@Nullable List<MediaController> controllers); } + /** + * Listens the volume key long-presses. + * @hide + */ + @SystemApi + public interface OnVolumeKeyLongPressListener { + /** + * Called when the volume key is long-pressed. + * <p>This will be called for both down and up events. + */ + void onVolumeKeyLongPress(KeyEvent event); + } + + /** + * Listens the media key. + * @hide + */ + @SystemApi + public interface OnMediaKeyListener { + /** + * Called when the media key is pressed. + * <p>If it takes more than 1s to return, the key event will be sent to + * other media sessions. + */ + boolean onMediaKey(KeyEvent event); + } + private static final class SessionsChangedWrapper { private Context mContext; private OnActiveSessionsChangedListener mListener; @@ -360,4 +485,60 @@ public final class MediaSessionManager { mHandler = null; } } + + private static final class OnVolumeKeyLongPressListenerImpl + extends IOnVolumeKeyLongPressListener.Stub { + private OnVolumeKeyLongPressListener mListener; + private Handler mHandler; + + public OnVolumeKeyLongPressListenerImpl( + OnVolumeKeyLongPressListener listener, Handler handler) { + mListener = listener; + mHandler = handler; + } + + @Override + public void onVolumeKeyLongPress(KeyEvent event) { + if (mListener == null || mHandler == null) { + Log.w(TAG, "Failed to call volume key long-press listener." + + " Either mListener or mHandler is null"); + return; + } + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onVolumeKeyLongPress(event); + } + }); + } + } + + private static final class OnMediaKeyListenerImpl extends IOnMediaKeyListener.Stub { + private OnMediaKeyListener mListener; + private Handler mHandler; + + public OnMediaKeyListenerImpl(OnMediaKeyListener listener, Handler handler) { + mListener = listener; + mHandler = handler; + } + + @Override + public void onMediaKey(KeyEvent event, ResultReceiver result) { + if (mListener == null || mHandler == null) { + Log.w(TAG, "Failed to call media key listener." + + " Either mListener or mHandler is null"); + return; + } + mHandler.post(new Runnable() { + @Override + public void run() { + boolean handled = mListener.onMediaKey(event); + Log.d(TAG, "The media key listener is returned " + handled); + result.send( + handled ? RESULT_MEDIA_KEY_HANDLED : RESULT_MEDIA_KEY_NOT_HANDLED, + null); + } + }); + } + } } diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java index 20706fd19aac..3ee80af3de9f 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -543,6 +543,17 @@ public final class TvContract { */ public static final String TYPE_S_DMB = "TYPE_S_DMB"; + /** + * The channel type for preview videos. + * + * <P>Unlike other broadcast TV channel types, the programs in the preview channel usually + * are promotional videos. The UI may treat the preview channels differently from the other + * broadcast channels. + * + * @see #COLUMN_TYPE + */ + public static final String TYPE_PREVIEW = "TYPE_PREVIEW"; + /** A generic service type. */ public static final String SERVICE_TYPE_OTHER = "SERVICE_TYPE_OTHER"; @@ -1001,6 +1012,20 @@ public final class TvContract { */ public static final String COLUMN_VERSION_NUMBER = "version_number"; + /** + * The flag indicating whether this TV channel is transient or not. + * + * <p>A value of 1 indicates that the channel will be automatically removed by the system on + * reboot, and a value of 0 indicates that the channel is persistent across reboot. If not + * specified, this value is set to 0 (not transient) by default. + * + * <p>Type: INTEGER (boolean) + * @see Programs#COLUMN_TRANSIENT + * @hide + */ + @SystemApi + public static final String COLUMN_TRANSIENT = "transient"; + private Channels() {} /** @@ -1165,6 +1190,8 @@ public final class TvContract { * previous program in the same channel. In practice, start time will usually be the end * time of the previous program. * + * <p>Can be empty if this program belongs to a {@link Channels#TYPE_PREVIEW} channel. + * * <p>Type: INTEGER (long) */ public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis"; @@ -1176,6 +1203,8 @@ public final class TvContract { * next program in the same channel. In practice, end time will usually be the start time of * the next program. * + * <p>Can be empty if this program belongs to a {@link Channels#TYPE_PREVIEW} channel. + * * <p>Type: INTEGER (long) */ public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis"; @@ -1410,6 +1439,102 @@ public final class TvContract { */ public static final String COLUMN_VERSION_NUMBER = "version_number"; + /** + * The internal ID used by individual TV input services. + * + * <p>This is internal to the provider that inserted it, and should not be decoded by other + * apps. + * + * <p>Can be empty. + * + * <p>Type: TEXT + */ + public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id"; + + /** + * The URI for the preview video. + * + * <p>This is only relevant to {@link Channels#TYPE_PREVIEW}. The data in the column must be + * a URL, or a URI in one of the following formats: + * + * <ul> + * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li> + * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE}) + * </li> + * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li> + * </ul> + * + * <p>Can be empty. + * + * <p>Type: TEXT + */ + public static final String COLUMN_PREVIEW_VIDEO_URI = "preview_video_uri"; + + /** + * The last playback position (in milliseconds) of the preview video. + * + * <p>This is only relevant to {@link Channels#TYPE_PREVIEW}. + * + * <p>Can be empty. + * + * <p>Type: INTEGER + */ + public static final String COLUMN_PREVIEW_LAST_PLAYBACK_POSITION = + "preview_last_playback_position"; + + /** + * The duration (in milliseconds) of the preview video. + * + * <p>This is only relevant to {@link Channels#TYPE_PREVIEW}. + * + * <p>Can be empty. + * + * <p>Type: INTEGER + */ + public static final String COLUMN_PREVIEW_DURATION = "preview_duration"; + + /** + * The intent URI which is launched when the preview video is selected. + * + * <p>The URI is created using {@link Intent#toUri} with {@link Intent#URI_INTENT_SCHEME} + * and converted back to the original intent with {@link Intent#parseUri}. The intent is + * launched when the user selects the preview video item. + * + * <p>Can be empty. + * + * <p>Type: TEXT + */ + public static final String COLUMN_PREVIEW_INTENT_URI = + "preview_intent_uri"; + + /** + * The weight of the preview program within the channel. + * + * <p>The UI may choose to show this item in a different position in the channel row. + * A larger weight value means the program is more important than other programs having + * smaller weight values. The value is relevant for the preview programs in the same + * channel. This is only relevant to {@link Channels#TYPE_PREVIEW}. + * + * <p>Can be empty. + * + * <p>Type: INTEGER + */ + public static final String COLUMN_PREVIEW_WEIGHT = "preview_weight"; + + /** + * The flag indicating whether this program is transient or not. + * + * <p>A value of 1 indicates that the channel will be automatically removed by the system on + * reboot, and a value of 0 indicates that the channel is persistent across reboot. If not + * specified, this value is set to 0 (not transient) by default. + * + * <p>Type: INTEGER (boolean) + * @see Channels#COLUMN_TRANSIENT + * @hide + */ + @SystemApi + public static final String COLUMN_TRANSIENT = "transient"; + private Programs() {} /** Canonical genres for TV programs. */ diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index dfddaa57109d..1eae8db60833 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -317,6 +317,13 @@ public final class TvInputManager { */ public static final String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS"; + /** + * Activity action to display the recording schedules. When invoked, the system will display an + * appropriate UI to browse the schedules. + */ + public static final String ACTION_VIEW_RECORDING_SCHEDULES = + "android.media.tv.action.VIEW_RECORDING_SCHEDULES"; + private final ITvInputManager mService; private final Object mLock = new Object(); diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml index 28d9e5c94510..e2080b08c4df 100644 --- a/packages/CarrierDefaultApp/AndroidManifest.xml +++ b/packages/CarrierDefaultApp/AndroidManifest.xml @@ -37,5 +37,7 @@ <activity android:name="com.android.carrierdefaultapp.CaptivePortalLaunchActivity" android:theme="@android:style/Theme.Translucent.NoTitleBar" android:excludeFromRecents="true"/> + <service android:name="com.android.carrierdefaultapp.ProvisionObserver" + android:permission="android.permission.BIND_JOB_SERVICE"/> </application> </manifest> diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java index bc0fa026fd77..3fd89d97617e 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java @@ -28,6 +28,10 @@ public class CarrierDefaultBroadcastReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "onReceive intent: " + intent.getAction()); + if (ProvisionObserver.isDeferredForProvision(context, intent)) { + Log.d(TAG, "skip carrier actions during provisioning"); + return; + } List<Integer> actionList = CustomConfigLoader.loadCarrierActionList(context, intent); for (int actionIdx : actionList) { Log.d(TAG, "apply carrier action idx: " + actionIdx); diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java new file mode 100644 index 000000000000..3e34f0aa6124 --- /dev/null +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.carrierdefaultapp; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.provider.Settings; +import android.util.Log; + +import com.android.internal.telephony.TelephonyIntents; + +/** + * Service to run {@link android.app.job.JobScheduler} job. + * Service to monitor when there is a change to conent URI + * {@link android.provider.Settings.Global#DEVICE_PROVISIONED DEVICE_PROVISIONED} + */ +public class ProvisionObserver extends JobService { + + private static final String TAG = ProvisionObserver.class.getSimpleName(); + public static final int PROVISION_OBSERVER_REEVALUATION_JOB_ID = 1; + // minimum & maximum update delay TBD + private static final int CONTENT_UPDATE_DELAY_MS = 100; + private static final int CONTENT_MAX_DELAY_MS = 200; + + @Override + public boolean onStartJob(JobParameters jobParameters) { + switch (jobParameters.getJobId()) { + case PROVISION_OBSERVER_REEVALUATION_JOB_ID: + if (isProvisioned(this)) { + Log.d(TAG, "device provisioned, force network re-evaluation"); + final ConnectivityManager connMgr = ConnectivityManager.from(this); + Network[] info = connMgr.getAllNetworks(); + for (Network nw : info) { + final NetworkCapabilities nc = connMgr.getNetworkCapabilities(nw); + if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + // force connectivity re-evaluation to assume skipped carrier actions. + // one of the following calls will match the last evaluation. + connMgr.reportNetworkConnectivity(nw, true); + connMgr.reportNetworkConnectivity(nw, false); + break; + } + } + } + default: + break; + } + return false; + } + + @Override + public boolean onStopJob(JobParameters jobParameters) { + return false; + } + + // Returns true if the device is not provisioned yet (in setup wizard), false otherwise + private static boolean isProvisioned(Context context) { + return Settings.Global.getInt(context.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0) == 1; + } + + /** + * Static utility function to schedule a job to execute upon the change of content URI + * {@link android.provider.Settings.Global#DEVICE_PROVISIONED DEVICE_PROVISIONED}. + * @param context The context used to retrieve the {@link ComponentName} and system services + * @return true carrier actions are deferred due to phone provisioning process, false otherwise + */ + public static boolean isDeferredForProvision(Context context, Intent intent) { + if (isProvisioned(context)) { + return false; + } + int jobId; + switch(intent.getAction()) { + case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED: + jobId = PROVISION_OBSERVER_REEVALUATION_JOB_ID; + break; + default: + return false; + } + final JobScheduler jobScheduler = (JobScheduler) context.getSystemService( + Context.JOB_SCHEDULER_SERVICE); + final JobInfo job = new JobInfo.Builder(jobId, + new ComponentName(context, ProvisionObserver.class)) + .addTriggerContentUri(new JobInfo.TriggerContentUri( + Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), 0)) + .setTriggerContentUpdateDelay(CONTENT_UPDATE_DELAY_MS) + .setTriggerContentMaxDelay(CONTENT_MAX_DELAY_MS) + .build(); + jobScheduler.schedule(job); + return true; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index fbc6aa382876..ae6ada2a258e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -21,6 +21,8 @@ import android.net.ScoredNetwork; import android.os.BatteryManager; import android.os.UserManager; import android.print.PrintManager; +import android.view.View; + import com.android.internal.util.UserIcons; import com.android.settingslib.drawable.UserIconDrawable; @@ -32,7 +34,7 @@ public class Utils { private static String sServicesSystemSharedLibPackageName; private static String sSharedSystemSharedLibPackageName; - static final int[] WIFI_PIE_FOR_BADGING = { + public static final int[] WIFI_PIE_FOR_BADGING = { com.android.internal.R.drawable.ic_signal_wifi_badged_0_bars, com.android.internal.R.drawable.ic_signal_wifi_badged_1_bar, com.android.internal.R.drawable.ic_signal_wifi_badged_2_bars, @@ -288,8 +290,15 @@ public class Utils { }); } - private static int getWifiBadgeResource(int badge) { + /** + * Returns the resource id for the given badge or {@link View.NO_ID} if no badge is to be shown. + * + * @throws IllegalArgumentException if the given badge value is not supported. + */ + public static int getWifiBadgeResource(int badge) { switch (badge) { + case ScoredNetwork.BADGING_NONE: + return View.NO_ID; case ScoredNetwork.BADGING_SD: return com.android.internal.R.drawable.ic_signal_wifi_badged_sd; case ScoredNetwork.BADGING_HD: diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index 2b1582d14830..24a3aa92b315 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -1429,6 +1429,23 @@ public class ApplicationsState { } }; + public static final AppFilter FILTER_GAMES = new AppFilter() { + @Override + public void init() { + } + + @Override + public boolean filterApp(ApplicationsState.AppEntry info) { + // TODO: Update for the new game category. + boolean isGame; + synchronized (info.info) { + isGame = ((info.info.flags & ApplicationInfo.FLAG_IS_GAME) != 0) + || info.info.category == ApplicationInfo.CATEGORY_GAME; + } + return isGame; + } + }; + public static class VolumeFilter implements AppFilter { private final String mVolumeUuid; diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index fabae573e4b0..6f52dcace323 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -12,13 +12,17 @@ package com.android.settingslib.wifi; import android.content.Intent; import android.net.NetworkInfo; +import android.net.NetworkKey; +import android.net.WifiKey; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; +import android.util.Log; import java.util.List; public class WifiStatusTracker { + private static final String TAG = "WifiStatusTracker"; private final WifiManager mWifiManager; public boolean enabled; @@ -26,6 +30,7 @@ public class WifiStatusTracker { public String ssid; public int rssi; public int level; + public NetworkKey networkKey; public WifiStatusTracker(WifiManager wifiManager) { mWifiManager = wifiManager; @@ -40,19 +45,32 @@ public class WifiStatusTracker { final NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); connected = networkInfo != null && networkInfo.isConnected(); + WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null + ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) + : mWifiManager.getConnectionInfo(); + // If Connected grab the signal strength and ssid. - if (connected) { - // try getting it out of the intent first - WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null - ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) - : mWifiManager.getConnectionInfo(); - if (info != null) { - ssid = getSsid(info); + if (connected && info != null) { + ssid = getSsid(info); + String bssid = info.getBSSID(); + if ((ssid != null) && (bssid != null)) { + // Reuse existing network key object if possible. + if ((networkKey == null) + || !networkKey.wifiKey.ssid.equals(ssid) + || !networkKey.wifiKey.bssid.equals(bssid)) { + try { + networkKey = new NetworkKey( + new WifiKey(ssid, bssid)); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Cannot create NetworkKey", e); + } + } } else { - ssid = null; + networkKey = null; } - } else if (!connected) { + } else { ssid = null; + networkKey = null; } } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { // Default to -200 as its below WifiManager.MIN_RSSI. diff --git a/packages/SettingsLib/tests/integ/Android.mk b/packages/SettingsLib/tests/integ/Android.mk index 98bce0ca42b2..bd910dd190db 100644 --- a/packages/SettingsLib/tests/integ/Android.mk +++ b/packages/SettingsLib/tests/integ/Android.mk @@ -28,7 +28,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-test \ espresso-core \ mockito-target-minus-junit4 \ - legacy-android-test + legacy-android-test \ + truth-prebuilt include frameworks/base/packages/SettingsLib/common.mk diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java new file mode 100644 index 000000000000..4f2347d845be --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.applications; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; + +import android.content.pm.ApplicationInfo; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ApplicationsStateTest { + private ApplicationsState.AppFilter mFilter; + private ApplicationsState.AppEntry mEntry; + + @Before + public void setUp() { + mFilter = ApplicationsState.FILTER_GAMES; + mEntry = mock(ApplicationsState.AppEntry.class); + mEntry.info = mock(ApplicationInfo.class); + } + + @Test + public void testGamesFilterAcceptsGameDeprecated() { + mEntry.info.flags = ApplicationInfo.FLAG_IS_GAME; + + assertThat(mFilter.filterApp(mEntry)).isTrue(); + } + + @Test + public void testGameFilterAcceptsCategorizedGame() { + mEntry.info.category = ApplicationInfo.CATEGORY_GAME; + + assertThat(mFilter.filterApp(mEntry)).isTrue(); + } + + @Test + public void testGameFilterAcceptsCategorizedGameAndDeprecatedIsGame() { + mEntry.info.flags = ApplicationInfo.FLAG_IS_GAME; + mEntry.info.category = ApplicationInfo.CATEGORY_GAME; + + assertThat(mFilter.filterApp(mEntry)).isTrue(); + } + + @Test + public void testGamesFilterRejectsNotGame() { + mEntry.info.category = ApplicationInfo.CATEGORY_UNDEFINED; + + assertThat(mFilter.filterApp(mEntry)).isFalse(); + } +} diff --git a/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml b/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml index 439ee3bc60d0..6264484a74d8 100644 --- a/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml +++ b/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml @@ -16,8 +16,8 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="17dp" android:height="17.0dp" - android:viewportWidth="20.0" - android:viewportHeight="20.0"> + android:viewportWidth="24.0" + android:viewportHeight="24.0"> <path android:fillColor="#FFFFFFFF" android:pathData="M19.0,6.41L17.59,5.0 12.0,10.59 6.41,5.0 5.0,6.41 10.59,12.0 5.0,17.59 6.41,19.0 12.0,13.41 17.59,19.0 19.0,17.59 13.41,12.0z"/> diff --git a/packages/SystemUI/res/drawable/ic_volume_accessibility.xml b/packages/SystemUI/res/drawable/ic_volume_accessibility.xml new file mode 100644 index 000000000000..657efaa213d4 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_volume_accessibility.xml @@ -0,0 +1,25 @@ +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="32dp" + android:height="32dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M20.5,6c-2.61,0.7 -5.67,1 -8.5,1s-5.89,-0.3 -8.5,-1L3,8c1.86,0.5 4,0.83 6,1v13h2v-6h2v6h2V9c2,-0.17 4.14,-0.5 6,-1l-0.5,-2zM12,6c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/stat_sys_data_disabled.xml b/packages/SystemUI/res/drawable/stat_sys_data_disabled.xml index 694019e98867..ea794d47312f 100644 --- a/packages/SystemUI/res/drawable/stat_sys_data_disabled.xml +++ b/packages/SystemUI/res/drawable/stat_sys_data_disabled.xml @@ -14,9 +14,10 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="8.5dp" + android:autoMirrored="true" + android:width="17.0dp" android:height="17.0dp" - android:viewportWidth="20.0" + android:viewportWidth="40.0" android:viewportHeight="40.0"> <path android:fillColor="#FFFFFFFF" diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml index 4122707de740..174776cef205 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml @@ -148,7 +148,7 @@ android:layout_alignParentTop="true" android:focusable="true" android:gravity="center_vertical" - android:paddingEnd="16dp" + android:paddingStart="16dp" android:paddingTop="6dp" android:singleLine="true" android:text="@*android:string/emergency_calls_only" diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml index 5ee242dc314c..789b765486fe 100644 --- a/packages/SystemUI/res/layout/recents_task_view_header.xml +++ b/packages/SystemUI/res/layout/recents_task_view_header.xml @@ -23,7 +23,6 @@ android:layout_gravity="top|center_horizontal"> <com.android.systemui.recents.views.FixedSizeImageView android:id="@+id/icon" - android:contentDescription="@string/recents_app_info_button_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|start" diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index b18b6acb6a35..30408143e30d 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -75,6 +75,9 @@ <!-- The color of the material notification background when dimmed --> <color name="notification_material_background_dimmed_color">#ccffffff</color> + <!-- The color of the material notification background when dark --> + <color name="notification_material_background_dark_color">#ff333333</color> + <!-- The color of the material notification background when low priority --> <color name="notification_material_background_low_priority_color">#fff5f5f5</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index ac86439665b3..d6ed9d8092ca 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -291,7 +291,7 @@ <bool name="quick_settings_show_full_alarm">false</bool> <!-- Whether to show a warning notification when the device reaches a certain temperature. --> - <bool name="config_showTemperatureWarning">false</bool> + <integer name="config_showTemperatureWarning">0</integer> <!-- Temp at which to show a warning notification if config_showTemperatureWarning is true. If < 0, uses the value from HardwarePropertiesManager#getDeviceTemperatures. --> diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 228996a707f1..ec11812611e8 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -30,9 +30,11 @@ import com.android.systemui.R; import com.android.systemui.assist.AssistManager; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.statusbar.BaseStatusBar; +import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.LightBarController; +import com.android.systemui.statusbar.phone.LockIcon; import com.android.systemui.statusbar.phone.LockscreenWallpaper; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.PhoneStatusBar; @@ -115,6 +117,11 @@ public class SystemUIFactory { return new NotificationIconAreaController(context, phoneStatusBar); } + public KeyguardIndicationController createKeyguardIndicationController(Context context, + ViewGroup indicationArea, LockIcon lockIcon) { + return new KeyguardIndicationController(context, indicationArea, lockIcon); + } + public QSTileHost createQSTileHost(Context context, PhoneStatusBar statusBar, StatusBarIconController iconController) { return new QSTileHost(context, statusBar, iconController); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index 3103267344a5..3df557d6227c 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -86,9 +86,12 @@ public class PipManager { // another package than the top activity in the stack boolean expandPipToFullscreen = true; if (sourceComponent != null) { - ComponentName topActivity = PipUtils.getTopPinnedActivity(mActivityManager); - expandPipToFullscreen = topActivity != null && topActivity.getPackageName().equals( - sourceComponent.getPackageName()); + ComponentName topActivity = PipUtils.getTopPinnedActivity(mContext, + mActivityManager); + if (topActivity != null && topActivity.getPackageName().equals( + sourceComponent.getPackageName())) { + expandPipToFullscreen = false; + } } if (expandPipToFullscreen) { mTouchHandler.expandPinnedStackToFullscreen(); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java index 22840138e152..d96baa6b3620 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java @@ -148,7 +148,8 @@ public class PipMediaController { */ private void resolveActiveMediaController(List<MediaController> controllers) { if (controllers != null) { - final ComponentName topActivity = PipUtils.getTopPinnedActivity(mActivityManager); + final ComponentName topActivity = PipUtils.getTopPinnedActivity(mContext, + mActivityManager); if (topActivity != null) { for (int i = 0; i < controllers.size(); i++) { final MediaController controller = controllers.get(i); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java index 9c03830810aa..a8cdd1bdb802 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java @@ -21,6 +21,7 @@ import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import android.app.ActivityManager.StackInfo; import android.app.IActivityManager; import android.content.ComponentName; +import android.content.Context; import android.os.RemoteException; import android.util.Log; @@ -29,14 +30,23 @@ public class PipUtils { private static final String TAG = "PipUtils"; /** - * @return the ComponentName of the top activity in the pinned stack, or null if none exists. + * @return the ComponentName of the top non-SystemUI activity in the pinned stack, or null if + * none exists. */ - public static ComponentName getTopPinnedActivity(IActivityManager activityManager) { + public static ComponentName getTopPinnedActivity(Context context, + IActivityManager activityManager) { try { - StackInfo pinnedStackInfo = activityManager.getStackInfo(PINNED_STACK_ID); + final String sysUiPackageName = context.getPackageName(); + final StackInfo pinnedStackInfo = activityManager.getStackInfo(PINNED_STACK_ID); if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null && pinnedStackInfo.taskIds.length > 0) { - return pinnedStackInfo.topActivity; + for (int i = pinnedStackInfo.taskNames.length - 1; i >= 0; i--) { + ComponentName cn = ComponentName.unflattenFromString( + pinnedStackInfo.taskNames[i]); + if (cn != null && !cn.getPackageName().equals(sysUiPackageName)) { + return cn; + } + } } } catch (RemoteException e) { Log.w(TAG, "Unable to get pinned stack."); diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index 28ca6a3a347b..1d4a5c71c2b4 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -22,6 +22,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.Resources; import android.database.ContentObserver; import android.os.BatteryManager; import android.os.Handler; @@ -221,11 +222,15 @@ public class PowerUI extends SystemUI { }; private void initTemperatureWarning() { - if (!mContext.getResources().getBoolean(R.bool.config_showTemperatureWarning)) { + ContentResolver resolver = mContext.getContentResolver(); + Resources resources = mContext.getResources(); + if (Settings.Global.getInt(resolver, Settings.Global.SHOW_TEMPERATURE_WARNING, + resources.getInteger(R.integer.config_showTemperatureWarning)) == 0) { return; } - mThrottlingTemp = mContext.getResources().getInteger(R.integer.config_warningTemperature); + mThrottlingTemp = Settings.Global.getFloat(resolver, Settings.Global.WARNING_TEMPERATURE, + resources.getInteger(R.integer.config_warningTemperature)); if (mThrottlingTemp < 0f) { // Get the throttling temperature. No need to check if we're not throttling. diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 0265c9e40112..8d18a75e6bdf 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -438,7 +438,7 @@ public class Recents extends SystemUI ActivityManager.StackId.isHomeOrRecentsStack(runningTask.stackId); if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) { logDockAttempt(mContext, runningTask.topActivity, runningTask.resizeMode); - if (runningTask.isDockable) { + if (runningTask.supportsSplitScreenMultiWindow) { if (metricsDockAction != -1) { MetricsLogger.action(mContext, metricsDockAction, runningTask.topActivity.flattenToShortString()); @@ -486,7 +486,6 @@ public class Recents extends SystemUI case ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE: return COUNTER_WINDOW_UNSUPPORTED; case ActivityInfo.RESIZE_MODE_RESIZEABLE: - case ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE: case ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION: return COUNTER_WINDOW_SUPPORTED; default: diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java index 5c7496d437f4..11b598478bfb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -197,7 +197,7 @@ public class RecentsTaskLoadPlan { Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon, thumbnail, title, titleDescription, dismissDescription, appInfoDescription, activityColor, backgroundColor, isLaunchTarget, isStackTask, isSystemApp, - t.isDockable, t.bounds, t.taskDescription, t.resizeMode, t.topActivity, + t.supportsSplitScreenMultiWindow, t.bounds, t.taskDescription, t.resizeMode, t.topActivity, isLocked); allTasks.add(task); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index 0777163293d9..b318ea72d94e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -462,7 +462,6 @@ public class TaskViewHeader extends FrameLayout mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor); mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ? mLightDismissDrawable : mDarkDismissDrawable); - mDismissButton.setContentDescription(t.dismissDescription); mDismissButton.setOnClickListener(this); mDismissButton.setClickable(false); ((RippleDrawable) mDismissButton.getBackground()).setForceSoftware(true); @@ -499,7 +498,6 @@ public class TaskViewHeader extends FrameLayout // In accessibility, a single click on the focused app info button will show it if (touchExplorationEnabled) { - mIconView.setContentDescription(t.appInfoDescription); mIconView.setOnClickListener(this); mIconView.setClickable(true); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index b5358bfb6f6e..b9ed72522bdb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -463,6 +463,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } mDark = dark; updateBackground(); + updateBackgroundTint(fade); if (!dark && fade && !shouldHideBackground()) { fadeInFromDark(delay); } @@ -700,8 +701,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView protected void updateBackground() { cancelFadeAnimations(); if (shouldHideBackground()) { - mBackgroundDimmed.setVisibility(View.INVISIBLE); - mBackgroundNormal.setVisibility(View.INVISIBLE); + mBackgroundDimmed.setVisibility(INVISIBLE); + mBackgroundNormal.setVisibility(mActivated ? VISIBLE : INVISIBLE); } else if (mDimmed) { // When groups are animating to the expanded state from the lockscreen, show the // normal background instead of the dimmed background @@ -940,6 +941,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView * @return the calculated background color */ private int calculateBgColor(boolean withTint, boolean withOverRide) { + if (mDark) { + return getContext().getColor(R.color.notification_material_background_dark_color); + } if (withOverRide && mOverrideTint != NO_COLOR) { int defaultTint = calculateBgColor(withTint, false); return NotificationUtils.interpolateColors(defaultTint, mOverrideTint, mOverrideAmount); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 6ac5cb8d3516..d9298ed6be27 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -24,6 +24,7 @@ import android.app.ActivityOptions; import android.app.INotificationManager; import android.app.KeyguardManager; import android.app.Notification; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.RemoteInput; @@ -41,6 +42,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.database.ContentObserver; +import android.graphics.PointF; import android.graphics.Rect; import android.os.AsyncTask; import android.os.Build; @@ -51,6 +53,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; @@ -316,9 +319,16 @@ public abstract class BaseStatusBar extends SystemUI implements }; private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { + private final int[] mTmpInt2 = new int[2]; + @Override public boolean onClickHandler( final View view, final PendingIntent pendingIntent, final Intent fillInIntent) { + view.getLocationInWindow(mTmpInt2); + wakeUpIfDozing(SystemClock.uptimeMillis(), new PointF( + mTmpInt2[0] + view.getWidth() / 2, mTmpInt2[1] + view.getHeight() / 2)); + + if (handleRemoteInput(view, pendingIntent, fillInIntent)) { return true; } @@ -1038,6 +1048,7 @@ public abstract class BaseStatusBar extends SystemUI implements private void bindGuts(final ExpandableNotificationRow row) { row.inflateGuts(); final StatusBarNotification sbn = row.getStatusBarNotification(); + final NotificationChannel channel = row.getEntry().channel; PackageManager pmUser = getPackageManagerForUser(mContext, sbn.getUser().getIdentifier()); row.setTag(sbn.getPackageName()); final NotificationGuts guts = row.getGuts(); @@ -1077,8 +1088,8 @@ public abstract class BaseStatusBar extends SystemUI implements closeControls(row, guts, v); } }; - guts.bindNotification(pmUser, iNotificationManager, sbn, onSettingsClick, onDoneClick, - mNonBlockablePkgs); + guts.bindNotification(pmUser, iNotificationManager, sbn, channel, + onSettingsClick, onDoneClick, mNonBlockablePkgs); } private void closeControls( @@ -1785,13 +1796,22 @@ public abstract class BaseStatusBar extends SystemUI implements return false; } + public void wakeUpIfDozing(long time, PointF where) { + } + private final class NotificationClicker implements View.OnClickListener { + private final int[] mTmpInt2 = new int[2]; + public void onClick(final View v) { if (!(v instanceof ExpandableNotificationRow)) { Log.e(TAG, "NotificationClicker called on a view that is not a notification row."); return; } + v.getLocationInWindow(mTmpInt2); + wakeUpIfDozing(SystemClock.uptimeMillis(), + new PointF(mTmpInt2[0] + v.getWidth() / 2, mTmpInt2[1] + v.getHeight() / 2)); + final ExpandableNotificationRow row = (ExpandableNotificationRow) v; final StatusBarNotification sbn = row.getStatusBarNotification(); if (sbn == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 08fd93da7924..d599ec1a2062 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -47,6 +47,7 @@ import com.android.systemui.R; import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; import com.android.systemui.statusbar.phone.LockIcon; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.policy.UserInfoController; /** * Controls the indications and error messages shown on the Keyguard @@ -83,6 +84,8 @@ public class KeyguardIndicationController { private int mChargingWattage; private String mMessageToShowOnScreenOn; + private KeyguardUpdateMonitorCallback mUpdateMonitor; + private final DevicePolicyManager mDevicePolicyManager; public KeyguardIndicationController(Context context, ViewGroup indicationArea, @@ -106,7 +109,7 @@ public class KeyguardIndicationController { mDevicePolicyManager = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); - KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitor); + KeyguardUpdateMonitor.getInstance(context).registerCallback(getKeyguardCallback()); context.registerReceiverAsUser(mTickReceiver, UserHandle.SYSTEM, new IntentFilter(Intent.ACTION_TIME_TICK), null, Dependency.get(Dependency.TIME_TICK_HANDLER)); @@ -114,6 +117,23 @@ public class KeyguardIndicationController { updateDisclosure(); } + /** + * Gets the {@link KeyguardUpdateMonitorCallback} instance associated with this + * {@link KeyguardIndicationController}. + * + * <p>Subclasses may override this method to extend or change the callback behavior by extending + * the {@link BaseKeyguardCallback}. + * + * @return A KeyguardUpdateMonitorCallback. Multiple calls to this method <b>must</b> return the + * same instance. + */ + protected KeyguardUpdateMonitorCallback getKeyguardCallback() { + if (mUpdateMonitor == null) { + mUpdateMonitor = new BaseKeyguardCallback(); + } + return mUpdateMonitor; + } + private void updateDisclosure() { if (mDevicePolicyManager == null) { return; @@ -152,6 +172,12 @@ public class KeyguardIndicationController { } /** + * Sets the active controller managing changes and callbacks to user information. + */ + public void setUserInfoController(UserInfoController userInfoController) { + } + + /** * Hides transient indication in {@param delayMs}. */ public void hideTransientIndicationDelayed(long delayMs) { @@ -264,8 +290,37 @@ public class KeyguardIndicationController { } } - KeyguardUpdateMonitorCallback mUpdateMonitor = new KeyguardUpdateMonitorCallback() { - public int mLastSuccessiveErrorMessage = -1; + public void setStatusBarKeyguardViewManager( + StatusBarKeyguardViewManager statusBarKeyguardViewManager) { + mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + } + + BroadcastReceiver mTickReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mHandler.post(() -> { + if (mVisible) { + updateIndication(); + } + }); + } + }; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_HIDE_TRANSIENT && mTransientIndication != null) { + mTransientIndication = null; + updateIndication(); + } else if (msg.what == MSG_CLEAR_FP_MSG) { + mLockIcon.setTransientFpError(false); + hideTransientIndication(); + } + } + }; + + protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback { + private int mLastSuccessiveErrorMessage = -1; @Override public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { @@ -372,34 +427,4 @@ public class KeyguardIndicationController { } } }; - - BroadcastReceiver mTickReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - mHandler.post(() -> { - if (mVisible) { - updateIndication(); - } - }); - } - }; - - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_HIDE_TRANSIENT && mTransientIndication != null) { - mTransientIndication = null; - updateIndication(); - } else if (msg.what == MSG_CLEAR_FP_MSG) { - mLockIcon.setTransientFpError(false); - hideTransientIndication(); - } - } - }; - - public void setStatusBarKeyguardViewManager( - StatusBarKeyguardViewManager statusBarKeyguardViewManager) { - mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 458daf162b24..3a891860c0cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar; import android.app.Notification; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.graphics.drawable.Icon; @@ -55,6 +56,7 @@ public class NotificationData { private static final int COLOR_INVALID = 1; public String key; public StatusBarNotification notification; + public NotificationChannel channel; public StatusBarIconView icon; public StatusBarIconView expandedIcon; public ExpandableNotificationRow row; // the outer expanded view @@ -429,6 +431,14 @@ public class NotificationData { return null; } + public NotificationChannel getChannel(String key) { + if (mRankingMap != null) { + mRankingMap.getRanking(key, mTmpRanking); + return mTmpRanking.getChannel(); + } + return null; + } + private void updateRankingAndSort(RankingMap ranking) { if (ranking != null) { mRankingMap = ranking; @@ -442,6 +452,7 @@ public class NotificationData { entry.notification.setOverrideGroupKey(overrideGroupKey); mGroupManager.onEntryUpdated(entry, oldSbn); } + entry.channel = getChannel(entry.key); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java index c7adb60c25f3..83104e685e22 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java @@ -72,11 +72,7 @@ public class NotificationGuts extends LinearLayout { private INotificationManager mINotificationManager; private int mStartingUserImportance; private StatusBarNotification mStatusBarNotification; - - private ImageView mAutoButton; - private TextView mImportanceSummary; - private TextView mImportanceTitle; - private boolean mAuto; + private NotificationChannel mNotificationChannel; private View mImportanceGroup; private View mChannelDisabled; @@ -170,11 +166,12 @@ public class NotificationGuts extends LinearLayout { } void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager, - final StatusBarNotification sbn, OnSettingsClickListener onSettingsClick, + final StatusBarNotification sbn, final NotificationChannel channel, + OnSettingsClickListener onSettingsClick, OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) { mINotificationManager = iNotificationManager; + mNotificationChannel = channel; mStatusBarNotification = sbn; - final NotificationChannel channel = sbn.getNotificationChannel(); mStartingUserImportance = channel.getImportance(); final String pkg = sbn.getPackageName(); @@ -288,14 +285,13 @@ public class NotificationGuts extends LinearLayout { if (selectedImportance == mStartingUserImportance) { return; } - final NotificationChannel channel = mStatusBarNotification.getNotificationChannel(); MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE, selectedImportance - mStartingUserImportance); - channel.setImportance(selectedImportance); + mNotificationChannel.setImportance(selectedImportance); try { mINotificationManager.updateNotificationChannelForPackage( mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(), - channel); + mNotificationChannel); } catch (RemoteException e) { // :( } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java index 1128101f142b..c8e8973b2113 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java @@ -16,15 +16,18 @@ package com.android.systemui.statusbar; +import android.annotation.ColorInt; import android.annotation.DrawableRes; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Animatable; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; import android.telephony.SubscriptionInfo; import android.util.ArraySet; import android.util.AttributeSet; @@ -74,8 +77,10 @@ public class SignalClusterView private boolean mEthernetVisible = false; private int mEthernetIconId = 0; private int mLastEthernetIconId = -1; + private int mWifiBadgeId = -1; private boolean mWifiVisible = false; private int mWifiStrengthId = 0; + private int mLastWifiBadgeId = -1; private int mLastWifiStrengthId = -1; private boolean mIsAirplaneMode = false; private int mAirplaneIconId = 0; @@ -259,6 +264,7 @@ public class SignalClusterView boolean activityIn, boolean activityOut, String description) { mWifiVisible = statusIcon.visible && !mBlockWifi; mWifiStrengthId = statusIcon.icon; + mWifiBadgeId = statusIcon.iconOverlay; mWifiDescription = statusIcon.contentDescription; apply(); @@ -399,6 +405,7 @@ public class SignalClusterView mWifi.setImageDrawable(null); mWifiDark.setImageDrawable(null); mLastWifiStrengthId = -1; + mLastWifiBadgeId = -1; } for (PhoneState state : mPhoneStates) { @@ -464,10 +471,16 @@ public class SignalClusterView (mEthernetVisible ? "VISIBLE" : "GONE"))); if (mWifiVisible) { - if (mWifiStrengthId != mLastWifiStrengthId) { - setIconForView(mWifi, mWifiStrengthId); - setIconForView(mWifiDark, mWifiStrengthId); + if (mWifiStrengthId != mLastWifiStrengthId || mWifiBadgeId != mLastWifiBadgeId) { + if (mWifiBadgeId == -1) { + setIconForView(mWifi, mWifiStrengthId); + setIconForView(mWifiDark, mWifiStrengthId); + } else { + setBadgedWifiIconForView(mWifi, mWifiStrengthId, mWifiBadgeId); + setBadgedWifiIconForView(mWifiDark, mWifiStrengthId, mWifiBadgeId); + } mLastWifiStrengthId = mWifiStrengthId; + mLastWifiBadgeId = mWifiBadgeId; } mWifiGroup.setContentDescription(mWifiDescription); mWifiGroup.setVisibility(View.VISIBLE); @@ -529,6 +542,10 @@ public class SignalClusterView // Using the imageView's context to retrieve the Drawable so that theme is preserved. Drawable icon = imageView.getContext().getDrawable(iconId); + setScaledIcon(imageView, icon); + } + + private void setScaledIcon(ImageView imageView, Drawable icon) { if (mIconScaleFactor == 1.f) { imageView.setImageDrawable(icon); } else { @@ -536,6 +553,33 @@ public class SignalClusterView } } + /** + * Creates and sets a LayerDrawable from the given ids on the given view. + * + * <p>This method will also scale the icon by {@link #mIconScaleFactor} if appropriate. + */ + private void setBadgedWifiIconForView(ImageView imageView, @DrawableRes int wifiPieId, + @DrawableRes int badgeId) { + // Using the imageView's context to retrieve the Drawable so that theme is preserved.; + LayerDrawable icon = new LayerDrawable(new Drawable[] { + imageView.getContext().getDrawable(wifiPieId), + imageView.getContext().getDrawable(badgeId)}); + + // The LayerDrawable shares an underlying state so we must mutate the object to change the + // color between the light and dark themes. + icon.mutate().setTint(getColorAttr(imageView.getContext(), R.attr.singleToneColor)); + + setScaledIcon(imageView, icon); + } + + /** Returns the given color attribute value, or white if not defined. */ + @ColorInt private static int getColorAttr(Context context, int attr) { + TypedArray ta = context.obtainStyledAttributes(new int[] {attr}); + @ColorInt int colorAccent = ta.getColor(0, Color.WHITE); + ta.recycle(); + return colorAccent; + } + public void setIconTint(int tint, float darkIntensity, Rect tintArea) { boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity || !mTintArea.equals(tintArea); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java index 695b500363e2..ef42b2f43287 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java @@ -29,11 +29,12 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; import com.android.systemui.statusbar.KeyguardAffordanceView; import com.android.systemui.statusbar.policy.AccessibilityController; +import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener; /** * Manages the different states and animations of the unlock icon. */ -public class LockIcon extends KeyguardAffordanceView { +public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChangedListener { private static final int FP_DRAW_OFF_TIMEOUT = 800; @@ -49,6 +50,7 @@ public class LockIcon extends KeyguardAffordanceView { private boolean mDeviceInteractive; private boolean mScreenOn; private boolean mLastScreenOn; + private Drawable mUserAvatarIcon; private TrustDrawable mTrustDrawable; private final UnlockMethodCache mUnlockMethodCache; private AccessibilityController mAccessibilityController; @@ -80,6 +82,12 @@ public class LockIcon extends KeyguardAffordanceView { mTrustDrawable.stop(); } + @Override + public void onUserInfoChanged(String name, Drawable picture, String userAccount) { + mUserAvatarIcon = picture; + update(); + } + public void setTransientFpError(boolean transientFpError) { mTransientFpError = transientFpError; update(); @@ -126,27 +134,33 @@ public class LockIcon extends KeyguardAffordanceView { boolean trustHidden = anyFingerprintIcon; if (state != mLastState || mDeviceInteractive != mLastDeviceInteractive || mScreenOn != mLastScreenOn || force) { - boolean isAnim = true; - int iconRes = getAnimationResForTransition(mLastState, state, mLastDeviceInteractive, + int iconAnimRes = + getAnimationResForTransition(mLastState, state, mLastDeviceInteractive, mDeviceInteractive, mLastScreenOn, mScreenOn); - if (iconRes == R.drawable.lockscreen_fingerprint_draw_off_animation) { + boolean isAnim = iconAnimRes != -1; + if (iconAnimRes == R.drawable.lockscreen_fingerprint_draw_off_animation) { anyFingerprintIcon = true; useAdditionalPadding = true; trustHidden = true; - } else if (iconRes == R.drawable.trusted_state_to_error_animation) { + } else if (iconAnimRes == R.drawable.trusted_state_to_error_animation) { anyFingerprintIcon = true; useAdditionalPadding = false; trustHidden = true; - } else if (iconRes == R.drawable.error_to_trustedstate_animation) { + } else if (iconAnimRes == R.drawable.error_to_trustedstate_animation) { anyFingerprintIcon = true; useAdditionalPadding = false; trustHidden = false; } - if (iconRes == -1) { - iconRes = getIconForState(state, mScreenOn, mDeviceInteractive); - isAnim = false; + + Drawable icon; + if (isAnim) { + // Load the animation resource. + icon = mContext.getDrawable(iconAnimRes); + } else { + // Load the static icon resource based on the current state. + icon = getIconForState(state, mScreenOn, mDeviceInteractive); } - Drawable icon = mContext.getDrawable(iconRes); + final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable ? (AnimatedVectorDrawable) icon : null; @@ -175,7 +189,7 @@ public class LockIcon extends KeyguardAffordanceView { animation.start(); } - if (iconRes == R.drawable.lockscreen_fingerprint_draw_off_animation) { + if (iconAnimRes == R.drawable.lockscreen_fingerprint_draw_off_animation) { removeCallbacks(mDrawOffTimeout); postDelayed(mDrawOffTimeout, FP_DRAW_OFF_TIMEOUT); } else { @@ -225,25 +239,38 @@ public class LockIcon extends KeyguardAffordanceView { mAccessibilityController = accessibilityController; } - private int getIconForState(int state, boolean screenOn, boolean deviceInteractive) { + private Drawable getIconForState(int state, boolean screenOn, boolean deviceInteractive) { + int iconRes; switch (state) { case STATE_LOCKED: - return R.drawable.ic_lock_24dp; + iconRes = R.drawable.ic_lock_24dp; + break; case STATE_LOCK_OPEN: - return R.drawable.ic_lock_open_24dp; + if (mUnlockMethodCache.isTrustManaged() && mUnlockMethodCache.isTrusted() + && mUserAvatarIcon != null) { + return mUserAvatarIcon; + } else { + iconRes = R.drawable.ic_lock_open_24dp; + } + break; case STATE_FACE_UNLOCK: - return com.android.internal.R.drawable.ic_account_circle; + iconRes = com.android.internal.R.drawable.ic_account_circle; + break; case STATE_FINGERPRINT: // If screen is off and device asleep, use the draw on animation so the first frame // gets drawn. - return screenOn && deviceInteractive + iconRes = screenOn && deviceInteractive ? R.drawable.ic_fingerprint : R.drawable.lockscreen_fingerprint_draw_on_animation; + break; case STATE_FINGERPRINT_ERROR: - return R.drawable.ic_fingerprint_error; + iconRes = R.drawable.ic_fingerprint_error; + break; default: throw new IllegalArgumentException(); } + + return mContext.getDrawable(iconRes); } private int getAnimationResForTransition(int oldState, int newState, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 3c46d269750f..d40326a8246e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -546,8 +546,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { private boolean onLongPressRecents() { if (mRecents == null || !ActivityManager.supportsMultiWindow() - || !mDivider.getView().getSnapAlgorithm() - .isSplitScreenFeasible()) { + || !mDivider.getView().getSnapAlgorithm().isSplitScreenFeasible()) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 3291d591e836..8dcc693a6c14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -801,7 +801,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, (KeyguardStatusView) mStatusBarWindow.findViewById(R.id.keyguard_status_view); mKeyguardBottomArea = (KeyguardBottomAreaView) mStatusBarWindow.findViewById(R.id.keyguard_bottom_area); - mKeyguardIndicationController = new KeyguardIndicationController(mContext, + mKeyguardIndicationController = + SystemUIFactory.getInstance().createKeyguardIndicationController(mContext, (ViewGroup) mStatusBarWindow.findViewById(R.id.keyguard_indication_area), mKeyguardBottomArea.getLockIcon()); mKeyguardBottomArea.setKeyguardIndicationController(mKeyguardIndicationController); @@ -1179,6 +1180,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mFingerprintUnlockController); mKeyguardIndicationController.setStatusBarKeyguardViewManager( mStatusBarKeyguardViewManager); + mKeyguardIndicationController.setUserInfoController( + Dependency.get(UserInfoController.class)); mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); mIconPolicy.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); mRemoteInputController.addCallback(mStatusBarKeyguardViewManager); @@ -1388,7 +1391,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, newNotification.headsUpContentView = sbn.getNotification().headsUpContentView; StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(), - sbn.getOpPkg(), sbn.getNotificationChannel(), + sbn.getOpPkg(), sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); @@ -3580,7 +3583,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, public void postQSRunnableDismissingKeyguard(final Runnable runnable) { mHandler.post(() -> { mLeaveOpenOnKeyguardHide = true; - executeRunnableDismissingKeyguard(runnable, null, false, false, false); + executeRunnableDismissingKeyguard(() -> mHandler.post(runnable), null, false, false, + false); }); } @@ -4604,12 +4608,13 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, return !mNotificationData.getActiveNotifications().isEmpty(); } - public void wakeUpIfDozing(long time, MotionEvent event) { + @Override + public void wakeUpIfDozing(long time, PointF where) { if (mDozing && mDozeScrimController.isPulsing()) { PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); pm.wakeUp(time, "com.android.systemui:NODOZE"); mWakeUpComingFromTouch = true; - mWakeUpTouchLocation = new PointF(event.getX(), event.getY()); + mWakeUpTouchLocation = where; mNotificationPanel.setTouchDisabled(false); mStatusBarKeyguardViewManager.notifyDeviceWakeUpRequested(); mFalsingManager.onScreenOnFromTouch(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java index 9e9380295f0c..a7064081753c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java @@ -157,7 +157,7 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements // Set the light/dark theming on the header status UI to match the current theme. SignalClusterView cluster = (SignalClusterView) findViewById(R.id.signal_cluster); int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground); - float intensity = colorForeground / (float) Color.WHITE; + float intensity = colorForeground == Color.WHITE ? 0 : 1; cluster.setIconTint(colorForeground, intensity, new Rect(0, 0, 0, 0)); BatteryMeterView battery = (BatteryMeterView) findViewById(R.id.battery); int colorSecondary = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index 1b73a3f53997..ccd63574426e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -29,6 +29,7 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.media.AudioManager; import android.media.session.MediaSessionLegacyHelper; import android.net.Uri; import android.os.Bundle; @@ -223,7 +224,8 @@ public class StatusBarWindowView extends FrameLayout { case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_UP: if (mService.isDozing()) { - MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, true); + MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent( + event, AudioManager.USE_DEFAULT_STREAM_TYPE, true); return true; } break; @@ -263,12 +265,9 @@ public class StatusBarWindowView extends FrameLayout { if (mNotificationPanel.isFullyExpanded() && mStackScrollLayout.getVisibility() == View.VISIBLE && mService.getBarState() == StatusBarState.KEYGUARD - && !mService.isBouncerShowing()) { + && !mService.isBouncerShowing() + && !mService.isDozing()) { intercept = mDragDownHelper.onInterceptTouchEvent(ev); - // wake up on a touch down event, if dozing - if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { - mService.wakeUpIfDozing(ev.getEventTime(), ev); - } } if (!intercept) { super.onInterceptTouchEvent(ev); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java index 0fc300d1aa07..528fefe49653 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java @@ -91,9 +91,11 @@ public class DeviceProvisionedControllerImpl extends CurrentUserTracker implemen @Override public void onUserSwitched(int newUserId) { - stopListening(); - startListening(newUserId); - notifyUserChanged(); + mContentResolver.unregisterContentObserver(mSettingsObserver); + mContentResolver.registerContentObserver(mDeviceProvisionedUri, true, + mSettingsObserver, 0); + mContentResolver.registerContentObserver(mUserSetupUri, true, + mSettingsObserver, newUserId); notifyUserChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java index a22fc6b8b72d..a3a9d71877b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.policy; import android.content.Context; import android.content.Intent; import android.telephony.SubscriptionInfo; +import android.view.View; + import com.android.settingslib.net.DataUsageController; import com.android.settingslib.wifi.AccessPoint; import com.android.systemui.DemoMode; @@ -68,15 +70,29 @@ public interface NetworkController extends CallbackController<SignalCallback>, D public static class IconState { public final boolean visible; + public final int icon; + + /** + * Optional iconOverlay resource id. + * + * <p>Set to -1 if not present. + */ + public final int iconOverlay; + public final String contentDescription; - public IconState(boolean visible, int icon, String contentDescription) { + public IconState(boolean visible, int icon, int iconOverlay, String contentDescription) { this.visible = visible; this.icon = icon; + this.iconOverlay = iconOverlay; this.contentDescription = contentDescription; } + public IconState(boolean visible, int icon, String contentDescription) { + this(visible, icon, -1 /* iconOverlay */, contentDescription); + } + public IconState(boolean visible, int icon, int contentDescription, Context context) { this(visible, icon, context.getString(contentDescription)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index edf2c8a93650..5e13f5959e30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -24,6 +24,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.NetworkCapabilities; +import android.net.NetworkScoreManager; import android.net.wifi.WifiManager; import android.os.AsyncTask; import android.os.Bundle; @@ -88,6 +89,7 @@ public class NetworkControllerImpl extends BroadcastReceiver private final DataSaverController mDataSaverController; private final CurrentUserTracker mUserTracker; private Config mConfig; + private final NetworkScoreManager mNetworkScoreManager; // Subcontrollers. @VisibleForTesting @@ -145,9 +147,12 @@ public class NetworkControllerImpl extends BroadcastReceiver public NetworkControllerImpl(Context context, Looper bgLooper, DeviceProvisionedController deviceProvisionedController) { this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE), + context.getSystemService(NetworkScoreManager.class), (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE), (WifiManager) context.getSystemService(Context.WIFI_SERVICE), - SubscriptionManager.from(context), Config.readConfig(context), bgLooper, + SubscriptionManager.from(context), + Config.readConfig(context), + bgLooper, new CallbackHandler(), new AccessPointControllerImpl(context, bgLooper), new DataUsageController(context), @@ -158,8 +163,12 @@ public class NetworkControllerImpl extends BroadcastReceiver @VisibleForTesting NetworkControllerImpl(Context context, ConnectivityManager connectivityManager, - TelephonyManager telephonyManager, WifiManager wifiManager, - SubscriptionManager subManager, Config config, Looper bgLooper, + NetworkScoreManager networkScoreManager, + TelephonyManager telephonyManager, + WifiManager wifiManager, + SubscriptionManager subManager, + Config config, + Looper bgLooper, CallbackHandler callbackHandler, AccessPointControllerImpl accessPointController, DataUsageController dataUsageController, @@ -182,6 +191,7 @@ public class NetworkControllerImpl extends BroadcastReceiver // wifi mWifiManager = wifiManager; + mNetworkScoreManager = networkScoreManager; mLocale = mContext.getResources().getConfiguration().locale; mAccessPoints = accessPointController; @@ -195,7 +205,7 @@ public class NetworkControllerImpl extends BroadcastReceiver } }); mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature, - mCallbackHandler, this); + mCallbackHandler, this, mNetworkScoreManager); mEthernetSignalController = new EthernetSignalController(mContext, mCallbackHandler, this); @@ -799,6 +809,8 @@ public class NetworkControllerImpl extends BroadcastReceiver MobileSignalController controller = mMobileSignalControllers .values().toArray(new MobileSignalController[0])[slot]; controller.getState().dataSim = datatype != null; + controller.getState().isDefault = datatype != null; + controller.getState().dataConnected = datatype != null; if (datatype != null) { controller.getState().iconGroup = datatype.equals("1x") ? TelephonyIcons.ONE_X : @@ -811,6 +823,7 @@ public class NetworkControllerImpl extends BroadcastReceiver datatype.equals("lte") ? TelephonyIcons.LTE : datatype.equals("lte+") ? TelephonyIcons.LTE_PLUS : datatype.equals("roam") ? TelephonyIcons.ROAMING : + datatype.equals("dis") ? TelephonyIcons.DATA_DISABLED : TelephonyIcons.UNKNOWN; } int[][] icons = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH; @@ -836,9 +849,11 @@ public class NetworkControllerImpl extends BroadcastReceiver private SubscriptionInfo addSignalController(int id, int simSlotIndex) { SubscriptionInfo info = new SubscriptionInfo(id, "", simSlotIndex, "", "", 0, 0, "", 0, null, 0, 0, ""); - mMobileSignalControllers.put(id, new MobileSignalController(mContext, + MobileSignalController controller = new MobileSignalController(mContext, mConfig, mHasMobileDataFeature, mPhone, mCallbackHandler, this, info, - mSubDefaults, mReceiverHandler.getLooper())); + mSubDefaults, mReceiverHandler.getLooper()); + mMobileSignalControllers.put(id, controller); + controller.getState().userSetup = true; return info; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java index b59cf6862292..ed8c7ff96aac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java @@ -224,7 +224,7 @@ class TelephonyIcons { static final int ICON_CARRIER_NETWORK_CHANGE = R.drawable.stat_sys_signal_carrier_network_change_animation; - static final int ICON_DATA_DISABLED = R.drawable.ic_qs_data_disabled; + static final int ICON_DATA_DISABLED = R.drawable.stat_sys_data_disabled; static final int QS_ICON_LTE = R.drawable.ic_qs_signal_lte; static final int QS_ICON_3G = R.drawable.ic_qs_signal_3g; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java index b1bc2f07a414..42c20ff6e2ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java @@ -15,18 +15,27 @@ */ package com.android.systemui.statusbar.policy; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.database.ContentObserver; import android.net.NetworkCapabilities; +import android.net.NetworkKey; +import android.net.NetworkScoreManager; +import android.net.ScoredNetwork; import android.net.wifi.WifiManager; +import android.net.wifi.WifiNetworkScoreCache; +import android.net.wifi.WifiNetworkScoreCache.CacheListener; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Messenger; +import android.provider.Settings; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.AsyncChannel; +import com.android.settingslib.Utils; import com.android.settingslib.wifi.WifiStatusTracker; import com.android.systemui.statusbar.policy.NetworkController.IconState; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; @@ -34,17 +43,24 @@ import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; import com.android.systemui.R; import java.util.Objects; +import java.util.List; public class WifiSignalController extends SignalController<WifiSignalController.WifiState, SignalController.IconGroup> { + private final WifiManager mWifiManager; private final AsyncChannel mWifiChannel; private final boolean mHasMobileData; + private final NetworkScoreManager mNetworkScoreManager; + private final WifiNetworkScoreCache mScoreCache; private final WifiStatusTracker mWifiTracker; + private boolean mScoringEnabled = false; + public WifiSignalController(Context context, boolean hasMobileData, - CallbackHandler callbackHandler, NetworkControllerImpl networkController) { + CallbackHandler callbackHandler, NetworkControllerImpl networkController, + NetworkScoreManager networkScoreManager) { super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI, callbackHandler, networkController); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); @@ -68,6 +84,44 @@ public class WifiSignalController extends WifiIcons.QS_WIFI_NO_NETWORK, AccessibilityContentDescriptions.WIFI_NO_CONNECTION ); + + mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(handler) { + @Override + public void networkCacheUpdated(List<ScoredNetwork> networks) { + mCurrentState.badgeEnum = getWifiBadgeEnum(); + notifyListenersIfNecessary(); + } + }); + + // Setup scoring + mNetworkScoreManager = networkScoreManager; + ContentObserver observer = new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + mScoringEnabled = + Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) == 1; + if (!mScoringEnabled) { + mScoreCache.clearScores(); + } + } + }; + ContentResolver cr = mContext.getContentResolver(); + cr.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED), + false /* notifyForDescendants */, + observer); + observer.onChange(false /* selfChange */); + registerScoreCache(); + } + + private void registerScoreCache() { + Log.d(mTag, "Registered score cache"); + mNetworkScoreManager.registerNetworkScoreCache( + NetworkKey.TYPE_WIFI, + mScoreCache, + NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK); } @Override @@ -88,27 +142,74 @@ public class WifiSignalController extends ("," + mContext.getString(R.string.accessibility_quick_settings_no_internet)); } - IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), contentDescription); - IconState qsIcon = new IconState(mCurrentState.connected, getQsCurrentIconId(), - contentDescription); + IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), + Utils.getWifiBadgeResource(mCurrentState.badgeEnum), contentDescription); + IconState qsIcon = new IconState( + mCurrentState.connected, getQsCurrentIconId(), + Utils.getWifiBadgeResource(mCurrentState.badgeEnum), contentDescription); callback.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon, ssidPresent && mCurrentState.activityIn, ssidPresent && mCurrentState.activityOut, wifiDesc); } + @Override + public int getCurrentIconId() { + if (mCurrentState.badgeEnum != ScoredNetwork.BADGING_NONE) { + return Utils.WIFI_PIE_FOR_BADGING[mCurrentState.level]; + } + return super.getCurrentIconId(); + } + /** * Extract wifi state directly from broadcasts about changes in wifi state. */ public void handleBroadcast(Intent intent) { + // Update the WifiStatusTracker with the new information and update the score cache. + NetworkKey previousNetworkKey = mWifiTracker.networkKey; mWifiTracker.handleBroadcast(intent); + updateScoreCacheIfNecessary(previousNetworkKey); + mCurrentState.enabled = mWifiTracker.enabled; mCurrentState.connected = mWifiTracker.connected; mCurrentState.ssid = mWifiTracker.ssid; mCurrentState.rssi = mWifiTracker.rssi; mCurrentState.level = mWifiTracker.level; + mCurrentState.badgeEnum = getWifiBadgeEnum(); notifyListenersIfNecessary(); } + /** + * Clears old scores out of the cache and requests new scores if the network key has changed. + * + * <p>New scores are requested asynchronously. + */ + private void updateScoreCacheIfNecessary(NetworkKey previousNetworkKey) { + if (mWifiTracker.networkKey == null) { + return; + } + if ((previousNetworkKey == null) || !mWifiTracker.networkKey.equals(previousNetworkKey)) { + mScoreCache.clearScores(); + mNetworkScoreManager.requestScores(new NetworkKey[]{mWifiTracker.networkKey}); + } + } + + /** + * Returns the wifi badge enum for the current {@link #mWifiTracker} state. + * + * <p>{@link #updateScoreCacheIfNecessary} should be called prior to this method. + */ + private int getWifiBadgeEnum() { + if (!mScoringEnabled || mWifiTracker.networkKey == null) { + return ScoredNetwork.BADGING_NONE; + } + ScoredNetwork score = mScoreCache.getScoredNetwork(mWifiTracker.networkKey); + + if (score != null) { + return score.calculateBadge(mWifiTracker.rssi); + } + return ScoredNetwork.BADGING_NONE; + } + @VisibleForTesting void setActivity(int wifiActivity) { mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT @@ -149,12 +250,14 @@ public class WifiSignalController extends static class WifiState extends SignalController.State { String ssid; + int badgeEnum; @Override public void copyFrom(State s) { super.copyFrom(s); WifiState state = (WifiState) s; ssid = state.ssid; + badgeEnum = state.badgeEnum; } @Override @@ -166,7 +269,8 @@ public class WifiSignalController extends @Override public boolean equals(Object o) { return super.equals(o) - && Objects.equals(((WifiState) o).ssid, ssid); + && Objects.equals(((WifiState) o).ssid, ssid) + && (((WifiState) o).badgeEnum == badgeEnum); } } } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java index f6b8891863d4..266f05372813 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java @@ -23,6 +23,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Bundle; +import android.provider.Settings; import android.support.v14.preference.PreferenceFragment; import android.support.v14.preference.SwitchPreference; import android.support.v7.preference.PreferenceCategory; @@ -30,9 +31,9 @@ import android.support.v7.preference.PreferenceScreen; import android.support.v7.preference.PreferenceViewHolder; import android.view.View; +import com.android.systemui.R; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.PluginPrefs; -import com.android.systemui.R; import java.util.List; import java.util.Set; @@ -147,6 +148,12 @@ public class PluginFragment extends PreferenceFragment { result.activityInfo.name))); } }); + holder.itemView.setOnLongClickListener(v -> { + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.fromParts("package", mComponent.getPackageName(), null)); + getContext().startActivity(intent); + return true; + }); } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java index d23ebc123e17..d057d863e9ef 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java @@ -127,6 +127,7 @@ public class VolumeDialog implements TunerService.Tunable { private boolean mShowing; private boolean mExpanded; + private boolean mShowA11yStream; private int mActiveStream; private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE; @@ -244,7 +245,6 @@ public class VolumeDialog implements TunerService.Tunable { if (!AudioSystem.isSingleVolume(mContext)) { addRow(AudioManager.STREAM_RING, R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true); - addRow(AudioManager.STREAM_ALARM, R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, false); addRow(AudioManager.STREAM_VOICE_CALL, @@ -253,6 +253,8 @@ public class VolumeDialog implements TunerService.Tunable { R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false); addRow(AudioManager.STREAM_SYSTEM, R.drawable.ic_volume_system, R.drawable.ic_volume_system_mute, false); + addRow(AudioManager.STREAM_ACCESSIBILITY, R.drawable.ic_volume_accessibility, + R.drawable.ic_volume_accessibility, true); } } else { addExistingRows(); @@ -307,10 +309,24 @@ public class VolumeDialog implements TunerService.Tunable { } private void addRow(int stream, int iconRes, int iconMuteRes, boolean important) { + addRow(stream, iconRes, iconMuteRes, important, false); + } + + private void addRow(int stream, int iconRes, int iconMuteRes, boolean important, + boolean dynamic) { VolumeRow row = new VolumeRow(); initRow(row, stream, iconRes, iconMuteRes, important); - mDialogRowsView.addView(row.view); - mRows.add(row); + int rowSize; + int viewSize; + if (mShowA11yStream && dynamic && (rowSize = mRows.size()) > 1 + && (viewSize = mDialogRowsView.getChildCount()) > 1) { + // A11y Stream should be the last in the list + mDialogRowsView.addView(row.view, viewSize - 2); + mRows.add(rowSize - 2, row); + } else { + mDialogRowsView.addView(row.view); + mRows.add(row); + } } private void addExistingRows() { @@ -592,6 +608,9 @@ public class VolumeDialog implements TunerService.Tunable { } private boolean shouldBeVisibleH(VolumeRow row, boolean isActive) { + if (row.stream == AudioSystem.STREAM_ACCESSIBILITY) { + return mShowA11yStream; + } return mExpanded && row.view.getVisibility() == View.VISIBLE || (mExpanded && (row.important || isActive)) || !mExpanded && isActive; @@ -644,7 +663,8 @@ public class VolumeDialog implements TunerService.Tunable { if (!ss.dynamic) continue; mDynamic.put(stream, true); if (findRow(stream) == null) { - addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true); + addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true, + true); } } @@ -1009,6 +1029,14 @@ public class VolumeDialog implements TunerService.Tunable { public void onShowSafetyWarning(int flags) { showSafetyWarningH(flags); } + + @Override + public void onAccessibilityModeChanged(Boolean showA11yStream) { + boolean show = showA11yStream == null ? false : showA11yStream; + mShowA11yStream = show; + updateRowsH(getActiveRow()); + + } }; private final ZenModePanel.Callback mZenPanelCallback = new ZenModePanel.Callback() { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java index 0e5ff43392d3..276b7c376867 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java @@ -75,13 +75,13 @@ public class VolumeDialogController { STREAMS.put(AudioSystem.STREAM_BLUETOOTH_SCO, R.string.stream_bluetooth_sco); STREAMS.put(AudioSystem.STREAM_DTMF, R.string.stream_dtmf); STREAMS.put(AudioSystem.STREAM_MUSIC, R.string.stream_music); + STREAMS.put(AudioSystem.STREAM_ACCESSIBILITY, R.string.stream_accessibility); STREAMS.put(AudioSystem.STREAM_NOTIFICATION, R.string.stream_notification); STREAMS.put(AudioSystem.STREAM_RING, R.string.stream_ring); STREAMS.put(AudioSystem.STREAM_SYSTEM, R.string.stream_system); STREAMS.put(AudioSystem.STREAM_SYSTEM_ENFORCED, R.string.stream_system_enforced); STREAMS.put(AudioSystem.STREAM_TTS, R.string.stream_tts); STREAMS.put(AudioSystem.STREAM_VOICE_CALL, R.string.stream_voice_call); - STREAMS.put(AudioSystem.STREAM_ACCESSIBILITY, R.string.stream_accessibility); } private final HandlerThread mWorkerThread; @@ -98,6 +98,7 @@ public class VolumeDialogController { private final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks(); private final Vibrator mVibrator; private final boolean mHasVibrator; + private boolean mShowA11yStream; private boolean mDestroyed; private VolumePolicy mVolumePolicy; @@ -204,6 +205,7 @@ public class VolumeDialogController { pw.print(" mHasVibrator: "); pw.println(mHasVibrator); pw.print(" mRemoteStreams: "); pw.println(mMediaSessionsCallbacksW.mRemoteStreams .values()); + pw.print(" mShowA11yStream: "); pw.println(mShowA11yStream); pw.println(); mMediaSessions.dump(pw); } @@ -301,6 +303,10 @@ public class VolumeDialogController { mCallbacks.onShowSafetyWarning(flags); } + private void onAccessibilityModeChanged(Boolean showA11yStream) { + mCallbacks.onAccessibilityModeChanged(showA11yStream); + } + private boolean checkRoutedToBluetoothW(int stream) { boolean changed = false; if (stream == AudioManager.STREAM_MUSIC) { @@ -570,13 +576,16 @@ public class VolumeDialogController { switch (mode) { case VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME: // "legacy" mode + mShowA11yStream = false; break; case VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME: + mShowA11yStream = true; break; default: Log.e(TAG, "Invalid accessibility mode " + mode); break; } + mWorker.obtainMessage(W.ACCESSIBILITY_MODE_CHANGED, mShowA11yStream).sendToTarget(); } } @@ -595,6 +604,7 @@ public class VolumeDialogController { private static final int NOTIFY_VISIBLE = 12; private static final int USER_ACTIVITY = 13; private static final int SHOW_SAFETY_WARNING = 14; + private static final int ACCESSIBILITY_MODE_CHANGED = 15; W(Looper looper) { super(looper); @@ -617,6 +627,7 @@ public class VolumeDialogController { case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0); break; case USER_ACTIVITY: onUserActivityW(); break; case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break; + case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj); } } } @@ -743,6 +754,19 @@ public class VolumeDialogController { }); } } + + @Override + public void onAccessibilityModeChanged(Boolean showA11yStream) { + boolean show = showA11yStream == null ? false : showA11yStream; + for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { + entry.getValue().post(new Runnable() { + @Override + public void run() { + entry.getKey().onAccessibilityModeChanged(show); + } + }); + } + } } @@ -1004,6 +1028,7 @@ public class VolumeDialogController { .append('[').append(ss.levelMin).append("..").append(ss.levelMax) .append(']'); if (ss.muted) sb.append(" [MUTED]"); + if (ss.dynamic) sb.append(" [DYNAMIC]"); } sep(sb, indent); sb.append("ringerModeExternal:").append(ringerModeExternal); sep(sb, indent); sb.append("ringerModeInternal:").append(ringerModeInternal); @@ -1037,6 +1062,7 @@ public class VolumeDialogController { void onShowSilentHint(); void onScreenOff(); void onShowSafetyWarning(int flags); + void onAccessibilityModeChanged(Boolean showA11yStream); } public interface UserActivityListener { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java index c65f7150de0e..cac0806e0f15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java @@ -91,7 +91,6 @@ public class NotificationGutsTest { // mMockStatusBarNotification with a test channel. mNotificationChannel = new NotificationChannel( TEST_CHANNEL, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW); - when(mMockStatusBarNotification.getNotificationChannel()).thenReturn(mNotificationChannel); when(mMockStatusBarNotification.getPackageName()).thenReturn(TEST_PACKAGE_NAME); } @@ -100,7 +99,7 @@ public class NotificationGutsTest { public void testBindNotification_SetsTextApplicationName() throws Exception { when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name"); mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, null, null); + mMockStatusBarNotification, mNotificationChannel, null, null, null); final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.pkgname); assertTrue(textView.getText().toString().contains("App Name")); } @@ -109,7 +108,7 @@ public class NotificationGutsTest { @UiThreadTest public void testBindNotification_SetsTextChannelName() throws Exception { mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, null, null); + mMockStatusBarNotification, mNotificationChannel, null, null, null); final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.channel_name); assertEquals(TEST_CHANNEL_NAME, textView.getText()); } @@ -119,8 +118,8 @@ public class NotificationGutsTest { public void testBindNotification_SetsOnClickListenerForSettings() throws Exception { final CountDownLatch latch = new CountDownLatch(1); mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, (View v, int appUid) -> { latch.countDown(); }, - null, null); + mMockStatusBarNotification, mNotificationChannel, + (View v, int appUid) -> { latch.countDown(); }, null, null); final TextView settingsButton = (TextView) mNotificationGuts.findViewById(R.id.more_settings); @@ -134,7 +133,7 @@ public class NotificationGutsTest { public void testBindNotification_SetsOnClickListenerForDone() throws Exception { final CountDownLatch latch = new CountDownLatch(1); mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, + mMockStatusBarNotification, mNotificationChannel, null, (View v) -> { latch.countDown(); }, null); @@ -148,7 +147,7 @@ public class NotificationGutsTest { @UiThreadTest public void testHasImportanceChanged_DefaultsToFalse() throws Exception { mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, null, null); + mMockStatusBarNotification, mNotificationChannel, null, null, null); assertFalse(mNotificationGuts.hasImportanceChanged()); } @@ -157,7 +156,7 @@ public class NotificationGutsTest { public void testHasImportanceChanged_ReturnsTrueAfterButtonChecked() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, null, null); + mMockStatusBarNotification, mNotificationChannel, null, null, null); // Find the high button and check it. RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance); highButton.setChecked(true); @@ -169,7 +168,7 @@ public class NotificationGutsTest { public void testImportanceButtonCheckedBasedOnInitialImportance() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_HIGH); mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, null, null); + mMockStatusBarNotification, mNotificationChannel, null, null, null); RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance); assertTrue(highButton.isChecked()); @@ -179,7 +178,7 @@ public class NotificationGutsTest { @UiThreadTest public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception { mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, null, null); + mMockStatusBarNotification, mNotificationChannel, null, null, null); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), anyInt(), any()); } @@ -189,7 +188,7 @@ public class NotificationGutsTest { public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, null, null); + mMockStatusBarNotification, mNotificationChannel, null, null, null); RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance); highButton.setChecked(true); @@ -201,7 +200,7 @@ public class NotificationGutsTest { @UiThreadTest public void testCloseControls_DoesNotUpdateNotificationChannelIfUnchanged() throws Exception { mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, null, null); + mMockStatusBarNotification, mNotificationChannel, null, null, null); mNotificationGuts.closeControls(-1, -1, true); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( @@ -213,7 +212,7 @@ public class NotificationGutsTest { public void testCloseControls_DoesNotUpdateNotificationChannelIfUnspecified() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_UNSPECIFIED); mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, null, null); + mMockStatusBarNotification, mNotificationChannel, null, null, null); mNotificationGuts.closeControls(-1, -1, true); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( @@ -225,7 +224,7 @@ public class NotificationGutsTest { public void testCloseControls_CallsUpdateNotificationChannelIfChanged() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, null, null); + mMockStatusBarNotification, mNotificationChannel, null, null, null); RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance); highButton.setChecked(true); @@ -240,7 +239,7 @@ public class NotificationGutsTest { public void testCloseControls_DoesNotUpdateNotificationChannelIfSaveFalse() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, null, null); + mMockStatusBarNotification, mNotificationChannel, null, null, null); RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance); highButton.setChecked(true); @@ -254,7 +253,7 @@ public class NotificationGutsTest { public void testEnabledSwitchOnByDefault() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, null, null); + mMockStatusBarNotification, mNotificationChannel, null, null, null); Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch); assertTrue(enabledSwitch.isChecked()); @@ -265,7 +264,7 @@ public class NotificationGutsTest { public void testEnabledSwitchVisibleByDefault() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, null, null); + mMockStatusBarNotification, mNotificationChannel, null, null, null); Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch); assertEquals(View.VISIBLE, enabledSwitch.getVisibility()); @@ -276,7 +275,8 @@ public class NotificationGutsTest { public void testEnabledSwitchInvisibleIfNonBlockable() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, null, Collections.singleton(TEST_PACKAGE_NAME)); + mMockStatusBarNotification, mNotificationChannel, null, null, + Collections.singleton(TEST_PACKAGE_NAME)); Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch); assertEquals(View.INVISIBLE, enabledSwitch.getVisibility()); @@ -287,7 +287,8 @@ public class NotificationGutsTest { public void testEnabledSwitchChangedCallsUpdateNotificationChannel() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, null, Collections.singleton(TEST_PACKAGE_NAME)); + mMockStatusBarNotification, mNotificationChannel, null, null, + Collections.singleton(TEST_PACKAGE_NAME)); Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch); enabledSwitch.setChecked(false); @@ -301,7 +302,7 @@ public class NotificationGutsTest { public void testEnabledSwitchOverridesOtherButtons() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, null, null, null); + mMockStatusBarNotification, mNotificationChannel, null, null, null); Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch); RadioButton lowButton = (RadioButton) mNotificationGuts.findViewById(R.id.low_importance); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java index 7b56ea39137d..c969cc2d2779 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.policy; import android.os.HandlerThread; import android.support.test.runner.AndroidJUnit4; import android.telephony.SubscriptionInfo; -import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import com.android.systemui.R; import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index 23c635c3e93f..6aa021e75239 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.policy; import android.content.Intent; import android.net.ConnectivityManager; import android.net.NetworkCapabilities; +import android.net.NetworkScoreManager; import android.net.wifi.WifiManager; import android.os.Looper; import android.telephony.PhoneStateListener; @@ -79,6 +80,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { protected Config mConfig; protected CallbackHandler mCallbackHandler; protected SubscriptionDefaults mMockSubDefaults; + protected NetworkScoreManager mMockNetworkScoreManager; protected int mSubId; @@ -105,6 +107,8 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mMockCm = mock(ConnectivityManager.class); mMockSubDefaults = mock(SubscriptionDefaults.class); mNetCapabilities = new NetworkCapabilities(); + mMockNetworkScoreManager = mock(NetworkScoreManager.class); + when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true); when(mMockCm.getDefaultNetworkCapabilitiesForUser(0)).thenReturn( new NetworkCapabilities[] { mNetCapabilities }); @@ -115,7 +119,8 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mConfig = new Config(); mConfig.hspaDataDistinguishable = true; mCallbackHandler = mock(CallbackHandler.class); - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager, + mMockTm, mMockWm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class)); @@ -157,8 +162,8 @@ public class NetworkControllerBaseTest extends SysuiTestCase { protected NetworkControllerImpl setUpNoMobileData() { when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false); NetworkControllerImpl networkControllerNoMobile - = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm, - mConfig, mContext.getMainLooper(), mCallbackHandler, + = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager, mMockTm, + mMockWm, mMockSm, mConfig, mContext.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java index 1f7ec1aecd16..1ec041872d19 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java @@ -100,7 +100,8 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { public void test4gDataIcon() { // Switch to showing 4g icon and re-initialize the NetworkController. mConfig.show4gForLte = true; - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager, + mMockTm, mMockWm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java index 1a61d80e24f1..00e5926415a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java @@ -53,7 +53,8 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { // Turn off mobile network support. Mockito.when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false); // Create a new NetworkController as this is currently handled in constructor. - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager, + mMockTm, mMockWm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class)); @@ -107,7 +108,8 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { // Turn off mobile network support. Mockito.when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false); // Create a new NetworkController as this is currently handled in constructor. - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager, + mMockTm, mMockWm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java index ed32f65179c6..06a512297282 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java @@ -1,24 +1,50 @@ package com.android.systemui.statusbar.policy; import android.content.Intent; +import android.graphics.drawable.Drawable; import android.net.NetworkCapabilities; import android.net.NetworkInfo; +import android.net.NetworkKey; +import android.net.RssiCurve; +import android.net.ScoredNetwork; +import android.net.WifiKey; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; +import android.net.wifi.WifiNetworkScoreCache; +import android.os.Bundle; +import android.provider.Settings; +import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; +import com.android.settingslib.Utils; import com.android.systemui.statusbar.policy.NetworkController.IconState; +import com.android.systemui.utils.FakeSettingsProvider.SettingOverrider; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Matchers; import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; @SmallTest @RunWith(AndroidJUnit4.class) @@ -27,6 +53,15 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { private static final int MIN_RSSI = -100; private static final int MAX_RSSI = -55; + private static final int LATCH_TIMEOUT = 2000; + private static final String TEST_SSID = "\"Test SSID\""; + private static final String TEST_BSSID = "00:00:00:00:00:00"; + + private final List<NetworkKey> mRequestedKeys = new ArrayList<>(); + private CountDownLatch mRequestScoresLatch; + + private SettingOverrider mSettingsOverrider; + @Test public void testWifiIcon() { String testSsid = "Test SSID"; @@ -47,6 +82,77 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { } @Test + public void testBadgedWifiIcon() throws Exception { + int testLevel = 1; + RssiCurve mockBadgeCurve = mock(RssiCurve.class); + Bundle attr = new Bundle(); + attr.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve); + ScoredNetwork score = + new ScoredNetwork( + new NetworkKey(new WifiKey(TEST_SSID, TEST_BSSID)), + null, + false /* meteredHint */, + attr); + + // Enable scoring + mSettingsOverrider = mContext.getSettingsProvider().acquireOverridesBuilder(this) + .addSetting("global", Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, "1") + .build(); + + setupNetworkScoreManager(); + mRequestScoresLatch = new CountDownLatch(1); + setWifiEnabled(true); + setWifiState(true, TEST_SSID, TEST_BSSID); + mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS); + + when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) ScoredNetwork.BADGING_SD); + + ArgumentCaptor<WifiNetworkScoreCache> scoreCacheCaptor = + ArgumentCaptor.forClass(WifiNetworkScoreCache.class); + verify(mMockNetworkScoreManager).registerNetworkScoreCache( + anyInt(), + scoreCacheCaptor.capture(), + Matchers.anyInt()); + scoreCacheCaptor.getValue().updateScores(Arrays.asList(score)); + + setWifiLevel(testLevel); + NetworkController.SignalCallback mockCallback = + mock(NetworkController.SignalCallback.class); + mNetworkController.addCallback(mockCallback); + + ArgumentCaptor<IconState> iconState = ArgumentCaptor.forClass(IconState.class); + Mockito.verify(mockCallback).setWifiIndicators( + anyBoolean(), iconState.capture(), any(), anyBoolean(), anyBoolean(), any()); + + assertEquals("Badged Wifi Resource is set", + Utils.WIFI_PIE_FOR_BADGING[testLevel], + iconState.getValue().icon); + assertEquals("SD Badge is set", + Utils.getWifiBadgeResource(ScoredNetwork.BADGING_SD), + iconState.getValue().iconOverlay); + + mSettingsOverrider.release(); + } + + private void setupNetworkScoreManager() { + // Capture requested keys and count down latch if present + doAnswer( + new Answer<Boolean>() { + @Override + public Boolean answer(InvocationOnMock input) { + if (mRequestScoresLatch != null) { + mRequestScoresLatch.countDown(); + } + NetworkKey[] keys = (NetworkKey[]) input.getArguments()[0]; + for (NetworkKey key : keys) { + mRequestedKeys.add(key); + } + return true; + } + }).when(mMockNetworkScoreManager).requestScores(Matchers.<NetworkKey[]>any()); + } + + @Test public void testQsWifiIcon() { String testSsid = "Test SSID"; @@ -97,7 +203,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { @Test public void testRoamingIconDuringWifi() { // Setup normal connection - String testSsid = "Test SSID"; + String testSsid = "\"Test SSID\""; int testLevel = 2; setWifiEnabled(true); setWifiState(true, testSsid); @@ -137,12 +243,19 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { } protected void setWifiState(boolean connected, String ssid) { + setWifiState(connected, ssid, null); + } + + protected void setWifiState(boolean connected, String ssid, String bssid) { Intent i = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); NetworkInfo networkInfo = Mockito.mock(NetworkInfo.class); Mockito.when(networkInfo.isConnected()).thenReturn(connected); WifiInfo wifiInfo = Mockito.mock(WifiInfo.class); Mockito.when(wifiInfo.getSSID()).thenReturn(ssid); + if (bssid != null) { + Mockito.when(wifiInfo.getBSSID()).thenReturn(bssid); + } i.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo); i.putExtra(WifiManager.EXTRA_WIFI_INFO, wifiInfo); @@ -166,8 +279,8 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { ArgumentCaptor<String> descArg = ArgumentCaptor.forClass(String.class); Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators( - enabledArg.capture(), any(), iconArg.capture(), anyBoolean(), anyBoolean(), - descArg.capture()); + enabledArg.capture(), any(), iconArg.capture(), anyBoolean(), + anyBoolean(), descArg.capture()); IconState iconState = iconArg.getValue(); assertEquals("WiFi enabled, in quick settings", enabled, (boolean) enabledArg.getValue()); assertEquals("WiFi connected, in quick settings", connected, iconState.visible); @@ -179,7 +292,8 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class); Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators( - anyBoolean(), iconArg.capture(), any(), anyBoolean(), anyBoolean(), any()); + anyBoolean(), iconArg.capture(), any(), anyBoolean(), anyBoolean(), + any()); IconState iconState = iconArg.getValue(); assertEquals("WiFi visible, in status bar", visible, iconState.visible); assertEquals("WiFi signal, in status bar", icon, iconState.icon); diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 88bc99fc14d7..341438d2c0f9 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -3311,6 +3311,26 @@ message MetricsEvent { // OS: 8.0 MANAGE_EXTERNAL_SOURCES = 808; + // ACTION: Logged when terms activity finishes. + // TIME: Indicates time taken by terms activity to finish in MS. + PROVISIONING_TERMS_ACTIVITY_TIME_MS = 809; + + // Indicates number of terms displayed on the terms screen. + PROVISIONING_TERMS_COUNT = 810; + + // Indicates number of terms read on the terms screen. + PROVISIONING_TERMS_READ = 811; + + // Logs that the user has edited the picture-in-picture settings. + // CATEGORY: SETTINGS + SETTINGS_MANAGE_PICTURE_IN_PICTURE = 812; + + // ACTION: Allow "Enable picture-in-picture on hide" for an app + APP_PICTURE_IN_PICTURE_ON_HIDE_ALLOW = 813; + + // ACTION: Deny "Enable picture-in-picture on hide" for an app + APP_PICTURE_IN_PICTURE_ON_HIDE_DENY = 814; + // ---- End O Constants, all O constants go above this line ---- // Add new aosp constants above this line. diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 7e825860ead3..88c05b55116b 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -34,13 +34,15 @@ import android.app.backup.BackupProgress; import android.app.backup.BackupTransport; import android.app.backup.FullBackup; import android.app.backup.FullBackupDataOutput; -import android.app.backup.IBackupObserver; -import android.app.backup.RestoreDescription; -import android.app.backup.RestoreSet; import android.app.backup.IBackupManager; +import android.app.backup.IBackupObserver; import android.app.backup.IFullBackupRestoreObserver; import android.app.backup.IRestoreObserver; import android.app.backup.IRestoreSession; +import android.app.backup.ISelectBackupTransportCallback; +import android.app.backup.RestoreDescription; +import android.app.backup.RestoreSet; +import android.app.backup.SelectBackupTransportCallback; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -55,16 +57,15 @@ import android.content.pm.IPackageDeleteObserver; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.content.pm.Signature; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.Signature; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Environment; +import android.os.Environment.UserEnvironment; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -79,15 +80,12 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.WorkSource; -import android.os.Environment.UserEnvironment; import android.os.storage.IStorageManager; import android.os.storage.StorageManager; import android.provider.Settings; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; -import android.util.ArrayMap; -import android.util.ArraySet; import android.util.AtomicFile; import android.util.EventLog; import android.util.Log; @@ -105,6 +103,8 @@ import com.android.server.SystemConfig; import com.android.server.SystemService; import com.android.server.backup.PackageManagerBackupAgent.Metadata; +import libcore.io.IoUtils; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; @@ -139,7 +139,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Random; @@ -166,8 +165,6 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; -import libcore.io.IoUtils; - public class BackupManagerService { private static final String TAG = "BackupManagerService"; @@ -271,6 +268,8 @@ public class BackupManagerService { private IStorageManager mStorageManager; IBackupManager mBackupManagerBinder; + private final TransportManager mTransportManager; + boolean mEnabled; // access to this is synchronized on 'this' boolean mProvisioned; boolean mAutoRestore; @@ -322,16 +321,6 @@ public class BackupManagerService { final Object mClearDataLock = new Object(); volatile boolean mClearingData; - // Transport bookkeeping - final ArraySet<ComponentName> mTransportWhitelist; - final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST); - final ArrayMap<String,String> mTransportNames - = new ArrayMap<String,String>(); // component name -> registration name - final ArrayMap<String,IBackupTransport> mTransports - = new ArrayMap<String,IBackupTransport>(); // registration name -> binder - final ArrayMap<String,TransportConnection> mTransportConnections - = new ArrayMap<String,TransportConnection>(); - String mCurrentTransport; ActiveRestoreSession mActiveRestoreSession; // Watch the device provisioning operation during setup @@ -756,7 +745,7 @@ public class BackupManagerService { { mLastBackupPass = System.currentTimeMillis(); - IBackupTransport transport = getTransport(mCurrentTransport); + IBackupTransport transport = mTransportManager.getCurrentTransportBinder(); if (transport == null) { Slog.v(TAG, "Backup requested but no transport available"); synchronized (mQueueLock) { @@ -1202,32 +1191,19 @@ public class BackupManagerService { // Set up our transport options and initialize the default transport // TODO: Don't create transports that we don't need to? SystemConfig systemConfig = SystemConfig.getInstance(); - mTransportWhitelist = systemConfig.getBackupTransportWhitelist(); + Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist(); String transport = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT); if (TextUtils.isEmpty(transport)) { transport = null; } - mCurrentTransport = transport; - if (DEBUG) Slog.v(TAG, "Starting with transport " + mCurrentTransport); + String currentTransport = transport; + if (DEBUG) Slog.v(TAG, "Starting with transport " + currentTransport); - // Find all transport hosts and bind to their services - // TODO: http://b/22388012 - List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser( - mTransportServiceIntent, 0, UserHandle.USER_SYSTEM); - if (DEBUG) { - Slog.v(TAG, "Found transports: " + ((hosts == null) ? "null" : hosts.size())); - } - if (hosts != null) { - for (int i = 0; i < hosts.size(); i++) { - final ServiceInfo transportService = hosts.get(i).serviceInfo; - if (MORE_DEBUG) { - Slog.v(TAG, " " + transportService.packageName + "/" + transportService.name); - } - tryBindTransport(transportService); - } - } + mTransportManager = new TransportManager(context, transportWhitelist, currentTransport, + mTransportBoundListener); + mTransportManager.registerAllTransports(); // Now that we know about valid backup participants, parse any // leftover journal files into the pending backup set @@ -1751,7 +1727,7 @@ public class BackupManagerService { mBackupHandler.removeMessages(MSG_RETRY_INIT); try { - IBackupTransport transport = getTransport(transportName); + IBackupTransport transport = mTransportManager.getTransportBinder(transportName); if (transport != null) { String transportDirName = transport.transportDirName(); File stateDir = new File(mBaseStateDir, transportDirName); @@ -1829,49 +1805,39 @@ public class BackupManagerService { } } - // Add a transport to our set of available backends. If 'transport' is null, this - // is an unregistration, and the transport's entry is removed from our bookkeeping. - private void registerTransport(String name, String component, IBackupTransport transport) { - synchronized (mTransports) { - if (DEBUG) Slog.v(TAG, "Registering transport " - + component + "::" + name + " = " + transport); - if (transport != null) { - mTransports.put(name, transport); - mTransportNames.put(component, name); - } else { - mTransports.remove(mTransportNames.get(component)); - mTransportNames.remove(component); - // Nothing further to do in the unregistration case - return; - } - } - - // If the init sentinel file exists, we need to be sure to perform the init - // as soon as practical. We also create the state directory at registration - // time to ensure it's present from the outset. - try { - String transportName = transport.transportDirName(); - File stateDir = new File(mBaseStateDir, transportName); - stateDir.mkdirs(); + 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(); - File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME); - if (initSentinel.exists()) { - synchronized (mQueueLock) { - mPendingInits.add(name); + File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME); + if (initSentinel.exists()) { + synchronized (mQueueLock) { + mPendingInits.add(name); - // TODO: pick a better starting time than now + 1 minute - long delay = 1000 * 60; // one minute, in milliseconds - mAlarmManager.set(AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + delay, mRunInitIntent); + // TODO: pick a better starting time than now + 1 minute + long delay = 1000 * 60; // one minute, in milliseconds + mAlarmManager.set(AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + delay, mRunInitIntent); + } } + return true; + } catch (Exception e) { + // the transport threw when asked its file naming prefs; declare it invalid + Slog.w(TAG, "Failed to regiser transport: " + name); + return false; } - } catch (Exception e) { - // the transport threw when asked its file naming prefs; declare it invalid - Slog.e(TAG, "Unable to register transport as " + name); - mTransportNames.remove(component); - mTransports.remove(name); } - } + }; // ----- Track installation/removal of packages ----- BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -1899,75 +1865,17 @@ public class BackupManagerService { // At package-changed we only care about looking at new transport states if (changed) { - try { - String[] components = - intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); + String[] components = + intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); - if (MORE_DEBUG) { - Slog.i(TAG, "Package " + pkgName + " changed; rechecking"); - for (int i = 0; i < components.length; i++) { - Slog.i(TAG, " * " + components[i]); - } - } - - // In general we need to try to bind any time we see a component enable - // state change, because that change may have made a transport available. - // However, because we currently only support a single transport component - // per package, we can skip the bind attempt if the change (a) affects a - // package known to host a transport, but (b) does not affect the known - // transport component itself. - // - // In addition, if the change *is* to a known transport component, we need - // to unbind it before retrying the binding. - boolean tryBind = true; - synchronized (mTransports) { - TransportConnection conn = mTransportConnections.get(pkgName); - if (conn != null) { - // We have a bound transport in this package; do we need to rebind it? - final ServiceInfo svc = conn.mTransport; - ComponentName svcName = - new ComponentName(svc.packageName, svc.name); - if (svc.packageName.equals(pkgName)) { - final String className = svcName.getClassName(); - if (MORE_DEBUG) { - Slog.i(TAG, "Checking need to rebind " + className); - } - // See whether it's the transport component within this package - boolean isTransport = false; - for (int i = 0; i < components.length; i++) { - if (className.equals(components[i])) { - // Okay, it's an existing transport component. - final String flatName = svcName.flattenToShortString(); - mContext.unbindService(conn); - mTransportConnections.remove(pkgName); - mTransports.remove(mTransportNames.get(flatName)); - mTransportNames.remove(flatName); - isTransport = true; - break; - } - } - if (!isTransport) { - // A non-transport component within a package that is hosting - // a bound transport - tryBind = false; - } - } - } - } - // and now (re)bind as appropriate - if (tryBind) { - if (MORE_DEBUG) { - Slog.i(TAG, "Yes, need to recheck binding"); - } - PackageInfo app = mPackageManager.getPackageInfo(pkgName, 0); - checkForTransportAndBind(app); - } - } catch (NameNotFoundException e) { - // Nope, can't find it - just ignore - if (MORE_DEBUG) { - Slog.w(TAG, "Can't find changed package " + pkgName); + if (MORE_DEBUG) { + Slog.i(TAG, "Package " + pkgName + " changed; rechecking"); + for (int i = 0; i < components.length; i++) { + Slog.i(TAG, " * " + components[i]); } } + + mTransportManager.onPackageChanged(pkgName, components); return; // nothing more to do in the PACKAGE_CHANGED case } @@ -2015,19 +1923,7 @@ public class BackupManagerService { writeFullBackupScheduleAsync(); } - // Transport maintenance: rebind to known existing transports that have - // just been updated; and bind to any newly-installed transport services. - synchronized (mTransports) { - final TransportConnection conn = mTransportConnections.get(packageName); - if (conn != null) { - if (MORE_DEBUG) { - Slog.i(TAG, "Transport package changed; rebinding"); - } - bindTransport(conn.mTransport); - } else { - checkForTransportAndBind(app); - } - } + mTransportManager.onPackageAdded(packageName); } catch (NameNotFoundException e) { // doesn't really exist; ignore it @@ -2051,107 +1947,13 @@ public class BackupManagerService { removePackageParticipantsLocked(pkgList, uid); } } + for (String pkgName : pkgList) { + mTransportManager.onPackageRemoved(pkgName); + } } } }; - // ----- Track connection to transports service ----- - class TransportConnection implements ServiceConnection { - ServiceInfo mTransport; - - public TransportConnection(ServiceInfo transport) { - mTransport = transport; - } - - @Override - public void onServiceConnected(ComponentName component, IBinder service) { - if (DEBUG) Slog.v(TAG, "Connected to transport " + component); - final String name = component.flattenToShortString(); - try { - IBackupTransport transport = IBackupTransport.Stub.asInterface(service); - registerTransport(transport.name(), name, transport); - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, name, 1); - } catch (Exception e) { - Slog.e(TAG, "Unable to register transport " + component - + ": " + e.getMessage()); - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, name, 0); - } - } - - @Override - public void onServiceDisconnected(ComponentName component) { - if (DEBUG) Slog.v(TAG, "Disconnected from transport " + component); - final String name = component.flattenToShortString(); - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, name, 0); - registerTransport(null, name, null); - } - }; - - // Check whether the given package hosts a transport, and bind if so - void checkForTransportAndBind(PackageInfo pkgInfo) { - Intent intent = new Intent(mTransportServiceIntent) - .setPackage(pkgInfo.packageName); - // TODO: http://b/22388012 - List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser( - intent, 0, UserHandle.USER_SYSTEM); - if (hosts != null) { - final int N = hosts.size(); - for (int i = 0; i < N; i++) { - final ServiceInfo info = hosts.get(i).serviceInfo; - tryBindTransport(info); - } - } - } - - // Verify that the service exists and is hosted by a privileged app, then proceed to bind - boolean tryBindTransport(ServiceInfo info) { - try { - PackageInfo packInfo = mPackageManager.getPackageInfo(info.packageName, 0); - if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) - != 0) { - return bindTransport(info); - } else { - Slog.w(TAG, "Transport package " + info.packageName + " not privileged"); - } - } catch (NameNotFoundException e) { - Slog.w(TAG, "Problem resolving transport package " + info.packageName); - } - return false; - } - - // Actually bind; presumes that we have already validated the transport service - boolean bindTransport(ServiceInfo transport) { - ComponentName svcName = new ComponentName(transport.packageName, transport.name); - if (!mTransportWhitelist.contains(svcName)) { - Slog.w(TAG, "Proposed transport " + svcName + " not whitelisted; ignoring"); - return false; - } - - if (MORE_DEBUG) { - Slog.i(TAG, "Binding to transport host " + svcName); - } - Intent intent = new Intent(mTransportServiceIntent); - intent.setComponent(svcName); - - TransportConnection connection; - synchronized (mTransports) { - connection = mTransportConnections.get(transport.packageName); - if (null == connection) { - connection = new TransportConnection(transport); - mTransportConnections.put(transport.packageName, connection); - } else { - // This is a rebind due to package upgrade. The service won't be - // automatically relaunched for us until we explicitly rebind, but - // we need to unbind the now-orphaned original connection. - mContext.unbindService(connection); - } - } - // TODO: http://b/22388012 - return mContext.bindServiceAsUser(intent, - connection, Context.BIND_AUTO_CREATE, - UserHandle.SYSTEM); - } - // Add the backup agents in the given packages to our set of known backup participants. // If 'packageNames' is null, adds all backup agents in the whole system. void addPackageParticipantsLocked(String[] packageNames) { @@ -2352,34 +2154,12 @@ public class BackupManagerService { } } - // Return the given transport - private IBackupTransport getTransport(String transportName) { - synchronized (mTransports) { - IBackupTransport transport = mTransports.get(transportName); - if (transport == null) { - Slog.w(TAG, "Requested unavailable transport: " + transportName); - } - return transport; - } - } - // What name is this transport registered under...? private String getTransportName(IBackupTransport transport) { if (MORE_DEBUG) { Slog.v(TAG, "Searching for transport name of " + transport); } - synchronized (mTransports) { - final int N = mTransports.size(); - for (int i = 0; i < N; i++) { - if (mTransports.valueAt(i).equals(transport)) { - if (MORE_DEBUG) { - Slog.v(TAG, " Name found: " + mTransports.keyAt(i)); - } - return mTransports.keyAt(i); - } - } - } - return null; + return mTransportManager.getTransportName(transport); } // fire off a backup agent, blocking until it attaches or times out @@ -2505,7 +2285,7 @@ public class BackupManagerService { throw new IllegalArgumentException("No packages are provided for backup"); } - IBackupTransport transport = getTransport(mCurrentTransport); + IBackupTransport transport = mTransportManager.getCurrentTransportBinder(); if (transport == null) { sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED); return BackupManager.ERROR_TRANSPORT_ABORTED; @@ -3025,7 +2805,7 @@ public class BackupManagerService { if (MORE_DEBUG) Slog.d(TAG, "Server requires init; rerunning"); addBackupTrace("init required; rerunning"); try { - final String name = getTransportName(mTransport); + final String name = mTransportManager.getTransportName(mTransport); if (name != null) { mPendingInits.add(name); } else { @@ -4503,7 +4283,7 @@ public class BackupManagerService { return; } - IBackupTransport transport = getTransport(mCurrentTransport); + IBackupTransport transport = mTransportManager.getCurrentTransportBinder(); if (transport == null) { Slog.w(TAG, "Transport not present; full data backup not performed"); backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED; @@ -5119,7 +4899,7 @@ public class BackupManagerService { headBusy = false; - if (!fullBackupAllowable(getTransport(mCurrentTransport))) { + if (!fullBackupAllowable(mTransportManager.getCurrentTransportBinder())) { if (MORE_DEBUG) { Slog.i(TAG, "Preconditions not met; not running full backup"); } @@ -9115,7 +8895,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF public void run() { try { for (String transportName : mQueue) { - IBackupTransport transport = getTransport(transportName); + IBackupTransport transport = + mTransportManager.getTransportBinder(transportName); if (transport == null) { Slog.e(TAG, "Requested init for " + transportName + " but not found"); continue; @@ -9312,7 +9093,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF if (MORE_DEBUG) Slog.v(TAG, "Found the app - running clear process"); mBackupHandler.removeMessages(MSG_RETRY_CLEAR); synchronized (mQueueLock) { - final IBackupTransport transport = getTransport(transportName); + final IBackupTransport transport = + mTransportManager.getTransportBinder(transportName); if (transport == null) { // transport is currently unavailable -- make sure to retry Message msg = mBackupHandler.obtainMessage(MSG_RETRY_CLEAR, @@ -9450,7 +9232,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF throw new IllegalStateException("Restore supported only for the device owner"); } - if (!fullBackupAllowable(getTransport(mCurrentTransport))) { + if (!fullBackupAllowable(mTransportManager.getCurrentTransportBinder())) { Slog.i(TAG, "Full backup not currently possible -- key/value backup not yet run?"); } else { if (DEBUG) { @@ -9718,10 +9500,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF if (wasEnabled && mProvisioned) { // NOTE: we currently flush every registered transport, not just // the currently-active one. - HashSet<String> allTransports; - synchronized (mTransports) { - allTransports = new HashSet<String>(mTransports.keySet()); - } + String[] allTransports = mTransportManager.getBoundTransportNames(); // build the set of transports for which we are posting an init for (String transport : allTransports) { recordInitPendingLocked(true, transport); @@ -9774,36 +9553,27 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF public String getCurrentTransport() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "getCurrentTransport"); - if (MORE_DEBUG) Slog.v(TAG, "... getCurrentTransport() returning " + mCurrentTransport); - return mCurrentTransport; + String currentTransport = mTransportManager.getCurrentTransportName(); + if (MORE_DEBUG) Slog.v(TAG, "... getCurrentTransport() returning " + currentTransport); + return currentTransport; } // Report all known, available backup transports public String[] listAllTransports() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransports"); - String[] list = null; - ArrayList<String> known = new ArrayList<String>(); - for (Map.Entry<String, IBackupTransport> entry : mTransports.entrySet()) { - if (entry.getValue() != null) { - known.add(entry.getKey()); - } - } + return mTransportManager.getBoundTransportNames(); + } - if (known.size() > 0) { - list = new String[known.size()]; - known.toArray(list); - } - return list; + public ComponentName[] listAllTransportComponents() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, + "listAllTransportComponents"); + return mTransportManager.getAllTransportCompenents(); } public String[] getTransportWhitelist() { // No permission check, intentionally. - String[] whitelist = new String[mTransportWhitelist.size()]; - for (int i = mTransportWhitelist.size() - 1; i >= 0; i--) { - whitelist[i] = mTransportWhitelist.valueAt(i).flattenToShortString(); - } - return whitelist; + return mTransportManager.getTransportWhitelist().toArray(new String[0]); } // Select which transport to use for the next backup operation. @@ -9811,20 +9581,56 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "selectBackupTransport"); - synchronized (mTransports) { - final long oldId = Binder.clearCallingIdentity(); - try { - String prevTransport = mCurrentTransport; - mCurrentTransport = transport; + final long oldId = Binder.clearCallingIdentity(); + try { + String prevTransport = mTransportManager.selectTransport(transport); + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.BACKUP_TRANSPORT, transport); + Slog.v(TAG, "selectBackupTransport() set " + mTransportManager.getCurrentTransportName() + + " returning " + prevTransport); + return prevTransport; + } finally { + Binder.restoreCallingIdentity(oldId); + } + } + + public void selectBackupTransportAsync(final ComponentName transport, + final ISelectBackupTransportCallback listener) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, + "selectBackupTransportAsync"); + + final long oldId = Binder.clearCallingIdentity(); + + Slog.v(TAG, "selectBackupTransportAsync() called with transport " + + transport.flattenToShortString()); + + mTransportManager.ensureTransportReady(transport, new SelectBackupTransportCallback() { + @Override + public void onSuccess(String transportName) { + mTransportManager.selectTransport(transportName); Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.BACKUP_TRANSPORT, transport); - Slog.v(TAG, "selectBackupTransport() set " + mCurrentTransport - + " returning " + prevTransport); - return prevTransport; - } finally { - Binder.restoreCallingIdentity(oldId); + Settings.Secure.BACKUP_TRANSPORT, + mTransportManager.getCurrentTransportName()); + Slog.v(TAG, "Transport successfully selected: " + transport.flattenToShortString()); + try { + listener.onSuccess(transportName); + } catch (RemoteException e) { + // Nothing to do here. + } } - } + + @Override + public void onFailure(int reason) { + Slog.v(TAG, "Failed to select transport: " + transport.flattenToShortString()); + try { + listener.onFailure(reason); + } catch (RemoteException e) { + // Nothing to do here. + } + } + }); + + Binder.restoreCallingIdentity(oldId); } // Supply the configuration Intent for the given transport. If the name is not one @@ -9834,18 +9640,16 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "getConfigurationIntent"); - synchronized (mTransports) { - final IBackupTransport transport = mTransports.get(transportName); - if (transport != null) { - try { - final Intent intent = transport.configurationIntent(); - if (MORE_DEBUG) Slog.d(TAG, "getConfigurationIntent() returning config intent " - + intent); - return intent; - } catch (Exception e) { - /* fall through to return null */ - Slog.e(TAG, "Unable to get configuration intent from transport: " + e.getMessage()); - } + final IBackupTransport transport = mTransportManager.getTransportBinder(transportName); + if (transport != null) { + try { + final Intent intent = transport.configurationIntent(); + if (MORE_DEBUG) Slog.d(TAG, "getConfigurationIntent() returning config intent " + + intent); + return intent; + } catch (Exception e) { + /* fall through to return null */ + Slog.e(TAG, "Unable to get configuration intent from transport: " + e.getMessage()); } } @@ -9861,17 +9665,15 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "getDestinationString"); - synchronized (mTransports) { - final IBackupTransport transport = mTransports.get(transportName); - if (transport != null) { - try { - final String text = transport.currentDestinationString(); - if (MORE_DEBUG) Slog.d(TAG, "getDestinationString() returning " + text); - return text; - } catch (Exception e) { - /* fall through to return null */ - Slog.e(TAG, "Unable to get string from transport: " + e.getMessage()); - } + final IBackupTransport transport = mTransportManager.getTransportBinder(transportName); + if (transport != null) { + try { + final String text = transport.currentDestinationString(); + if (MORE_DEBUG) Slog.d(TAG, "getDestinationString() returning " + text); + return text; + } catch (Exception e) { + /* fall through to return null */ + Slog.e(TAG, "Unable to get string from transport: " + e.getMessage()); } } @@ -9883,18 +9685,16 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "getDataManagementIntent"); - synchronized (mTransports) { - final IBackupTransport transport = mTransports.get(transportName); - if (transport != null) { - try { - final Intent intent = transport.dataManagementIntent(); - if (MORE_DEBUG) Slog.d(TAG, "getDataManagementIntent() returning intent " - + intent); - return intent; - } catch (Exception e) { - /* fall through to return null */ - Slog.e(TAG, "Unable to get management intent from transport: " + e.getMessage()); - } + final IBackupTransport transport = mTransportManager.getTransportBinder(transportName); + if (transport != null) { + try { + final Intent intent = transport.dataManagementIntent(); + if (MORE_DEBUG) Slog.d(TAG, "getDataManagementIntent() returning intent " + + intent); + return intent; + } catch (Exception e) { + /* fall through to return null */ + Slog.e(TAG, "Unable to get management intent from transport: " + e.getMessage()); } } @@ -9907,17 +9707,15 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "getDataManagementLabel"); - synchronized (mTransports) { - final IBackupTransport transport = mTransports.get(transportName); - if (transport != null) { - try { - final String text = transport.dataManagementLabel(); - if (MORE_DEBUG) Slog.d(TAG, "getDataManagementLabel() returning " + text); - return text; - } catch (Exception e) { - /* fall through to return null */ - Slog.e(TAG, "Unable to get management label from transport: " + e.getMessage()); - } + final IBackupTransport transport = mTransportManager.getTransportBinder(transportName); + if (transport != null) { + try { + final String text = transport.dataManagementLabel(); + if (MORE_DEBUG) Slog.d(TAG, "getDataManagementLabel() returning " + text); + return text; + } catch (Exception e) { + /* fall through to return null */ + Slog.e(TAG, "Unable to get management label from transport: " + e.getMessage()); } } @@ -9979,7 +9777,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } // Do we have a transport to fetch data for us? - IBackupTransport transport = getTransport(mCurrentTransport); + IBackupTransport transport = mTransportManager.getCurrentTransportBinder(); if (transport == null) { if (DEBUG) Slog.w(TAG, "No transport"); skip = true; @@ -10033,7 +9831,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF boolean needPermission = true; if (transport == null) { - transport = mCurrentTransport; + transport = mTransportManager.getCurrentTransportName(); if (packageName != null) { PackageInfo app = null; @@ -10127,7 +9925,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF appIsStopped(packageInfo.applicationInfo)) { return false; } - IBackupTransport transport = getTransport(mCurrentTransport); + IBackupTransport transport = mTransportManager.getCurrentTransportBinder(); if (transport != null) { try { return transport.isAppEligibleForBackup(packageInfo, @@ -10156,7 +9954,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF ActiveRestoreSession(String packageName, String transport) { mPackageName = packageName; - mRestoreTransport = getTransport(transport); + mRestoreTransport = mTransportManager.getTransportBinder(transport); } public void markTimedOut() { @@ -10515,7 +10313,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled()); pw.println("Transport whitelist:"); - for (ComponentName transport : mTransportWhitelist) { + for (ComponentName transport : mTransportManager.getTransportWhitelist()) { pw.print(" "); pw.println(transport.flattenToShortString()); } @@ -10524,9 +10322,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF final String[] transports = listAllTransports(); if (transports != null) { for (String t : listAllTransports()) { - pw.println((t.equals(mCurrentTransport) ? " * " : " ") + t); + pw.println((t.equals(mTransportManager.getCurrentTransportName()) ? " * " : " ") + t); try { - IBackupTransport transport = getTransport(t); + IBackupTransport transport = mTransportManager.getTransportBinder(t); File dir = new File(mBaseStateDir, transport.transportDirName()); pw.println(" destination: " + transport.currentDestinationString()); pw.println(" intent: " + transport.configurationIntent()); diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index d677f5ee04a8..a1a2c95e1eac 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -20,6 +20,8 @@ import android.app.backup.IBackupManager; import android.app.backup.IBackupObserver; import android.app.backup.IFullBackupRestoreObserver; import android.app.backup.IRestoreSession; +import android.app.backup.ISelectBackupTransportCallback; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Binder; @@ -275,6 +277,12 @@ public class Trampoline extends IBackupManager.Stub { } @Override + public ComponentName[] listAllTransportComponents() throws RemoteException { + BackupManagerService svc = mService; + return (svc != null) ? svc.listAllTransportComponents() : null; + } + + @Override public String[] getTransportWhitelist() { BackupManagerService svc = mService; return (svc != null) ? svc.getTransportWhitelist() : null; @@ -287,6 +295,15 @@ public class Trampoline extends IBackupManager.Stub { } @Override + public void selectBackupTransportAsync(ComponentName transport, + ISelectBackupTransportCallback listener) throws RemoteException { + BackupManagerService svc = mService; + if (svc != null) { + svc.selectBackupTransportAsync(transport, listener); + } + } + + @Override public Intent getConfigurationIntent(String transport) throws RemoteException { BackupManagerService svc = mService; return (svc != null) ? svc.getConfigurationIntent(transport) : null; diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java new file mode 100644 index 000000000000..93d5a1ea8880 --- /dev/null +++ b/services/backup/java/com/android/server/backup/TransportManager.java @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup; + +import android.app.backup.BackupManager; +import android.app.backup.SelectBackupTransportCallback; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.EventLog; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.backup.IBackupTransport; +import com.android.server.EventLogTags; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Handles in-memory bookkeeping of all BackupTransport objects. + */ +class TransportManager { + + private static final String TAG = "BackupTransportManager"; + + private static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST"; + + private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST); + private final Context mContext; + private final PackageManager mPackageManager; + private final Set<ComponentName> mTransportWhitelist; + + /** + * This listener is called after we bind to any transport. If it returns true, this is a valid + * transport. + */ + private final TransportBoundListener mTransportBoundListener; + + private String mCurrentTransportName; + + /** Lock on this before accessing mValidTransports and mBoundTransports. */ + private final Object mTransportLock = new Object(); + + /** + * We have detected these transports on the device. Unless in exceptional cases, we are also + * bound to all of these. + */ + @GuardedBy("mTransportLock") + private final Map<ComponentName, TransportConnection> mValidTransports = new ArrayMap<>(); + + /** We are currently bound to these transports. */ + @GuardedBy("mTransportLock") + private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>(); + + TransportManager(Context context, Set<ComponentName> whitelist, String defaultTransport, + TransportBoundListener listener) { + mContext = context; + mPackageManager = context.getPackageManager(); + mTransportWhitelist = whitelist; + mCurrentTransportName = defaultTransport; + mTransportBoundListener = listener; + } + + void onPackageAdded(String packageName) { + // New package added. Bind to all transports it contains. + synchronized (mTransportLock) { + log_verbose("Package added. Binding to all transports. " + packageName); + bindToAllInternal(packageName, null /* all components */); + } + } + + void onPackageRemoved(String packageName) { + // Package removed. Remove all its transports from our list. These transports have already + // been removed from mBoundTransports because onServiceDisconnected would already been + // called on TransportConnection objects. + synchronized (mTransportLock) { + for (ComponentName transport : mValidTransports.keySet()) { + if (transport.getPackageName().equals(packageName)) { + TransportConnection removed = mValidTransports.remove(transport); + if (removed != null) { + mContext.unbindService(removed); + log_verbose("Package removed, Removing transport: " + + transport.flattenToShortString()); + } + } + } + } + } + + void onPackageChanged(String packageName, String[] components) { + synchronized (mTransportLock) { + // Remove all changed components from mValidTransports. We'll bind to them again + // and re-add them if still valid. + for (String component : components) { + ComponentName componentName = new ComponentName(packageName, component); + TransportConnection removed = mValidTransports.remove(componentName); + if (removed != null) { + mContext.unbindService(removed); + log_verbose("Package changed. Removing transport: " + + componentName.flattenToShortString()); + } + } + bindToAllInternal(packageName, components); + } + } + + IBackupTransport getTransportBinder(String transportName) { + synchronized (mTransportLock) { + ComponentName component = mBoundTransports.get(transportName); + if (component == null) { + Slog.w(TAG, "Transport " + transportName + " not bound."); + return null; + } + TransportConnection conn = mValidTransports.get(component); + if (conn == null) { + Slog.w(TAG, "Transport " + transportName + " not valid."); + return null; + } + return conn.getBinder(); + } + } + + IBackupTransport getCurrentTransportBinder() { + return getTransportBinder(mCurrentTransportName); + } + + String getTransportName(IBackupTransport binder) { + synchronized (mTransportLock) { + for (TransportConnection conn : mValidTransports.values()) { + if (conn.getBinder() == binder) { + return conn.getName(); + } + } + } + return null; + } + + String[] getBoundTransportNames() { + synchronized (mTransportLock) { + return mBoundTransports.keySet().toArray(new String[0]); + } + } + + ComponentName[] getAllTransportCompenents() { + synchronized (mTransportLock) { + return mValidTransports.keySet().toArray(new ComponentName[0]); + } + } + + String getCurrentTransportName() { + return mCurrentTransportName; + } + + Set<ComponentName> getTransportWhitelist() { + return mTransportWhitelist; + } + + String selectTransport(String transport) { + synchronized (mTransportLock) { + String prevTransport = mCurrentTransportName; + mCurrentTransportName = transport; + return prevTransport; + } + } + + void ensureTransportReady(ComponentName transportComponent, SelectBackupTransportCallback listener) { + synchronized (mTransportLock) { + TransportConnection conn = mValidTransports.get(transportComponent); + if (conn == null) { + listener.onFailure(BackupManager.ERROR_TRANSPORT_UNAVAILABLE); + return; + } + // Transport can be unbound if the process hosting it crashed. + conn.bindIfUnbound(); + conn.addListener(listener); + } + } + + void registerAllTransports() { + bindToAllInternal(null /* all packages */, null /* all components */); + } + + /** + * Bind to all transports belonging to the given package and the given component list. + * null acts a wildcard. + * + * If packageName is null, bind to all transports in all packages. + * If components is null, bind to all transports in the given package. + */ + private void bindToAllInternal(String packageName, String[] components) { + PackageInfo pkgInfo = null; + if (packageName != null) { + try { + pkgInfo = mPackageManager.getPackageInfo(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Package not found: " + packageName); + return; + } + } + + Intent intent = new Intent(mTransportServiceIntent); + if (packageName != null) { + intent.setPackage(packageName); + } + + List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser( + intent, 0, UserHandle.USER_SYSTEM); + if (hosts != null) { + for (ResolveInfo host : hosts) { + final ServiceInfo info = host.serviceInfo; + boolean shouldBind = false; + if (components != null && packageName != null) { + for (String component : components) { + ComponentName cn = new ComponentName(pkgInfo.packageName, component); + if (info.getComponentName().equals(cn)) { + shouldBind = true; + break; + } + } + } else { + shouldBind = true; + } + if (shouldBind && isTransportTrusted(info.getComponentName())) { + tryBindTransport(info); + } + } + } + } + + /** Transport has to be whitelisted and privileged. */ + private boolean isTransportTrusted(ComponentName transport) { + if (!mTransportWhitelist.contains(transport)) { + Slog.w(TAG, "BackupTransport " + transport.flattenToShortString() + + " not whitelisted."); + return false; + } + try { + PackageInfo packInfo = mPackageManager.getPackageInfo(transport.getPackageName(), 0); + if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) + == 0) { + Slog.w(TAG, "Transport package " + transport.getPackageName() + " not privileged"); + return false; + } + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Package not found.", e); + return false; + } + return true; + } + + private void tryBindTransport(ServiceInfo transport) { + Slog.d(TAG, "Binding to transport: " + transport.getComponentName().flattenToShortString()); + // TODO: b/22388012 (Multi user backup and restore) + TransportConnection connection = new TransportConnection(transport.getComponentName()); + if (bindToTransport(transport.getComponentName(), connection)) { + synchronized (mTransportLock) { + mValidTransports.put(transport.getComponentName(), connection); + } + } else { + Slog.w(TAG, "Couldn't bind to transport " + transport.getComponentName()); + } + } + + private boolean bindToTransport(ComponentName componentName, ServiceConnection connection) { + Intent intent = new Intent(mTransportServiceIntent) + .setComponent(componentName); + return mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE, + UserHandle.SYSTEM); + } + + private class TransportConnection implements ServiceConnection { + + // Hold mTransportsLock to access these fields so as to provide a consistent view of them. + private IBackupTransport mBinder; + private final List<SelectBackupTransportCallback> mListeners = new ArrayList<>(); + private String mTransportName; + + private final ComponentName mTransportComponent; + + private TransportConnection(ComponentName transportComponent) { + mTransportComponent = transportComponent; + } + + @Override + public void onServiceConnected(ComponentName component, IBinder binder) { + synchronized (mTransportLock) { + mBinder = IBackupTransport.Stub.asInterface(binder); + boolean success = false; + + EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, + component.flattenToShortString(), 1); + + try { + mTransportName = mBinder.name(); + // BackupManager requests some fields from the transport. If they are + // invalid, throw away this transport. + success = mTransportBoundListener.onTransportBound(mBinder); + } catch (RemoteException e) { + success = false; + Slog.e(TAG, "Couldn't get transport name.", e); + } finally { + if (success) { + Slog.d(TAG, "Bound to transport: " + component.flattenToShortString()); + mBoundTransports.put(mTransportName, component); + for (SelectBackupTransportCallback listener : mListeners) { + listener.onSuccess(mTransportName); + } + } else { + Slog.w(TAG, "Bound to transport " + component.flattenToShortString() + + " but it is invalid"); + EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, + component.flattenToShortString(), 0); + mContext.unbindService(this); + mValidTransports.remove(component); + mBinder = null; + for (SelectBackupTransportCallback listener : mListeners) { + listener.onFailure(BackupManager.ERROR_TRANSPORT_INVALID); + } + } + mListeners.clear(); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName component) { + synchronized (mTransportLock) { + mBinder = null; + mBoundTransports.remove(mTransportName); + } + EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, + component.flattenToShortString(), 0); + Slog.w(TAG, "Disconnected from transport " + component.flattenToShortString()); + } + + private IBackupTransport getBinder() { + synchronized (mTransportLock) { + return mBinder; + } + } + + private String getName() { + synchronized (mTransportLock) { + return mTransportName; + } + } + + private void bindIfUnbound() { + synchronized (mTransportLock) { + if (mBinder == null) { + Slog.d(TAG, + "Rebinding to transport " + mTransportComponent.flattenToShortString()); + bindToTransport(mTransportComponent, this); + } + } + } + + private void addListener(SelectBackupTransportCallback listener) { + synchronized (mTransportLock) { + if (mBinder == null) { + // We are waiting for bind to complete. If mBinder is set to null after the bind + // is complete due to transport being invalid, we won't find 'this' connection + // object in mValidTransports list and this function can't be called. + mListeners.add(listener); + } else { + listener.onSuccess(mTransportName); + } + } + } + } + + interface TransportBoundListener { + /** Should return true if this is a valid transport. */ + boolean onTransportBound(IBackupTransport binder); + } + + private static void log_verbose(String message) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Slog.v(TAG, message); + } + } +} diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index e8ecc3e5f44a..dab4dfb41dec 100644 --- a/services/core/java/com/android/server/NetworkScoreService.java +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -41,6 +41,10 @@ import android.net.RecommendationRequest; import android.net.RecommendationResult; import android.net.ScoredNetwork; import android.net.Uri; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiScanner; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -76,7 +80,9 @@ import java.util.Map; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; /** * Backing service for {@link android.net.NetworkScoreManager}. @@ -391,6 +397,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { isEmpty = callbackList == null || callbackList.getRegisteredCallbackCount() == 0; } + if (isEmpty) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "No scorer registered for type " + entry.getKey() @@ -399,18 +406,10 @@ public class NetworkScoreService extends INetworkScoreService.Stub { continue; } - sendCallback(new Consumer<INetworkScoreCache>() { - @Override - public void accept(INetworkScoreCache networkScoreCache) { - try { - networkScoreCache.updateScores(entry.getValue()); - } catch (RemoteException e) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e); - } - } - } - }, Collections.singleton(callbackList)); + final BiConsumer<INetworkScoreCache, Object> consumer = + new FilteringCacheUpdatingConsumer(mContext, entry.getValue(), + entry.getKey()); + sendCacheUpdateCallback(consumer, Collections.singleton(callbackList)); } return true; @@ -419,6 +418,229 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } } + /** + * A {@link BiConsumer} implementation that filters the given {@link ScoredNetwork} + * list (if needed) before invoking {@link INetworkScoreCache#updateScores(List)} on the + * accepted {@link INetworkScoreCache} implementation. + */ + @VisibleForTesting + public static class FilteringCacheUpdatingConsumer + implements BiConsumer<INetworkScoreCache, Object> { + private final Context mContext; + private final List<ScoredNetwork> mScoredNetworkList; + private final int mNetworkType; + // TODO(jjoslin): 1/23/17 - Consider a Map if we implement more filters. + private Function<List<ScoredNetwork>, List<ScoredNetwork>> mCurrentNetworkFilter; + private Function<List<ScoredNetwork>, List<ScoredNetwork>> mScanResultsFilter; + + public FilteringCacheUpdatingConsumer(Context context, + List<ScoredNetwork> scoredNetworkList, int networkType) { + this(context, scoredNetworkList, networkType, null, null); + } + + @VisibleForTesting + public FilteringCacheUpdatingConsumer(Context context, + List<ScoredNetwork> scoredNetworkList, int networkType, + Function<List<ScoredNetwork>, List<ScoredNetwork>> currentNetworkFilter, + Function<List<ScoredNetwork>, List<ScoredNetwork>> scanResultsFilter) { + mContext = context; + mScoredNetworkList = scoredNetworkList; + mNetworkType = networkType; + mCurrentNetworkFilter = currentNetworkFilter; + mScanResultsFilter = scanResultsFilter; + } + + @Override + public void accept(INetworkScoreCache networkScoreCache, Object cookie) { + int filterType = NetworkScoreManager.CACHE_FILTER_NONE; + if (cookie instanceof Integer) { + filterType = (Integer) cookie; + } + + try { + final List<ScoredNetwork> filteredNetworkList = + filterScores(mScoredNetworkList, filterType); + if (!filteredNetworkList.isEmpty()) { + networkScoreCache.updateScores( + Collections.unmodifiableList(filteredNetworkList)); + } + } catch (RemoteException e) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Unable to update scores of type " + mNetworkType, e); + } + } + } + + /** + * Applies the appropriate filter and returns the filtered results. + */ + private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList, + int filterType) { + switch (filterType) { + case NetworkScoreManager.CACHE_FILTER_NONE: + return scoredNetworkList; + + case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK: + if (mCurrentNetworkFilter == null) { + mCurrentNetworkFilter = + new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext)); + } + return mCurrentNetworkFilter.apply(scoredNetworkList); + + case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS: + if (mScanResultsFilter == null) { + mScanResultsFilter = new ScanResultsScoreCacheFilter( + new ScanResultsSupplier(mContext)); + } + return mScanResultsFilter.apply(scoredNetworkList); + + default: + Log.w(TAG, "Unknown filter type: " + filterType); + return scoredNetworkList; + } + } + } + + /** + * Helper class that improves the testability of the cache filter Functions. + */ + private static class WifiInfoSupplier implements Supplier<WifiInfo> { + private final Context mContext; + + WifiInfoSupplier(Context context) { + mContext = context; + } + + @Override + public WifiInfo get() { + WifiManager wifiManager = mContext.getSystemService(WifiManager.class); + if (wifiManager != null) { + return wifiManager.getConnectionInfo(); + } + Log.w(TAG, "WifiManager is null, failed to return the WifiInfo."); + return null; + } + } + + /** + * Helper class that improves the testability of the cache filter Functions. + */ + private static class ScanResultsSupplier implements Supplier<List<ScanResult>> { + private final Context mContext; + + ScanResultsSupplier(Context context) { + mContext = context; + } + + @Override + public List<ScanResult> get() { + WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class); + if (wifiScanner != null) { + return wifiScanner.getSingleScanResults(); + } + Log.w(TAG, "WifiScanner is null, failed to return scan results."); + return Collections.emptyList(); + } + } + + /** + * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the + * {@link ScoredNetwork} associated with the current network. If no network is connected the + * returned list will be empty. + * <p> + * Note: this filter performs some internal caching for consistency and performance. The + * current network is determined at construction time and never changed. Also, the + * last filtered list is saved so if the same input is provided multiple times in a row + * the computation is only done once. + */ + @VisibleForTesting + public static class CurrentNetworkScoreCacheFilter + implements Function<List<ScoredNetwork>, List<ScoredNetwork>> { + private final NetworkKey mCurrentNetwork; + private Pair<List<ScoredNetwork>, Integer> mCache; + + CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) { + mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get()); + } + + @Override + public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) { + if (mCurrentNetwork == null || scoredNetworks.isEmpty()) { + return Collections.emptyList(); + } + + final int inputListHash = scoredNetworks.hashCode(); + if (mCache == null || mCache.second != inputListHash) { + ScoredNetwork currentScore = null; + for (int i = 0; i < scoredNetworks.size(); i++) { + final ScoredNetwork scoredNetwork = scoredNetworks.get(i); + if (scoredNetwork.networkKey.equals(mCurrentNetwork)) { + currentScore = scoredNetwork; + break; + } + } + + if (currentScore == null) { + mCache = Pair.create(Collections.emptyList(), inputListHash); + } else { + mCache = Pair.create(Collections.singletonList(currentScore), inputListHash); + } + } + + return mCache.first; + } + } + + /** + * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the + * {@link ScoredNetwork} associated with the current set of {@link ScanResult}s. + * If there are no {@link ScanResult}s the returned list will be empty. + * <p> + * Note: this filter performs some internal caching for consistency and performance. The + * current set of ScanResults is determined at construction time and never changed. + * Also, the last filtered list is saved so if the same input is provided multiple + * times in a row the computation is only done once. + */ + @VisibleForTesting + public static class ScanResultsScoreCacheFilter + implements Function<List<ScoredNetwork>, List<ScoredNetwork>> { + private final List<NetworkKey> mScanResultKeys; + private Pair<List<ScoredNetwork>, Integer> mCache; + + ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) { + mScanResultKeys = new ArrayList<>(); + List<ScanResult> scanResults = resultsSupplier.get(); + for (int i = 0; i < scanResults.size(); i++) { + ScanResult scanResult = scanResults.get(i); + mScanResultKeys.add(NetworkKey.createFromScanResult(scanResult)); + } + } + + @Override + public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) { + if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) { + return Collections.emptyList(); + } + + final int inputListHash = scoredNetworks.hashCode(); + if (mCache == null || mCache.second != inputListHash) { + List<ScoredNetwork> filteredScores = new ArrayList<>(); + for (int i = 0; i < scoredNetworks.size(); i++) { + final ScoredNetwork scoredNetwork = scoredNetworks.get(i); + for (int j = 0; j < mScanResultKeys.size(); j++) { + final NetworkKey scanResultKey = mScanResultKeys.get(j); + if (scanResultKey.equals(scoredNetwork.networkKey)) { + filteredScores.add(scoredNetwork); + } + } + } + mCache = Pair.create(filteredScores, inputListHash); + } + + return mCache.first; + } + } + private boolean isCallerSystemUid() { // REQUEST_NETWORK_SCORES is a signature only permission. return mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES) == @@ -499,9 +721,9 @@ public class NetworkScoreService extends INetworkScoreService.Stub { /** Clear scores. Callers are responsible for checking permissions as appropriate. */ private void clearInternal() { - sendCallback(new Consumer<INetworkScoreCache>() { + sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() { @Override - public void accept(INetworkScoreCache networkScoreCache) { + public void accept(INetworkScoreCache networkScoreCache, Object cookie) { try { networkScoreCache.clearScores(); } catch (RemoteException e) { @@ -675,9 +897,9 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } writer.println("Current scorer: " + currentScorer.packageName); - sendCallback(new Consumer<INetworkScoreCache>() { + sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() { @Override - public void accept(INetworkScoreCache networkScoreCache) { + public void accept(INetworkScoreCache networkScoreCache, Object cookie) { try { TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args); } catch (IOException | RemoteException e) { @@ -708,14 +930,15 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } } - private void sendCallback(Consumer<INetworkScoreCache> consumer, + private void sendCacheUpdateCallback(BiConsumer<INetworkScoreCache, Object> consumer, Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) { for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) { synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList final int count = callbackList.beginBroadcast(); try { for (int i = 0; i < count; i++) { - consumer.accept(callbackList.getBroadcastItem(i)); + consumer.accept(callbackList.getBroadcastItem(i), + callbackList.getRegisteredCallbackCookie(i)); } } finally { callbackList.finishBroadcast(); diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index bb8401ff371a..d51e96ad78fb 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -19,6 +19,7 @@ package com.android.server; import android.content.ContentResolver; import android.content.Context; import android.content.pm.UserInfo; +import android.os.Build; import android.os.RecoverySystem; import android.os.SystemClock; import android.os.SystemProperties; @@ -45,6 +46,7 @@ import com.android.internal.util.ArrayUtils; public class RescueParty { private static final String TAG = "RescueParty"; + private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; private static final String PROP_RESCUE_LEVEL = "sys.rescue_level"; private static final String PROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count"; private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start"; @@ -55,16 +57,23 @@ public class RescueParty { private static final int LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3; private static final int LEVEL_FACTORY_RESET = 4; + private static final boolean DISABLE_RESET_SETTINGS = true; + /** Threshold for boot loops */ private static final Threshold sBoot = new BootThreshold(); /** Threshold for app crash loops */ private static SparseArray<Threshold> sApps = new SparseArray<>(); + private static boolean isDisabled() { + return Build.IS_ENG || SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false); + } + /** * Take note of a boot event. If we notice too many of these events * happening in rapid succession, we'll send out a rescue party. */ public static void noteBoot(Context context) { + if (isDisabled()) return; if (sBoot.incrementAndTest()) { sBoot.reset(); incrementRescueLevel(sBoot.uid); @@ -77,6 +86,7 @@ public class RescueParty { * events happening in rapid succession, we'll send out a rescue party. */ public static void notePersistentAppCrash(Context context, int uid) { + if (isDisabled()) return; Threshold t = sApps.get(uid); if (t == null) { t = new AppThreshold(uid); @@ -151,6 +161,8 @@ public class RescueParty { } private static void resetAllSettings(Context context, int mode) throws Exception { + if (DISABLE_RESET_SETTINGS) return; + // Try our best to reset all settings possible, and once finished // rethrow any exception that we encountered Exception res = null; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a72950b01dc8..189474b8ba01 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -7625,12 +7625,11 @@ public class ActivityManagerService extends IActivityManager.Stub final float aspectRatio = r.pictureInPictureArgs.getAspectRatio(); final List<RemoteAction> actions = r.pictureInPictureArgs.getActions(); final Rect bounds = isValidPictureInPictureAspectRatio(aspectRatio) - ? mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY, - aspectRatio) + ? mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY, aspectRatio) : mWindowManager.getPictureInPictureDefaultBounds(DEFAULT_DISPLAY); mStackSupervisor.moveActivityToPinnedStackLocked(r, "enterPictureInPictureMode", bounds, true /* moveHomeStackToFront */); - mWindowManager.setPictureInPictureActions(actions); + mStackSupervisor.getStack(PINNED_STACK_ID).setPictureInPictureActions(actions); }; if (isKeyguardLocked()) { @@ -7678,12 +7677,11 @@ public class ActivityManagerService extends IActivityManager.Stub // Only update the saved args from the args that are set r.pictureInPictureArgs.copyOnlySet(args); - if (r.getStack().getStackId() == PINNED_STACK_ID) { + final ActivityStack stack = r.getStack(); + if (stack.getStackId() == PINNED_STACK_ID) { // If the activity is already in picture-in-picture, update the pinned stack now - mWindowManager.setPictureInPictureAspectRatio( - r.pictureInPictureArgs.getAspectRatio()); - mWindowManager.setPictureInPictureActions( - r.pictureInPictureArgs.getActions()); + stack.setPictureInPictureAspectRatio(r.pictureInPictureArgs.getAspectRatio()); + stack.setPictureInPictureActions(r.pictureInPictureArgs.getActions()); } } } finally { @@ -9341,7 +9339,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (tr.mBounds != null) { rti.bounds = new Rect(tr.mBounds); } - rti.isDockable = tr.canGoInDockedStack(); + rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreen(); rti.resizeMode = tr.mResizeMode; ActivityRecord base = null; @@ -10221,7 +10219,9 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (this) { if (animate) { if (stackId == PINNED_STACK_ID) { - mWindowManager.animateResizePinnedStack(bounds, animationDuration); + final ActivityStack pinnedStack = + mStackSupervisor.getStack(PINNED_STACK_ID); + pinnedStack.animateResizePinnedStack(bounds, animationDuration); } else { throw new IllegalArgumentException("Stack: " + stackId + " doesn't support animated resize."); @@ -13404,13 +13404,12 @@ public class ActivityManagerService extends IActivityManager.Stub if (supportsMultiWindow || forceResizable) { mSupportsMultiWindow = true; mSupportsFreeformWindowManagement = freeformWindowManagement || forceResizable; - mSupportsPictureInPicture = supportsPictureInPicture || forceResizable; } else { mSupportsMultiWindow = false; mSupportsFreeformWindowManagement = false; - mSupportsPictureInPicture = false; } mSupportsSplitScreenMultiWindow = supportsSplitScreenMultiWindow; + mSupportsPictureInPicture = supportsPictureInPicture; mWindowManager.setForceResizableTasks(mForceResizableActivities); mWindowManager.setSupportsPictureInPicture(mSupportsPictureInPicture); // This happens before any activities are started, so we can change global configuration @@ -17756,6 +17755,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Not backing this app up any more; reset its OOM adjustment final ProcessRecord proc = mBackupTarget.app; updateOomAdjLocked(proc); + proc.inFullBackup = false; // If the app crashed during backup, 'thread' will be null here if (proc.thread != null) { @@ -19594,7 +19594,7 @@ public class ActivityManagerService extends IActivityManager.Stub /** Helper method that requests bounds from WM and applies them to stack. */ private void resizeStackWithBoundsFromWindowManager(int stackId, boolean deferResume) { - final Rect newBounds = mWindowManager.getBoundsForNewConfiguration(stackId); + final Rect newBounds = mStackSupervisor.getStack(stackId).getBoundsForNewConfiguration(); mStackSupervisor.resizeStackLocked( stackId, newBounds, null /* tempTaskBounds */, null /* tempTaskInsetBounds */, false /* preserveWindows */, false /* allowResizeInDockedMode */, deferResume); @@ -19615,7 +19615,8 @@ public class ActivityManagerService extends IActivityManager.Stub && config.navigation == Configuration.NAVIGATION_NONAV); int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK; final boolean uiModeSupportsDialogs = (modeType != Configuration.UI_MODE_TYPE_CAR - && !(modeType == Configuration.UI_MODE_TYPE_WATCH && "user".equals(Build.TYPE))); + && !(modeType == Configuration.UI_MODE_TYPE_WATCH && "user".equals(Build.TYPE)) + && modeType != Configuration.UI_MODE_TYPE_TELEVISION); return inputMethodExists && uiModeSupportsDialogs && !inVrMode; } diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java index e46d20457706..65b8554ac0c1 100644 --- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java @@ -3,9 +3,7 @@ package com.android.server.am; import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; -import static android.app.ActivityManager.StackId.HOME_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; -import static android.app.ActivityManager.StackId.RECENTS_STACK_ID; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -17,7 +15,7 @@ import android.content.Context; import android.os.SystemClock; import android.util.Slog; -import com.android.internal.logging.LogBuilder; +import android.metrics.LogMaker; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -172,7 +170,7 @@ class ActivityMetricsLogger { MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS, (int) (SystemClock.uptimeMillis() / 1000)); - LogBuilder builder = new LogBuilder(MetricsEvent.APP_TRANSITION); + LogMaker builder = new LogMaker(MetricsEvent.APP_TRANSITION); builder.addTaggedData(MetricsEvent.APP_TRANSITION_COMPONENT_NAME, componentName); builder.addTaggedData(MetricsEvent.APP_TRANSITION_PROCESS_RUNNING, processRunning ? 1 : 0); builder.addTaggedData(MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS, diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index a968e0bb2a47..2a849b6d573d 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -22,6 +22,8 @@ import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.HOME_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT; @@ -37,7 +39,6 @@ import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; -import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.os.Build.VERSION_CODES.HONEYCOMB; @@ -450,6 +451,7 @@ final class ActivityRecord implements AppWindowContainerListener { } if (info != null) { pw.println(prefix + "resizeMode=" + ActivityInfo.resizeModeToString(info.resizeMode)); + pw.println(prefix + "supportsPictureInPicture=" + info.supportsPictureInPicture()); } pw.println(prefix + "supportsPictureInPictureWhilePausing: " + supportsPictureInPictureWhilePausing); @@ -875,24 +877,50 @@ final class ActivityRecord implements AppWindowContainerListener { } boolean isResizeable() { - return ActivityInfo.isResizeableMode(info.resizeMode); + return ActivityInfo.isResizeableMode(info.resizeMode) || info.supportsPictureInPicture(); } - boolean isResizeableOrForced() { - return !isHomeActivity() && (isResizeable() || service.mForceResizableActivities); - } - - boolean isNonResizableOrForced() { + /** + * @return whether this activity is non-resizeable or forced to be resizeable + */ + boolean isNonResizableOrForcedResizable() { return info.resizeMode != RESIZE_MODE_RESIZEABLE - && info.resizeMode != RESIZE_MODE_RESIZEABLE_AND_PIPABLE && info.resizeMode != RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; } /** - * @return whether this activity's resize mode supports PIP. + * @return whether this activity supports PiP multi-window and can be put in the pinned stack. */ boolean supportsPictureInPicture() { - return !isHomeActivity() && info.resizeMode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE; + return service.mSupportsPictureInPicture && !isHomeActivity() + && info.supportsPictureInPicture(); + } + + /** + * @return whether this activity supports split-screen multi-window and can be put in the docked + * stack. + */ + boolean supportsSplitScreen() { + // An activity can not be docked even if it is considered resizeable because it only + // supports picture-in-picture mode but has a non-resizeable resizeMode + return service.mSupportsSplitScreenMultiWindow && supportsResizeableMultiWindow(); + } + + /** + * @return whether this activity supports freeform multi-window and can be put in the freeform + * stack. + */ + boolean supportsFreeform() { + return service.mSupportsFreeformWindowManagement && supportsResizeableMultiWindow(); + } + + /** + * @return whether this activity supports non-PiP multi-window. + */ + private boolean supportsResizeableMultiWindow() { + return service.mSupportsMultiWindow && !isHomeActivity() + && (ActivityInfo.isResizeableMode(info.resizeMode) + || service.mForceResizableActivities); } /** @@ -911,13 +939,15 @@ final class ActivityRecord implements AppWindowContainerListener { case PAUSED: // When pausing, only allow enter PiP if not on the lockscreen and there is not // already an existing PiP activity - return !isKeyguardLocked && !hasPinnedStack && supportsPictureInPictureWhilePausing; + return !isKeyguardLocked && !hasPinnedStack && supportsPictureInPictureWhilePausing + && checkEnterPictureInPictureOnHideAppOpsState(); case STOPPING: // When stopping in a valid state, then only allow enter PiP as in the pause state. // Otherwise, fall through to throw an exception if the caller is trying to enter // PiP in an invalid stopping state. if (supportsPictureInPictureWhilePausing) { - return !isKeyguardLocked && !hasPinnedStack; + return !isKeyguardLocked && !hasPinnedStack + && checkEnterPictureInPictureOnHideAppOpsState(); } default: throw new IllegalStateException(caller @@ -926,8 +956,17 @@ final class ActivityRecord implements AppWindowContainerListener { } } - boolean canGoInDockedStack() { - return !isHomeActivity() && isResizeableOrForced(); + /** + * @return Whether AppOps allows this package to enter picture-in-picture when it is hidden. + */ + private boolean checkEnterPictureInPictureOnHideAppOpsState() { + try { + return service.getAppOpsService().checkOperation(OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE, + appInfo.uid, packageName) == MODE_ALLOWED; + } catch (RemoteException e) { + // Local call + } + return false; } boolean isAlwaysFocusable() { diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index d7b3728e9803..973951e4cc99 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -62,7 +62,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NA import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; import static com.android.server.am.ActivityStackSupervisor.FindTaskResult; -import static com.android.server.am.ActivityStackSupervisor.ON_TOP; import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE; import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN; @@ -73,7 +72,6 @@ import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN_BEHIND; import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_BACK; import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_FRONT; import static java.lang.Integer.MAX_VALUE; -import static java.lang.Integer.MIN_VALUE; import android.app.Activity; import android.app.ActivityManager; @@ -82,6 +80,7 @@ import android.app.ActivityManager.StackId; import android.app.ActivityOptions; import android.app.AppGlobals; import android.app.IActivityController; +import android.app.RemoteAction; import android.app.ResultInfo; import android.content.ComponentName; import android.content.Intent; @@ -116,6 +115,8 @@ import com.android.internal.os.BatteryStatsImpl; import com.android.server.Watchdog; import com.android.server.am.ActivityManagerService.ItemMatcher; import com.android.server.am.ActivityStackSupervisor.ActivityContainer; +import com.android.server.wm.StackWindowController; +import com.android.server.wm.StackWindowListener; import com.android.server.wm.WindowManagerService; import java.io.FileDescriptor; @@ -130,7 +131,7 @@ import java.util.Set; /** * State and management of a single stack of activities. */ -final class ActivityStack extends ConfigurationContainer { +final class ActivityStack extends ConfigurationContainer implements StackWindowListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_AM; private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE; @@ -242,6 +243,7 @@ final class ActivityStack extends ConfigurationContainer { final ActivityManagerService mService; private final WindowManagerService mWindowManager; + private StackWindowController mWindowContainerController; private final RecentTasks mRecentTasks; /** @@ -330,7 +332,7 @@ final class ActivityStack extends ConfigurationContainer { private final SparseArray<Configuration> mTmpConfigs = new SparseArray<>(); private final SparseArray<Rect> mTmpBounds = new SparseArray<>(); private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>(); - private final Rect tempRect2 = new Rect(); + private final Rect mTmpRect2 = new Rect(); /** Run all ActivityStacks through this */ private final ActivityStackSupervisor mStackSupervisor; @@ -443,7 +445,7 @@ final class ActivityStack extends ConfigurationContainer { } ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer, - RecentTasks recentTasks) { + RecentTasks recentTasks, boolean onTop) { mActivityContainer = activityContainer; mStackSupervisor = activityContainer.getOuter(); mService = mStackSupervisor.mService; @@ -454,13 +456,25 @@ final class ActivityStack extends ConfigurationContainer { mRecentTasks = recentTasks; mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID ? new LaunchingTaskPositioner() : null; + final ActivityStackSupervisor.ActivityDisplay display = mActivityContainer.mActivityDisplay; + mTmpRect2.setEmpty(); + mWindowContainerController = new StackWindowController(mStackId, this, + display.mDisplayId, onTop, mTmpRect2); + activityContainer.mStack = this; + mStackSupervisor.mActivityContainers.put(mStackId, activityContainer); + postAddToDisplay(display, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop); + } + + StackWindowController getWindowContainerController() { + return mWindowContainerController; } /** Adds the stack to specified display and calls WindowManager to do the same. */ - void addToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) { - final Rect bounds = mWindowManager.addStackToDisplay(mStackId, activityDisplay.mDisplayId, - onTop); - postAddToDisplay(activityDisplay, bounds); + void reparent(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) { + removeFromDisplay(); + mTmpRect2.setEmpty(); + mWindowContainerController.reparent(activityDisplay.mDisplayId, mTmpRect2); + postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop); } /** @@ -469,10 +483,10 @@ final class ActivityStack extends ConfigurationContainer { * @param bounds Updated bounds. */ private void postAddToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, - Rect bounds) { + Rect bounds, boolean onTop) { mDisplayId = activityDisplay.mDisplayId; mStacks = activityDisplay.mStacks; - mBounds = bounds; + mBounds = bounds != null ? new Rect(bounds) : null; mFullscreen = mBounds == null; if (mTaskPositioner != null) { mTaskPositioner.setDisplay(activityDisplay.mDisplay); @@ -480,6 +494,7 @@ final class ActivityStack extends ConfigurationContainer { } onParentChanged(); + activityDisplay.attachStack(this, onTop); if (mStackId == DOCKED_STACK_ID) { // If we created a docked stack we want to resize it so it resizes all other stacks // in the system. @@ -489,16 +504,6 @@ final class ActivityStack extends ConfigurationContainer { } /** - * Moves the stack to specified display. - * @param activityDisplay Target display to move the stack to. - */ - void moveToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay) { - removeFromDisplay(); - final Rect bounds = mWindowManager.moveStackToDisplay(mStackId, activityDisplay.mDisplayId); - postAddToDisplay(activityDisplay, bounds); - } - - /** * Updates the inner state of the stack to remove it from its current parent, so it can be * either destroyed completely or re-parented. */ @@ -520,7 +525,8 @@ final class ActivityStack extends ConfigurationContainer { void remove() { removeFromDisplay(); mStackSupervisor.deleteActivityContainerRecord(mStackId); - mWindowManager.removeStack(mStackId); + mWindowContainerController.removeContainer(); + mWindowContainerController = null; onParentChanged(); } @@ -528,6 +534,42 @@ final class ActivityStack extends ConfigurationContainer { mActivityContainer.mActivityDisplay.mDisplay.getSize(out); } + void animateResizePinnedStack(Rect bounds, int animationDuration) { + mWindowContainerController.animateResizePinnedStack(bounds, animationDuration); + } + + void setPictureInPictureAspectRatio(float aspectRatio) { + mWindowContainerController.setPictureInPictureAspectRatio(aspectRatio); + } + + void getStackDockedModeBounds(Rect outBounds, boolean ignoreVisibility) { + mWindowContainerController.getStackDockedModeBounds(outBounds, ignoreVisibility); + } + + void prepareFreezingTaskBounds() { + mWindowContainerController.prepareFreezingTaskBounds(); + } + + void setPictureInPictureActions(List<RemoteAction> actions) { + mWindowContainerController.setPictureInPictureActions(actions); + } + + void getWindowContainerBounds(Rect outBounds) { + if (mWindowContainerController != null) { + mWindowContainerController.getBounds(outBounds); + } + outBounds.setEmpty(); + } + + Rect getBoundsForNewConfiguration() { + return mWindowContainerController.getBoundsForNewConfiguration(); + } + + void positionChildWindowContainerAtTop(TaskRecord child) { + mWindowContainerController.positionChildAtTop(child.getWindowContainerController(), + true /* includingParents */); + } + /** * Defers updating the bounds of the stack. If the stack was resized/repositioned while * deferring, the bounds will update in {@link #continueUpdateBounds()}. @@ -548,8 +590,7 @@ final class ActivityStack extends ConfigurationContainer { final boolean wasDeferred = mUpdateBoundsDeferred; mUpdateBoundsDeferred = false; if (wasDeferred && mUpdateBoundsDeferredCalled) { - mStackSupervisor.resizeStackUncheckedLocked(this, - mDeferredBounds.isEmpty() ? null : mDeferredBounds, + resize(mDeferredBounds.isEmpty() ? null : mDeferredBounds, mDeferredTaskBounds.isEmpty() ? null : mDeferredTaskBounds, mDeferredTaskInsetBounds.isEmpty() ? null : mDeferredTaskInsetBounds); } @@ -767,7 +808,8 @@ final class ActivityStack extends ConfigurationContainer { task = topTask(); if (task != null) { - task.moveWindowContainerToTop(true /* includingParents */); + mWindowContainerController.positionChildAtTop(task.getWindowContainerController(), + true /* includingParents */); } } @@ -786,7 +828,7 @@ final class ActivityStack extends ConfigurationContainer { mTaskHistory.remove(task); mTaskHistory.add(0, task); updateTaskMovement(task, false); - task.moveWindowContainerToBottom(); + mWindowContainerController.positionChildAtBottom(task.getWindowContainerController()); } } @@ -1545,7 +1587,7 @@ final class ActivityStack extends ConfigurationContainer { // home task even though it's not resizable. final ActivityRecord r = focusedStack.topRunningActivityLocked(); final TaskRecord task = r != null ? r.task : null; - return task == null || task.canGoInDockedStack() || task.isHomeTask() ? STACK_VISIBLE + return task == null || task.supportsSplitScreen() || task.isHomeTask() ? STACK_VISIBLE : STACK_INVISIBLE; } @@ -1795,6 +1837,12 @@ final class ActivityStack extends ConfigurationContainer { } } + void addStartingWindowsForVisibleActivities(boolean taskSwitch) { + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + mTaskHistory.get(taskNdx).addStartingWindowsForVisibleActivities(taskSwitch); + } + } + /** * @return true if the top visible activity wants to occlude the Keyguard, false otherwise */ @@ -2574,7 +2622,8 @@ final class ActivityStack extends ConfigurationContainer { position = getAdjustedPositionForTask(task, position, null /* starting */); mTaskHistory.remove(task); mTaskHistory.add(position, task); - task.positionWindowContainerAt(position); + mWindowContainerController.positionChildAt(task.getWindowContainerController(), position, + task.mBounds, task.getOverrideConfiguration()); updateTaskMovement(task, true); } @@ -2586,7 +2635,8 @@ final class ActivityStack extends ConfigurationContainer { final int position = getAdjustedPositionForTask(task, mTaskHistory.size(), starting); mTaskHistory.add(position, task); updateTaskMovement(task, true); - task.moveWindowContainerToTop(true /* includingParents */); + mWindowContainerController.positionChildAtTop(task.getWindowContainerController(), + true /* includingParents */); } private void updateTaskReturnToForTopInsertion(TaskRecord task) { @@ -2614,7 +2664,7 @@ final class ActivityStack extends ConfigurationContainer { // This also makes sure that non-home activities are visible under a transparent // non-home activity. task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE); - } else if (!isHomeOrRecentsStack() && (fromHomeOrRecents || topTask != task)) { + } else if (!isHomeOrRecentsStack() && (fromHomeOrRecents || topTask() != task)) { // If it's a last task over home - we default to keep its return to type not to // make underlying task focused when this one will be finished. int returnToType = isLastTaskOverHome @@ -2864,7 +2914,8 @@ final class ActivityStack extends ConfigurationContainer { targetTask.addActivityAtBottom(p); } - targetTask.moveWindowContainerToBottom(); + mWindowContainerController.positionChildAtBottom( + targetTask.getWindowContainerController()); replyChainEnd = -1; } else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) { // If the activity should just be removed -- either @@ -3000,7 +3051,8 @@ final class ActivityStack extends ConfigurationContainer { if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Pulling activity " + p + " from " + srcPos + " in to resetting task " + task); } - task.moveWindowContainerToTop(true /* includingParents */); + mWindowContainerController.positionChildAtTop( + task.getWindowContainerController(), true /* includingParents */); // Now we've moved it in to place... but what if this is // a singleTop activity and we have put it on top of another @@ -4369,7 +4421,7 @@ final class ActivityStack extends ConfigurationContainer { } mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false); - tr.moveWindowContainerToBottom(); + mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController()); final TaskRecord task = mResumedActivity != null ? mResumedActivity.task : null; if (prevIsHome || (task == tr && canGoHome) || (numTasks <= 1 && isOnHomeDisplay())) { @@ -4432,11 +4484,24 @@ final class ActivityStack extends ConfigurationContainer { } } - /** Update override configurations of all tasks in the stack. */ - void updateOverrideConfiguration(Rect stackBounds, Rect tempTaskBounds, - Rect tempTaskInsetBounds) { + // TODO: Figure-out a way to consolidate with resize() method below. + @Override + public void requestResize(Rect bounds) { + mService.resizeStack(mStackId, bounds, true /* allowResizeInDockedMode */, + false /* preserveWindows */, false /* animate */, -1 /* animationDuration */); + } + + // TODO: Can only be called from special methods in ActivityStackSupervisor. + // Need to consolidate those calls points into this resize method so anyone can call directly. + void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds) { + bounds = TaskRecord.validateBounds(bounds); + + if (!updateBoundsAllowed(bounds, tempTaskBounds, tempTaskInsetBounds)) { + return; + } - final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : stackBounds; + // Update override configurations of all tasks in the stack. + final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds; final Rect insetBounds = tempTaskInsetBounds != null ? tempTaskInsetBounds : taskBounds; mTmpBounds.clear(); @@ -4450,9 +4515,9 @@ final class ActivityStack extends ConfigurationContainer { // For freeform stack we don't adjust the size of the tasks to match that // of the stack, but we do try to make sure the tasks are still contained // with the bounds of the stack. - tempRect2.set(task.mBounds); - fitWithinBounds(tempRect2, stackBounds); - task.updateOverrideConfiguration(tempRect2); + mTmpRect2.set(task.mBounds); + fitWithinBounds(mTmpRect2, bounds); + task.updateOverrideConfiguration(mTmpRect2); } else { task.updateOverrideConfiguration(taskBounds, insetBounds); } @@ -4465,11 +4530,9 @@ final class ActivityStack extends ConfigurationContainer { } } - // We might trigger a configuration change. Save the current task bounds for freezing. - mWindowManager.prepareFreezingTaskBounds(mStackId); - mFullscreen = mWindowManager.resizeStack(mStackId, stackBounds, mTmpConfigs, mTmpBounds, + mFullscreen = mWindowContainerController.resize(bounds, mTmpConfigs, mTmpBounds, mTmpInsetBounds); - setBounds(stackBounds); + setBounds(bounds); } @@ -4659,7 +4722,7 @@ final class ActivityStack extends ConfigurationContainer { } ci.numActivities = numActivities; ci.numRunning = numRunning; - ci.isDockable = task.canGoInDockedStack(); + ci.supportsSplitScreenMultiWindow = task.supportsSplitScreen(); ci.resizeMode = task.mResizeMode; list.add(ci); } @@ -4893,7 +4956,8 @@ final class ActivityStack extends ConfigurationContainer { addTask(task, toTop ? MAX_VALUE : 0, reason); if (toTop) { // TODO: figure-out a way to remove this call. - task.moveWindowContainerToTop(true /* includingParents */); + mWindowContainerController.positionChildAtTop(task.getWindowContainerController(), + true /* includingParents */); } } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 14899b4d5256..29032f8c797d 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -90,7 +90,6 @@ import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE; import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED; import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS; import static java.lang.Integer.MAX_VALUE; -import static java.lang.Integer.MIN_VALUE; import android.Manifest; import android.annotation.NonNull; @@ -377,7 +376,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // TODO: Add listener for removal of references. /** Mapping from (ActivityStack/TaskStack).mStackId to their current state */ - private SparseArray<ActivityContainer> mActivityContainers = new SparseArray<>(); + SparseArray<ActivityContainer> mActivityContainers = new SparseArray<>(); /** Mapping from displayId to display current state */ private final SparseArray<ActivityDisplay> mActivityDisplays = new SparseArray<>(); @@ -2199,7 +2198,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId); mWindowManager.deferSurfaceLayout(); try { - resizeStackUncheckedLocked(stack, bounds, tempTaskBounds, tempTaskInsetBounds); + stack.resize(bounds, tempTaskBounds, tempTaskInsetBounds); if (!deferResume) { stack.ensureVisibleActivitiesConfigurationLocked( stack.topRunningActivityLocked(), preserveWindows); @@ -2237,17 +2236,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mResizingTasksDuringAnimation.clear(); } - void resizeStackUncheckedLocked(ActivityStack stack, Rect bounds, Rect tempTaskBounds, - Rect tempTaskInsetBounds) { - bounds = TaskRecord.validateBounds(bounds); - - if (!stack.updateBoundsAllowed(bounds, tempTaskBounds, tempTaskInsetBounds)) { - return; - } - - stack.updateOverrideConfiguration(bounds, tempTaskBounds, tempTaskInsetBounds); - } - void moveTasksToFullscreenStackLocked(int fromStackId, boolean onTop) { final ActivityStack stack = getStack(fromStackId); if (stack == null) { @@ -2341,8 +2329,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // Don't allow re-entry while resizing. E.g. due to docked stack detaching. mAllowDockedStackResize = false; ActivityRecord r = stack.topRunningActivityLocked(); - resizeStackUncheckedLocked(stack, dockedBounds, tempDockedTaskBounds, - tempDockedTaskInsetBounds); + stack.resize(dockedBounds, tempDockedTaskBounds, tempDockedTaskInsetBounds); // TODO: Checking for isAttached might not be needed as if the user passes in null // dockedBounds then they want the docked stack to be dismissed. @@ -2359,10 +2346,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // static stacks need to be adjusted so they don't overlap with the docked stack. // We get the bounds to use from window manager which has been adjusted for any // screen controls and is also the same for all stacks. - mWindowManager.getStackDockedModeBounds( - HOME_STACK_ID, tempRect, true /* ignoreVisibility */); for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) { - if (StackId.isResizeableByDockedStack(i) && getStack(i) != null) { + final ActivityStack current = getStack(i); + if (current != null && StackId.isResizeableByDockedStack(i)) { + current.getStackDockedModeBounds(tempRect, true /* ignoreVisibility */); resizeStackLocked(i, tempRect, tempOtherTaskBounds, tempOtherTaskInsetBounds, preserveWindows, true /* allowResizeInDockedMode */, deferResume); @@ -2395,8 +2382,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mWindowManager.deferSurfaceLayout(); try { ActivityRecord r = stack.topRunningActivityLocked(); - resizeStackUncheckedLocked(stack, pinnedBounds, tempPinnedTaskBounds, - null); + stack.resize(pinnedBounds, tempPinnedTaskBounds, null); stack.ensureVisibleActivitiesConfigurationLocked(r, false); } finally { mWindowManager.continueSurfaceLayout(); @@ -2405,14 +2391,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } ActivityStack createStackOnDisplay(int stackId, int displayId, boolean onTop) { - ActivityDisplay activityDisplay = mActivityDisplays.get(displayId); + final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId); if (activityDisplay == null) { return null; } - ActivityContainer activityContainer = new ActivityContainer(stackId); - mActivityContainers.put(stackId, activityContainer); - activityContainer.addToDisplayLocked(activityDisplay, onTop); + final ActivityContainer activityContainer = + new ActivityContainer(stackId, activityDisplay, onTop); return activityContainer.mStack; } @@ -2576,7 +2561,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // This means that tasks that were on external displays will be restored on the // primary display. stackId = task.getLaunchStackId(); - } else if (stackId == DOCKED_STACK_ID && !task.canGoInDockedStack()) { + } else if (stackId == DOCKED_STACK_ID && !task.supportsSplitScreen()) { // Preferred stack is the docked stack, but the task can't go in the docked stack. // Put it in the fullscreen stack. stackId = FULLSCREEN_WORKSPACE_STACK_ID; @@ -2771,7 +2756,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } // We might trigger a configuration change. Save the current task bounds for freezing. - mWindowManager.prepareFreezingTaskBounds(stack.mStackId); + // TODO: Should this call be moved inside the resize method in WM? + stack.prepareFreezingTaskBounds(); // Make sure the task has the appropriate bounds/size for the stack it is in. if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) { @@ -2840,7 +2826,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D void moveActivityToPinnedStackLocked(ActivityRecord r, String reason, Rect bounds, boolean moveHomeStackToFront) { + mWindowManager.deferSurfaceLayout(); + // Need to make sure the pinned stack exist so we can resize it below... + final ActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP); + try { final TaskRecord task = r.task; @@ -2850,9 +2840,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D requestVisibleBehindLocked(r, false); } - // Need to make sure the pinned stack exist so we can resize it below... - final ActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP); - // Resize the pinned stack to match the current size of the task the activity we are // going to be moving is currently contained in. We do this to have the right starting // animation bounds for the pinned stack to the desired bounds the caller wants. @@ -2892,7 +2879,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); resumeFocusedStackTopActivityLocked(); - mWindowManager.animateResizePinnedStack(bounds, -1); + stack.animateResizePinnedStack(bounds, -1); mService.mTaskChangeNotificationController.notifyActivityPinned(); } @@ -3229,6 +3216,17 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } + void addStartingWindowsForVisibleActivities(boolean taskSwitch) { + for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + final int topStackNdx = stacks.size() - 1; + for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + stack.addStartingWindowsForVisibleActivities(taskSwitch); + } + } + } + void invalidateTaskLayers() { mTaskLayersChanged = true; } @@ -3346,7 +3344,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D stack.switchUserLocked(userId); TaskRecord task = stack.topTask(); if (task != null) { - task.moveWindowContainerToTop(true /* includingParents */); + stack.positionChildWindowContainerAtTop(task); } } } @@ -3784,7 +3782,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D private StackInfo getStackInfoLocked(ActivityStack stack) { final ActivityDisplay display = mActivityDisplays.get(DEFAULT_DISPLAY); StackInfo info = new StackInfo(); - mWindowManager.getStackBounds(stack.mStackId, info.bounds); + stack.getWindowContainerBounds(info.bounds); info.displayId = DEFAULT_DISPLAY; info.stackId = stack.mStackId; info.userId = stack.mCurrentUser; @@ -3884,14 +3882,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } final ActivityRecord topActivity = task.getTopActivity(); - if (!task.canGoInDockedStack() || forceNonResizable) { + if (!task.supportsSplitScreen() || forceNonResizable) { // Display a warning toast that we tried to put a non-dockable task in the docked stack. mService.mTaskChangeNotificationController.notifyActivityDismissingDockedStack(); // Dismiss docked stack. If task appeared to be in docked stack but is not resizable - // we need to move it to top of fullscreen stack, otherwise it will be covered. moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, actualStackId == DOCKED_STACK_ID); - } else if (topActivity != null && topActivity.isNonResizableOrForced() + } else if (topActivity != null && topActivity.isNonResizableOrForcedResizable() && !topActivity.noDisplay) { String packageName = topActivity.appInfo.packageName; mService.mTaskChangeNotificationController.notifyActivityForcedResizable( @@ -4310,7 +4308,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION; final int mStackId; IActivityContainerCallback mCallback = null; - final ActivityStack mStack; + ActivityStack mStack; ActivityRecord mParentActivity = null; String mIdString; @@ -4324,10 +4322,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final static int CONTAINER_STATE_FINISHING = 2; int mContainerState = CONTAINER_STATE_HAS_SURFACE; - ActivityContainer(int stackId) { + ActivityContainer(int stackId, ActivityDisplay activityDisplay, boolean onTop) { synchronized (mService) { mStackId = stackId; - mStack = new ActivityStack(this, mRecentTasks); + mActivityDisplay = activityDisplay; + new ActivityStack(this, mRecentTasks, onTop); mIdString = "ActivtyContainer{" + mStackId + "}"; if (DEBUG_STACK) Slog.d(TAG_STACK, "Creating " + this); } @@ -4335,21 +4334,18 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D /** * Adds the stack to specified display. Also calls WindowManager to do the same from - * {@link ActivityStack#addToDisplay(ActivityDisplay, boolean)}. + * {@link ActivityStack#reparent(ActivityDisplay, boolean)}. * @param activityDisplay The display to add the stack to. - * @param onTop If true the stack will be place at the top of the display, else at the - * bottom. */ - void addToDisplayLocked(ActivityDisplay activityDisplay, boolean onTop) { + void addToDisplayLocked(ActivityDisplay activityDisplay) { if (DEBUG_STACK) Slog.d(TAG_STACK, "addToDisplayLocked: " + this - + " to display=" + activityDisplay + " onTop=" + onTop); + + " to display=" + activityDisplay); if (mActivityDisplay != null) { throw new IllegalStateException("ActivityContainer is already attached, " + "displayId=" + mActivityDisplay.mDisplayId); } mActivityDisplay = activityDisplay; - mStack.addToDisplay(activityDisplay, onTop); - activityDisplay.attachActivities(mStack, onTop); + mStack.reparent(activityDisplay, true /* onTop */); } @Override @@ -4359,7 +4355,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (activityDisplay == null) { return; } - addToDisplayLocked(activityDisplay, true /* onTop */); + addToDisplayLocked(activityDisplay); } } @@ -4436,7 +4432,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (DEBUG_STACK) Slog.d(TAG_STACK, "removeFromDisplayLocked: " + this + " current displayId=" + mActivityDisplay.mDisplayId); - mActivityDisplay.detachActivitiesLocked(mStack); + mActivityDisplay.detachStack(mStack); mActivityDisplay = null; } @@ -4452,8 +4448,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D removeFromDisplayLocked(); mActivityDisplay = activityDisplay; - mStack.moveToDisplay(activityDisplay); - activityDisplay.attachActivities(mStack, ON_TOP); + mStack.reparent(activityDisplay, ON_TOP); } @Override @@ -4543,7 +4538,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean mDrawn = false; VirtualActivityContainer(ActivityRecord parent, IActivityContainerCallback callback) { - super(getNextStackId()); + super(getNextStackId(), parent.getStack().mActivityContainer.mActivityDisplay, + true /* onTop */); mParentActivity = parent; mCallback = callback; mContainerState = CONTAINER_STATE_NO_SURFACE; @@ -4575,7 +4571,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D new VirtualActivityDisplay(width, height, density); mActivityDisplay = virtualActivityDisplay; mActivityDisplays.put(virtualActivityDisplay.mDisplayId, virtualActivityDisplay); - addToDisplayLocked(virtualActivityDisplay, true /* onTop */); + addToDisplayLocked(virtualActivityDisplay); } if (mSurface != null) { @@ -4664,10 +4660,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mDisplayId = display.getDisplayId(); } - void attachActivities(ActivityStack stack, boolean onTop) { - if (DEBUG_STACK) Slog.v(TAG_STACK, - "attachActivities: attaching " + stack + " to displayId=" + mDisplayId - + " onTop=" + onTop); + void attachStack(ActivityStack stack, boolean onTop) { + if (DEBUG_STACK) Slog.v(TAG_STACK, "attachStack: attaching " + stack + + " to displayId=" + mDisplayId + " onTop=" + onTop); if (onTop) { mStacks.add(stack); } else { @@ -4675,8 +4670,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - void detachActivitiesLocked(ActivityStack stack) { - if (DEBUG_STACK) Slog.v(TAG_STACK, "detachActivitiesLocked: detaching " + stack + void detachStack(ActivityStack stack) { + if (DEBUG_STACK) Slog.v(TAG_STACK, "detachStack: detaching " + stack + " from displayId=" + mDisplayId); mStacks.remove(stack); } @@ -4754,8 +4749,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } @Override - void detachActivitiesLocked(ActivityStack stack) { - super.detachActivitiesLocked(stack); + void detachStack(ActivityStack stack) { + super.detachStack(stack); if (mVirtualDisplay != null) { mVirtualDisplay.release(); mVirtualDisplay = null; diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 26d2ee2e1547..b913a233fdbf 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -460,6 +460,7 @@ class ActivityStarter { final String splitName = rInfo.ephemeralResponse.splitName; final boolean needsPhaseTwo = rInfo.ephemeralResponse.needsPhase2; final String token = rInfo.ephemeralResponse.token; + final int versionCode = rInfo.ephemeralResponse.resolveInfo.getVersionCode(); if (needsPhaseTwo) { // request phase two resolution mService.getPackageManagerInternalLocked().requestEphemeralResolutionPhaseTwo( @@ -467,8 +468,8 @@ class ActivityStarter { callingPackage, userId); } intent = EphemeralResolver.buildEphemeralInstallerIntent(intent, ephemeralIntent, - callingPackage, resolvedType, userId, packageName, splitName, token, - needsPhaseTwo); + callingPackage, resolvedType, userId, packageName, splitName, versionCode, + token, needsPhaseTwo); resolvedType = null; callingUid = realCallingUid; callingPid = realCallingPid; @@ -1834,7 +1835,7 @@ class ActivityStarter { mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mStartActivity.info, mIntent, null, null, true, mStartActivity.mActivityType); mStartActivity.setTask(task, null); - mStartActivity.task.moveWindowContainerToTop(true /* includingParents */); + mStartActivity.task.getStack().positionChildWindowContainerAtTop(mStartActivity.task); if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity + " in new guessed " + mStartActivity.task); } @@ -1966,10 +1967,10 @@ class ActivityStarter { canUseFocusedStack = true; break; case DOCKED_STACK_ID: - canUseFocusedStack = r.canGoInDockedStack(); + canUseFocusedStack = r.supportsSplitScreen(); break; case FREEFORM_WORKSPACE_STACK_ID: - canUseFocusedStack = r.isResizeableOrForced(); + canUseFocusedStack = r.supportsFreeform(); break; default: canUseFocusedStack = isDynamicStack(focusedStackId) @@ -2056,29 +2057,24 @@ class ActivityStarter { } boolean isValidLaunchStackId(int stackId, ActivityRecord r) { - if (stackId == INVALID_STACK_ID || stackId == HOME_STACK_ID) { - return false; - } - - if (stackId != FULLSCREEN_WORKSPACE_STACK_ID - && (!mService.mSupportsMultiWindow || !r.isResizeableOrForced())) { - return false; - } - - if (stackId == DOCKED_STACK_ID && r.canGoInDockedStack()) { - return true; - } - - if (stackId == FREEFORM_WORKSPACE_STACK_ID && !mService.mSupportsFreeformWindowManagement) { - return false; - } - - final boolean supportsPip = mService.mSupportsPictureInPicture - && (r.supportsPictureInPicture() || mService.mForceResizableActivities); - if (stackId == PINNED_STACK_ID && !supportsPip) { - return false; + switch (stackId) { + case INVALID_STACK_ID: + case HOME_STACK_ID: + return false; + case FULLSCREEN_WORKSPACE_STACK_ID: + return true; + case FREEFORM_WORKSPACE_STACK_ID: + return r.supportsFreeform(); + case DOCKED_STACK_ID: + return r.supportsSplitScreen(); + case PINNED_STACK_ID: + return r.supportsPictureInPicture(); + case RECENTS_STACK_ID: + return r.isRecentsActivity(); + default: + Slog.e(TAG, "isValidLaunchStackId: Unexpected stackId=" + stackId); + return false; } - return true; } Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) { diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java index cfe2eb075530..b0a4746b8ff1 100644 --- a/services/core/java/com/android/server/am/KeyguardController.java +++ b/services/core/java/com/android/server/am/KeyguardController.java @@ -29,6 +29,7 @@ import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AW import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_GOING_AWAY; import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_OCCLUDE; import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE; +import static com.android.server.wm.AppTransition.TRANSIT_NONE; import static com.android.server.wm.AppTransition.TRANSIT_UNSET; import android.os.IBinder; @@ -120,6 +121,7 @@ class KeyguardController { // Some stack visibility might change (e.g. docked stack) mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); + mStackSupervisor.addStartingWindowsForVisibleActivities(true /* taskSwitch */); mWindowManager.executeAppTransition(); } finally { mWindowManager.continueSurfaceLayout(); diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 1f5152a1606d..f12d7b79a82f 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -85,6 +85,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRA import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; +import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; @@ -141,6 +142,7 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color"; private static final String ATTR_CALLING_UID = "calling_uid"; private static final String ATTR_CALLING_PACKAGE = "calling_package"; + private static final String ATTR_SUPPORTS_PICTURE_IN_PICTURE = "supports_picture_in_picture"; private static final String ATTR_RESIZE_MODE = "resize_mode"; private static final String ATTR_PRIVILEGED = "privileged"; private static final String ATTR_NON_FULLSCREEN_BOUNDS = "non_fullscreen_bounds"; @@ -188,6 +190,9 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta int mResizeMode; // The resize mode of this task and its activities. // Based on the {@link ActivityInfo#resizeMode} of the root activity. + boolean mSupportsPictureInPicture; // Whether or not this task and its activities support PiP. + // Based on the {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE} flag of the root + // activity. boolean mTemporarilyUnresizable; // Separate flag from mResizeMode used to suppress resize // changes on a temporary basis. private int mLockTaskMode; // Which tasklock mode to launch this task in. One of @@ -356,8 +361,9 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta boolean neverRelinquishIdentity, TaskDescription _lastTaskDescription, TaskThumbnailInfo lastThumbnailInfo, int taskAffiliation, int prevTaskId, int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage, - int resizeMode, boolean privileged, boolean _realActivitySuspended, - boolean userSetupComplete, int minWidth, int minHeight) { + int resizeMode, boolean supportsPictureInPicture, boolean privileged, + boolean _realActivitySuspended, boolean userSetupComplete, int minWidth, + int minHeight) { mService = service; mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + TaskPersister.IMAGE_EXTENSION; @@ -396,6 +402,7 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta mCallingUid = callingUid; mCallingPackage = callingPackage; mResizeMode = resizeMode; + mSupportsPictureInPicture = supportsPictureInPicture; mPrivileged = privileged; mMinWidth = minWidth; mMinHeight = minHeight; @@ -414,9 +421,10 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta final Rect bounds = updateOverrideConfigurationFromLaunchBounds(); final Configuration overrideConfig = getOverrideConfiguration(); - mWindowContainerController = new TaskWindowContainerController(taskId, this, getStackId(), - userId, bounds, overrideConfig, mResizeMode, isHomeTask(), isOnTopLauncher(), onTop, - showForAllUsers); + mWindowContainerController = new TaskWindowContainerController(taskId, this, + getStack().getWindowContainerController(), userId, bounds, overrideConfig, + mResizeMode, mSupportsPictureInPicture, isHomeTask(), isOnTopLauncher(), + onTop, showForAllUsers, lastTaskDescription); } void removeWindowContainer() { @@ -450,6 +458,12 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta mWindowContainerController.setTaskDockedResizing(resizing); } + // TODO: Consolidate this with the resize() method below. + @Override + public void requestResize(Rect bounds, int resizeMode) { + mService.resizeTask(taskId, bounds, resizeMode); + } + boolean resize(Rect bounds, int resizeMode, boolean preserveWindow, boolean deferResume) { if (!isResizeable()) { Slog.w(TAG, "resizeTask: task " + this + " not resizeable."); @@ -520,25 +534,6 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta false /* forced */); } - // TODO: Remove once we have a stack controller. - void positionWindowContainerAt(int position) { - mWindowContainerController.positionAt(position, mBounds, getOverrideConfiguration()); - } - - // TODO: Replace with moveChildToTop? - void moveWindowContainerToTop(boolean includingParents) { - if (mWindowContainerController != null) { - mWindowContainerController.moveToTop(includingParents); - } - } - - // TODO: Replace with moveChildToBottom? - void moveWindowContainerToBottom() { - if (mWindowContainerController != null) { - mWindowContainerController.moveToBottom(); - } - } - void getWindowContainerBounds(Rect bounds) { mWindowContainerController.getBounds(bounds); } @@ -556,7 +551,7 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta // Must reparent first in window manager to avoid a situation where AM can delete the // we are coming from in WM before we reparent because it became empty. - mWindowContainerController.reparent(stackId, position); + mWindowContainerController.reparent(newStack.getWindowContainerController(), position); final ActivityStack prevStack = mStack; prevStack.removeTask(this, reason, REMOVE_TASK_MODE_MOVING); @@ -691,6 +686,7 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta autoRemoveRecents = false; } mResizeMode = info.resizeMode; + mSupportsPictureInPicture = info.supportsPictureInPicture(); mIsOnTopLauncher = (info.flags & FLAG_ON_TOP_LAUNCHER) != 0; mLockTaskMode = info.lockTaskLaunchMode; mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0; @@ -1301,9 +1297,21 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta return mTaskToReturnTo == HOME_ACTIVITY_TYPE; } + private boolean isResizeable(boolean checkSupportsPip) { + return (mService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode) + || (checkSupportsPip && mSupportsPictureInPicture)) && !mTemporarilyUnresizable; + } + boolean isResizeable() { - return (mService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode)) - && !mTemporarilyUnresizable; + return isResizeable(true /* checkSupportsPip */); + } + + boolean supportsSplitScreen() { + // A task can not be docked even if it is considered resizeable because it only supports + // picture-in-picture mode but has a non-resizeable resizeMode + return mService.mSupportsSplitScreenMultiWindow + && isResizeable(false /* checkSupportsPip */) + && !ActivityInfo.isPreserveOrientationMode(mResizeMode); } /** @@ -1329,11 +1337,6 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta return isHomeTask() && mIsOnTopLauncher; } - boolean canGoInDockedStack() { - return isResizeable() && mService.mSupportsSplitScreenMultiWindow && - !ActivityInfo.isPreserveOrientationMode(mResizeMode); - } - /** * Find the activity in the history stack within the given task. Returns * the index within the history at which it's found, or < 0 if not found. @@ -1402,6 +1405,9 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta } lastTaskDescription = new TaskDescription(label, null, iconFilename, colorPrimary, colorBackground); + if (mWindowContainerController != null) { + mWindowContainerController.setTaskDescription(lastTaskDescription); + } // Update the task affiliation color if we are the parent of the group if (taskId == mAffiliatedTaskId) { mAffiliatedTaskColor = lastTaskDescription.getPrimaryColor(); @@ -1479,6 +1485,8 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta out.attribute(null, ATTR_CALLING_UID, String.valueOf(mCallingUid)); out.attribute(null, ATTR_CALLING_PACKAGE, mCallingPackage == null ? "" : mCallingPackage); out.attribute(null, ATTR_RESIZE_MODE, String.valueOf(mResizeMode)); + out.attribute(null, ATTR_SUPPORTS_PICTURE_IN_PICTURE, + String.valueOf(mSupportsPictureInPicture)); out.attribute(null, ATTR_PRIVILEGED, String.valueOf(mPrivileged)); if (mLastNonFullscreenBounds != null) { out.attribute( @@ -1549,6 +1557,7 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta int callingUid = -1; String callingPackage = ""; int resizeMode = RESIZE_MODE_FORCE_RESIZEABLE; + boolean supportsPictureInPicture = false; boolean privileged = false; Rect bounds = null; int minWidth = INVALID_MIN_SIZE; @@ -1615,6 +1624,8 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta callingPackage = attrValue; } else if (ATTR_RESIZE_MODE.equals(attrName)) { resizeMode = Integer.parseInt(attrValue); + } else if (ATTR_SUPPORTS_PICTURE_IN_PICTURE.equals(attrName)) { + supportsPictureInPicture = Boolean.parseBoolean(attrValue); } else if (ATTR_PRIVILEGED.equals(attrName)) { privileged = Boolean.parseBoolean(attrValue); } else if (ATTR_NON_FULLSCREEN_BOUNDS.equals(attrName)) { @@ -1687,6 +1698,15 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta if (taskType == HOME_ACTIVITY_TYPE && resizeMode == RESIZE_MODE_RESIZEABLE) { resizeMode = RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; } + } else { + // This activity has previously marked itself explicitly as both resizeable and + // supporting picture-in-picture. Since there is no longer a requirement for + // picture-in-picture activities to be resizeable, we can mark this simply as + // resizeable and supporting picture-in-picture separately. + if (resizeMode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED) { + resizeMode = RESIZE_MODE_RESIZEABLE; + supportsPictureInPicture = true; + } } final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent, @@ -1694,8 +1714,9 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta autoRemoveRecents, askedCompatMode, taskType, userId, effectiveUid, lastDescription, activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity, taskDescription, thumbnailInfo, taskAffiliation, prevTaskId, nextTaskId, - taskAffiliationColor, callingUid, callingPackage, resizeMode, privileged, - realActivitySuspended, userSetupComplete, minWidth, minHeight); + taskAffiliationColor, callingUid, callingPackage, resizeMode, + supportsPictureInPicture, privileged, realActivitySuspended, userSetupComplete, + minWidth, minHeight); task.updateOverrideConfiguration(bounds); for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) { @@ -1981,6 +2002,15 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta return rootAffinity != null && getStackId() != PINNED_STACK_ID; } + void addStartingWindowsForVisibleActivities(boolean taskSwitch) { + for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = mActivities.get(activityNdx); + if (r.visible) { + r.showStartingWindow(null /* prev */, false /* newTask */, taskSwitch); + } + } + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("userId="); pw.print(userId); pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid); @@ -2072,6 +2102,7 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta pw.print(prefix); pw.print("stackId="); pw.println(getStackId()); pw.print(prefix + "hasBeenVisible=" + hasBeenVisible); pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode)); + pw.print(" mSupportsPictureInPicture=" + mSupportsPictureInPicture); pw.print(" isResizeable=" + isResizeable()); pw.print(" firstActiveTime=" + lastActiveTime); pw.print(" lastActiveTime=" + lastActiveTime); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index df5f01d47cf7..31ef94fb5381 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -5932,7 +5932,7 @@ public class AudioService extends IAudioService.Stub * the whether any exposes the FLAG_ENABLE_ACCESSIBILITY_VOLUME flag * - set to false to listen to when accessibility services are started (e.g. "TalkBack started") */ - private static final boolean USE_FLAG_ENABLE_ACCESSIBILITY_VOLUME = true; + private static final boolean USE_FLAG_ENABLE_ACCESSIBILITY_VOLUME = false; private void initA11yMonitoring() { final AccessibilityManager accessibilityManager = @@ -6502,6 +6502,35 @@ public class AudioService extends IAudioService.Stub return mRecordMonitor.getActiveRecordingConfigurations(); } + public void disableRingtoneSync() { + final int callingUserId = UserHandle.getCallingUserId(); + final long token = Binder.clearCallingIdentity(); + try { + UserManager userManager = UserManager.get(mContext); + + // Disable the sync setting + Settings.Secure.putIntForUser(mContentResolver, + Settings.Secure.SYNC_PARENT_SOUNDS, 0 /* false */, callingUserId); + + UserInfo parentInfo = userManager.getProfileParent(callingUserId); + if (parentInfo != null && parentInfo.id != callingUserId) { + // This is a managed profile, so we clone the ringtones from the parent profile + cloneRingtoneSetting(callingUserId, parentInfo.id, Settings.System.RINGTONE); + cloneRingtoneSetting(callingUserId, parentInfo.id, + Settings.System.NOTIFICATION_SOUND); + cloneRingtoneSetting(callingUserId, parentInfo.id, Settings.System.ALARM_ALERT); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private void cloneRingtoneSetting(int userId, int parentId, String ringtoneSetting) { + String parentSetting = Settings.System.getStringForUser(mContentResolver, ringtoneSetting, + parentId); + Settings.System.putStringForUser(mContentResolver, ringtoneSetting, parentSetting, userId); + } + //====================== // Audio playback notification //====================== diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index e84bf40b344e..b0e45097aff6 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -975,8 +975,6 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering private final ArrayList<TetherInterfaceStateMachine> mNotifyList; private final IPv6TetheringCoordinator mIPv6TetheringCoordinator; - private int mPreviousMobileType = ConnectivityManager.TYPE_NONE; - private static final int UPSTREAM_SETTLE_TIME_MS = 10000; TetherMasterSM(String name, Looper looper) { @@ -1010,43 +1008,14 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering return false; } - protected boolean requestUpstreamMobileConnection(int apnType) { - if (apnType == ConnectivityManager.TYPE_NONE) { return false; } - - if (apnType != mPreviousMobileType) { - // Unregister any previous mobile upstream callback because - // this request, if any, will be different. - unrequestUpstreamMobileConnection(); - } - - if (mUpstreamNetworkMonitor.mobileNetworkRequested()) { - // Looks like we already filed a request for this apnType. - return true; - } - - switch (apnType) { - case ConnectivityManager.TYPE_MOBILE_DUN: - case ConnectivityManager.TYPE_MOBILE: - case ConnectivityManager.TYPE_MOBILE_HIPRI: - mPreviousMobileType = apnType; - break; - default: - return false; - } - - // TODO: Replace this with a call to pass the current tethering - // configuration to mUpstreamNetworkMonitor and let it handle - // choosing APN type accordingly. - mUpstreamNetworkMonitor.updateMobileRequiresDun( - apnType == ConnectivityManager.TYPE_MOBILE_DUN); - + protected boolean requestUpstreamMobileConnection() { + mUpstreamNetworkMonitor.updateMobileRequiresDun(mConfig.isDunRequired); mUpstreamNetworkMonitor.registerMobileNetworkRequest(); return true; } protected void unrequestUpstreamMobileConnection() { mUpstreamNetworkMonitor.releaseMobileNetworkRequest(); - mPreviousMobileType = ConnectivityManager.TYPE_NONE; } protected boolean turnOnMasterTetherSettings() { @@ -1128,11 +1097,10 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering case ConnectivityManager.TYPE_MOBILE_DUN: case ConnectivityManager.TYPE_MOBILE_HIPRI: // If we're on DUN, put our own grab on it. - requestUpstreamMobileConnection(upType); + requestUpstreamMobileConnection(); break; case ConnectivityManager.TYPE_NONE: - if (tryCell && - requestUpstreamMobileConnection(preferredUpstreamMobileApn)) { + if (tryCell && requestUpstreamMobileConnection()) { // We think mobile should be coming up; don't set a retry. } else { sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS); diff --git a/services/core/java/com/android/server/display/NightDisplayService.java b/services/core/java/com/android/server/display/NightDisplayService.java index 0ec48b992b1e..0357b1b42935 100644 --- a/services/core/java/com/android/server/display/NightDisplayService.java +++ b/services/core/java/com/android/server/display/NightDisplayService.java @@ -75,6 +75,11 @@ public final class NightDisplayService extends SystemService }; /** + * The transition time, in milliseconds, for Night Display to turn on/off. + */ + private static final long TRANSITION_DURATION = 3000L; + + /** * The identity matrix, used if one of the given matrices is {@code null}. */ private static final float[] MATRIX_IDENTITY = new float[16]; @@ -285,8 +290,7 @@ public final class NightDisplayService extends SystemService mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR, from == null ? MATRIX_IDENTITY : from, to == null ? MATRIX_IDENTITY : to); - mColorMatrixAnimator.setDuration(getContext().getResources() - .getInteger(android.R.integer.config_longAnimTime)); + mColorMatrixAnimator.setDuration(TRANSITION_DURATION); mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator( getContext(), android.R.interpolator.fast_out_slow_in)); mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 9b37f12e468a..3bf95efaef11 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -17,6 +17,7 @@ package com.android.server.media; import android.Manifest; +import android.annotation.NonNull; import android.app.Activity; import android.app.ActivityManager; import android.app.KeyguardManager; @@ -36,10 +37,13 @@ import android.media.AudioSystem; import android.media.IAudioService; import android.media.IRemoteVolumeController; import android.media.session.IActiveSessionsListener; +import android.media.session.IOnMediaKeyListener; +import android.media.session.IOnVolumeKeyLongPressListener; import android.media.session.ISession; import android.media.session.ISessionCallback; import android.media.session.ISessionManager; import android.media.session.MediaSession; +import android.media.session.MediaSessionManager; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -78,10 +82,11 @@ import java.util.List; public class MediaSessionService extends SystemService implements Monitor { private static final String TAG = "MediaSessionService"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - // Leave log for media key event always. - private static final boolean DEBUG_MEDIA_KEY_EVENT = DEBUG || true; + // Leave log for key event always. + private static final boolean DEBUG_KEY_EVENT = true; private static final int WAKELOCK_TIMEOUT = 5000; + private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000; /* package */final IBinder mICallback = new Binder(); @@ -523,6 +528,14 @@ public class MediaSessionService extends SystemService implements Monitor { } } + private String getCallingPackageName(int uid) { + String[] packages = getContext().getPackageManager().getPackagesForUid(uid); + if (packages != null && packages.length > 0) { + return packages[0]; + } + return ""; + } + /** * Information about a particular user. The contents of this object is * guarded by mLock. @@ -534,6 +547,15 @@ public class MediaSessionService extends SystemService implements Monitor { private PendingIntent mLastMediaButtonReceiver; private ComponentName mRestoredMediaButtonReceiver; + private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener; + private int mOnVolumeKeyLongPressListenerUid; + private KeyEvent mInitialDownVolumeKeyEvent; + private int mInitialDownVolumeStream; + private boolean mInitialDownMusicOnly; + + private IOnMediaKeyListener mOnMediaKeyListener; + private int mOnMediaKeyListenerUid; + public UserRecord(Context context, int userId) { mContext = context; mUserId = userId; @@ -564,6 +586,12 @@ public class MediaSessionService extends SystemService implements Monitor { String indent = prefix + " "; pw.println(indent + "MediaButtonReceiver:" + mLastMediaButtonReceiver); pw.println(indent + "Restored ButtonReceiver:" + mRestoredMediaButtonReceiver); + pw.println(indent + "Volume key long-press listener:" + mOnVolumeKeyLongPressListener); + pw.println(indent + "Volume key long-press listener package:" + + getCallingPackageName(mOnVolumeKeyLongPressListenerUid)); + pw.println(indent + "Media key listener: " + mOnMediaKeyListener); + pw.println(indent + "Media key listener package: " + + getCallingPackageName(mOnMediaKeyListenerUid)); int size = mSessions.size(); pw.println(indent + size + " Sessions:"); for (int i = 0; i < size; i++) { @@ -769,28 +797,190 @@ public class MediaSessionService extends SystemService implements Monitor { } synchronized (mLock) { - // If we don't have a media button receiver to fall back on - // include non-playing sessions for dispatching - boolean useNotPlayingSessions = true; - for (int userId : mCurrentUserIdList) { - UserRecord ur = mUserRecords.get(userId); - if (ur.mLastMediaButtonReceiver != null - || ur.mRestoredMediaButtonReceiver != null) { - useNotPlayingSessions = false; - break; + if (!isGlobalPriorityActive() && isVoiceKey(keyEvent.getKeyCode())) { + handleVoiceKeyEventLocked(keyEvent, needWakeLock); + } else { + dispatchMediaKeyEventLocked(keyEvent, needWakeLock, true); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void setOnVolumeKeyLongPressListener(IOnVolumeKeyLongPressListener listener) { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + try { + // Enforce SET_VOLUME_KEY_LONG_PRESS_LISTENER permission. + if (getContext().checkPermission( + android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER, pid, uid) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Must hold the SET_VOLUME_KEY_LONG_PRESS_LISTENER" + + " permission."); + } + + synchronized (mLock) { + UserRecord user = mUserRecords.get(UserHandle.getUserId(uid)); + if (user.mOnVolumeKeyLongPressListener != null && + user.mOnVolumeKeyLongPressListenerUid != uid) { + Log.w(TAG, "Volume key long-press listener cannot be reset by another app"); + return; + } + + user.mOnVolumeKeyLongPressListener = listener; + user.mOnVolumeKeyLongPressListenerUid = uid; + + Log.d(TAG, "Volume key long-press listener " + + listener + " is set by " + getCallingPackageName(uid)); + + if (user.mOnVolumeKeyLongPressListener != null) { + try { + user.mOnVolumeKeyLongPressListener.asBinder().linkToDeath( + new IBinder.DeathRecipient() { + @Override + public void binderDied() { + synchronized (mLock) { + user.mOnVolumeKeyLongPressListener = null; + } + } + }, 0); + } catch (RemoteException e) { + Log.w(TAG, "Failed to set death recipient " + + user.mOnVolumeKeyLongPressListener); + user.mOnVolumeKeyLongPressListener = null; } } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } - if (DEBUG) { - Log.d(TAG, "dispatchMediaKeyEvent, useNotPlayingSessions=" - + useNotPlayingSessions); + @Override + public void setOnMediaKeyListener(IOnMediaKeyListener listener) { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + try { + // Enforce SET_MEDIA_KEY_LISTENER permission. + if (getContext().checkPermission( + android.Manifest.permission.SET_MEDIA_KEY_LISTENER, pid, uid) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Must hold the SET_MEDIA_KEY_LISTENER" + + " permission."); + } + + synchronized (mLock) { + int userId = UserHandle.getUserId(uid); + UserRecord user = mUserRecords.get(userId); + if (user.mOnMediaKeyListener != null && user.mOnMediaKeyListenerUid != uid) { + Log.w(TAG, "Media key listener cannot be reset by another app"); + return; + } + + user.mOnMediaKeyListener = listener; + user.mOnMediaKeyListenerUid = uid; + + Log.d(TAG, "Media key listener " + user.mOnMediaKeyListener + + " is set by " + getCallingPackageName(uid)); + + if (user.mOnMediaKeyListener != null) { + try { + user.mOnMediaKeyListener.asBinder().linkToDeath( + new IBinder.DeathRecipient() { + @Override + public void binderDied() { + synchronized (mLock) { + user.mOnMediaKeyListener = null; + } + } + }, 0); + } catch (RemoteException e) { + Log.w(TAG, "Failed to set death recipient " + user.mOnMediaKeyListener); + user.mOnMediaKeyListener = null; + } } - MediaSessionRecord session = mPriorityStack.getDefaultMediaButtonSession( - mCurrentUserIdList, useNotPlayingSessions); - if (isVoiceKey(keyEvent.getKeyCode())) { - handleVoiceKeyEventLocked(keyEvent, needWakeLock, session); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** + * Handles the dispatching of the volume button events to one of the + * registered listeners. If there's a volume key long-press listener and + * there's no active global priority session, long-pressess will be sent to the + * long-press listener instead of adjusting volume. + * + * @param keyEvent a non-null KeyEvent whose key code is one of the + * {@link KeyEvent#KEYCODE_VOLUME_UP}, + * {@link KeyEvent#KEYCODE_VOLUME_DOWN}, + * or {@link KeyEvent#KEYCODE_VOLUME_MUTE}. + * @param stream stream type to adjust volume. + * @param musicOnly true if both UI nor haptic feedback aren't needed when adjust volume. + */ + @Override + public void dispatchVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) { + if (keyEvent == null || + (keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_UP + && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_DOWN + && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_MUTE)) { + Log.w(TAG, "Attempted to dispatch null or non-volume key event."); + return; + } + + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + + if (DEBUG) { + Log.d(TAG, "dispatchVolumeKeyEvent, pid=" + pid + ", uid=" + uid + ", event=" + + keyEvent); + } + + try { + synchronized (mLock) { + // Only consider full user. + UserRecord user = mUserRecords.get(mCurrentUserIdList.get(0)); + + if (mPriorityStack.isGlobalPriorityActive() + || user.mOnVolumeKeyLongPressListener == null) { + dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly); } else { - dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session); + // TODO: Consider the case when both volume up and down keys are pressed + // at the same time. + if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) { + if (keyEvent.getRepeatCount() == 0) { + user.mInitialDownVolumeKeyEvent = keyEvent; + user.mInitialDownVolumeStream = stream; + user.mInitialDownMusicOnly = musicOnly; + } + if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) { + if (user.mInitialDownVolumeKeyEvent != null) { + dispatchVolumeKeyLongPressLocked( + user.mInitialDownVolumeKeyEvent); + // Mark that the key is already handled. + user.mInitialDownVolumeKeyEvent = null; + } + dispatchVolumeKeyLongPressLocked(keyEvent); + } + } else { // if up + if (user.mInitialDownVolumeKeyEvent != null + && user.mInitialDownVolumeKeyEvent.getDownTime() + == keyEvent.getDownTime()) { + // Short-press. Should change volume. + dispatchVolumeKeyEventLocked( + user.mInitialDownVolumeKeyEvent, + user.mInitialDownVolumeStream, + user.mInitialDownMusicOnly); + dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly); + } else { + dispatchVolumeKeyLongPressLocked(keyEvent); + } + } } } } finally { @@ -798,14 +988,66 @@ public class MediaSessionService extends SystemService implements Monitor { } } + private void dispatchVolumeKeyLongPressLocked(KeyEvent keyEvent) { + // Only consider full user. + UserRecord user = mUserRecords.get(mCurrentUserIdList.get(0)); + try { + user.mOnVolumeKeyLongPressListener.onVolumeKeyLongPress(keyEvent); + } catch (RemoteException e) { + Log.w(TAG, "Failed to send " + keyEvent + " to volume key long-press listener"); + } + } + + private void dispatchVolumeKeyEventLocked( + KeyEvent keyEvent, int stream, boolean musicOnly) { + boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN; + boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP; + int direction = 0; + boolean isMute = false; + switch (keyEvent.getKeyCode()) { + case KeyEvent.KEYCODE_VOLUME_UP: + direction = AudioManager.ADJUST_RAISE; + break; + case KeyEvent.KEYCODE_VOLUME_DOWN: + direction = AudioManager.ADJUST_LOWER; + break; + case KeyEvent.KEYCODE_VOLUME_MUTE: + isMute = true; + break; + } + if (down || up) { + int flags = AudioManager.FLAG_FROM_KEY; + if (musicOnly) { + // This flag is used when the screen is off to only affect active media. + flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY; + } else { + // These flags are consistent with the home screen + if (up) { + flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE; + } else { + flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE; + } + } + if (direction != 0) { + // If this is action up we want to send a beep for non-music events + if (up) { + direction = 0; + } + dispatchAdjustVolumeLocked(stream, direction, flags); + } else if (isMute) { + if (down && keyEvent.getRepeatCount() == 0) { + dispatchAdjustVolumeLocked(stream, AudioManager.ADJUST_TOGGLE_MUTE, flags); + } + } + } + } + @Override public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - MediaSessionRecord session = mPriorityStack - .getDefaultVolumeSession(mCurrentUserIdList); - dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session); + dispatchAdjustVolumeLocked(suggestedStream, delta, flags); } } finally { Binder.restoreCallingIdentity(token); @@ -881,8 +1123,9 @@ public class MediaSessionService extends SystemService implements Monitor { return resolvedUserId; } - private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags, - MediaSessionRecord session) { + private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags) { + MediaSessionRecord session = mPriorityStack.getDefaultVolumeSession(mCurrentUserIdList); + boolean preferSuggestedStream = false; if (isValidLocalStreamType(suggestedStream) && AudioSystem.isStreamActive(suggestedStream, 0)) { @@ -925,13 +1168,7 @@ public class MediaSessionService extends SystemService implements Monitor { } } - private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock, - MediaSessionRecord session) { - if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) { - // If the phone app has priority just give it the event - dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session); - return; - } + private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) { int action = keyEvent.getAction(); boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0; if (action == KeyEvent.ACTION_DOWN) { @@ -948,24 +1185,60 @@ public class MediaSessionService extends SystemService implements Monitor { if (!mVoiceButtonHandled && !keyEvent.isCanceled()) { // Resend the down then send this event through KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN); - dispatchMediaKeyEventLocked(downEvent, needWakeLock, session); - dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session); + dispatchMediaKeyEventLocked(downEvent, needWakeLock, true); + dispatchMediaKeyEventLocked(keyEvent, needWakeLock, true); } } } } private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock, - MediaSessionRecord session) { + boolean checkMediaKeyListener) { + // If we don't have a media button receiver to fall back on + // include non-playing sessions for dispatching. + boolean useNotPlayingSessions = true; + for (int userId : mCurrentUserIdList) { + UserRecord ur = mUserRecords.get(userId); + if (ur.mLastMediaButtonReceiver != null + || ur.mRestoredMediaButtonReceiver != null) { + useNotPlayingSessions = false; + break; + } + } + if (DEBUG) { + Log.d(TAG, "dispatchMediaKeyEvent, useNotPlayingSessions=" + + useNotPlayingSessions); + } + + MediaSessionRecord session = mPriorityStack.getDefaultMediaButtonSession( + mCurrentUserIdList, useNotPlayingSessions); + + if ((session == null + || !session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) + && checkMediaKeyListener) { + // Only consider full user. + UserRecord user = mUserRecords.get(mCurrentUserIdList.get(0)); + if (user.mOnMediaKeyListener != null) { + if (DEBUG_KEY_EVENT) { + Log.d(TAG, "Send " + keyEvent + " to media key listener"); + } + try { + user.mOnMediaKeyListener.onMediaKey(keyEvent, + new MediaKeyListenerResultReceiver(keyEvent, needWakeLock)); + return; + } catch (RemoteException e) { + Log.w(TAG, "Failed to send " + keyEvent + " to media key listener"); + } + } + } if (session != null) { - if (DEBUG_MEDIA_KEY_EVENT) { + if (DEBUG_KEY_EVENT) { Log.d(TAG, "Sending " + keyEvent + " to " + session); } if (needWakeLock) { mKeyEventReceiver.aquireWakeLockLocked(); } - // If we don't need a wakelock use -1 as the id so we - // won't release it later + // If we don't need a wakelock use -1 as the id so we won't release it later. session.sendMediaButton(keyEvent, needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, mKeyEventReceiver, Process.SYSTEM_UID, @@ -986,7 +1259,7 @@ public class MediaSessionService extends SystemService implements Monitor { mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); try { if (user.mLastMediaButtonReceiver != null) { - if (DEBUG_MEDIA_KEY_EVENT) { + if (DEBUG_KEY_EVENT) { Log.d(TAG, "Sending " + keyEvent + " to the last known pendingIntent " + user.mLastMediaButtonReceiver); @@ -995,7 +1268,7 @@ public class MediaSessionService extends SystemService implements Monitor { needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, mediaButtonIntent, mKeyEventReceiver, mHandler); } else { - if (DEBUG_MEDIA_KEY_EVENT) { + if (DEBUG_KEY_EVENT) { Log.d(TAG, "Sending " + keyEvent + " to the restored intent " + user.mRestoredMediaButtonReceiver); } @@ -1081,6 +1354,46 @@ public class MediaSessionService extends SystemService implements Monitor { && streamType <= AudioManager.STREAM_NOTIFICATION; } + private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable { + private KeyEvent mKeyEvent; + private boolean mNeedWakeLock; + private boolean mHandled; + + private MediaKeyListenerResultReceiver(KeyEvent keyEvent, boolean needWakeLock) { + super(mHandler); + mHandler.postDelayed(this, MEDIA_KEY_LISTENER_TIMEOUT); + mKeyEvent = keyEvent; + mNeedWakeLock = needWakeLock; + } + + @Override + public void run() { + Log.d(TAG, "The media key listener is timed-out for " + mKeyEvent); + dispatchMediaKeyEvent(); + } + + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + if (resultCode == MediaSessionManager.RESULT_MEDIA_KEY_HANDLED) { + mHandled = true; + mHandler.removeCallbacks(this); + return; + } + dispatchMediaKeyEvent(); + } + + private void dispatchMediaKeyEvent() { + if (mHandled) { + return; + } + mHandled = true; + mHandler.removeCallbacks(this); + synchronized (mLock) { + dispatchMediaKeyEventLocked(mKeyEvent, mNeedWakeLock, false); + } + } + } + private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler); class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable, diff --git a/services/core/java/com/android/server/notification/BadgeExtractor.java b/services/core/java/com/android/server/notification/BadgeExtractor.java new file mode 100644 index 000000000000..4795fbf0ba3c --- /dev/null +++ b/services/core/java/com/android/server/notification/BadgeExtractor.java @@ -0,0 +1,59 @@ +/** +* Copyright (C) 2017 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package com.android.server.notification; + +import android.content.Context; +import android.util.Slog; + +/** + * Determines whether a badge should be shown for this notification + */ +public class BadgeExtractor implements NotificationSignalExtractor { + private static final String TAG = "BadgeExtractor"; + private static final boolean DBG = false; + + private RankingConfig mConfig; + + public void initialize(Context ctx, NotificationUsageStats usageStats) { + if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + "."); + } + + public RankingReconsideration process(NotificationRecord record) { + if (record == null || record.getNotification() == null) { + if (DBG) Slog.d(TAG, "skipping empty notification"); + return null; + } + + if (mConfig == null) { + if (DBG) Slog.d(TAG, "missing config"); + return null; + } + boolean appCanShowBadge = + mConfig.canShowBadge(record.sbn.getPackageName(), record.sbn.getUid()); + if (!appCanShowBadge) { + record.setShowBadge(false); + } else { + record.setShowBadge(record.getChannel().canShowBadge() && appCanShowBadge); + } + + return null; + } + + @Override + public void setConfig(RankingConfig config) { + mConfig = config; + } +} diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 168884dbe189..72faa81ab249 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1243,6 +1243,35 @@ public class NotificationManagerService extends SystemService { sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); } + private void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel, + boolean fromAssistant) { + if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) { + // cancel + cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true, + UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, + null); + } + if (fromAssistant) { + mRankingHelper.updateNotificationChannelFromAssistant(pkg, uid, channel); + } else { + mRankingHelper.updateNotificationChannel(pkg, uid, channel); + } + + synchronized (mNotificationList) { + final int N = mNotificationList.size(); + for (int i = N - 1; i >= 0; --i) { + NotificationRecord r = mNotificationList.get(i); + if (channel.getId() != null && channel.getId().equals(r.getChannel().getId())) { + r.updateNotificationChannel(mRankingHelper.getNotificationChannel( + r.sbn.getPackageName(), r.getUser().getIdentifier(), + channel.getId(), false)); + } + } + } + mRankingHandler.requestSort(true); + savePolicyFile(); + } + private ArrayList<ComponentName> getSuppressors() { ArrayList<ComponentName> names = new ArrayList<ComponentName>(); for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) { @@ -1521,6 +1550,19 @@ public class NotificationManagerService extends SystemService { } @Override + public boolean canShowBadge(String pkg, int uid) { + checkCallerIsSystem(); + return mRankingHelper.canShowBadge(pkg, uid); + } + + @Override + public void setShowBadge(String pkg, int uid, boolean showBadge) { + checkCallerIsSystem(); + mRankingHelper.setShowBadge(pkg, uid, showBadge); + savePolicyFile(); + } + + @Override public void createNotificationChannels(String pkg, ParceledListSlice channelsList) throws RemoteException { checkCallerIsSystemOrSameApp(pkg); @@ -1566,15 +1608,8 @@ public class NotificationManagerService extends SystemService { public void updateNotificationChannelForPackage(String pkg, int uid, NotificationChannel channel) { enforceSystemOrSystemUI("Caller not system or systemui"); - if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) { - // cancel - cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true, - UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, - null); - } - mRankingHelper.updateNotificationChannel(pkg, uid, channel); - mRankingHandler.requestSort(true); - savePolicyFile(); + Preconditions.checkNotNull(channel); + updateNotificationChannelInt(pkg, uid, channel, false); } @Override @@ -1701,7 +1736,6 @@ public class NotificationManagerService extends SystemService { return new StatusBarNotification( sbn.getPackageName(), sbn.getOpPkg(), - sbn.getNotificationChannel(), sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), sbn.getNotification().clone(), sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); @@ -1910,31 +1944,16 @@ public class NotificationManagerService extends SystemService { } /** - * Allow an INotificationListener to snooze a single notification. - * - * @param token The binder for the listener, to check that the caller is allowed - */ - @Override - public void snoozeNotificationFromListener(INotificationListener token, String key) { - long identity = Binder.clearCallingIdentity(); - try { - final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); - snoozeNotificationInt(key, SNOOZE_UNTIL_UNSPECIFIED, null, info); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Allow an INotificationListener to un-snooze a single notification. + * Allows the notification assistant to un-snooze a single notification. * - * @param token The binder for the listener, to check that the caller is allowed + * @param token The binder for the assistant, to check that the caller is allowed */ @Override - public void unsnoozeNotificationFromListener(INotificationListener token, String key) { + public void unsnoozeNotificationFromAssistant(INotificationListener token, String key) { long identity = Binder.clearCallingIdentity(); try { - final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); + final ManagedServiceInfo info = + mNotificationAssistants.checkServiceTokenLocked(token); unsnoozeNotificationInt(key, info); } finally { Binder.restoreCallingIdentity(identity); @@ -2005,6 +2024,36 @@ public class NotificationManagerService extends SystemService { } } + /** + * Allow an INotificationListener to request the list of outstanding snoozed notifications + * seen by the current user. Useful when starting up, after which point the listener + * callbacks should be used. + * + * @param token The binder for the listener, to check that the caller is allowed + * @returns The return value will contain the notifications specified in keys, in that + * order, or if keys is null, all the notifications, in natural order. + */ + @Override + public ParceledListSlice<StatusBarNotification> getSnoozedNotificationsFromListener( + INotificationListener token, int trim) { + synchronized (mNotificationLock) { + final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); + List<NotificationRecord> snoozedRecords = mSnoozeHelper.getSnoozed(); + final int N = snoozedRecords.size(); + final ArrayList<StatusBarNotification> list = new ArrayList<>(N); + for (int i=0; i < N; i++) { + final NotificationRecord r = snoozedRecords.get(i); + if (r == null) continue; + StatusBarNotification sbn = r.sbn; + if (!isVisibleToListener(sbn, info)) continue; + StatusBarNotification sbnToSend = + (trim == TRIM_FULL) ? sbn : sbn.cloneLight(); + list.add(sbnToSend); + } + return new ParceledListSlice<>(list); + } + } + @Override public void requestHintsFromListener(INotificationListener token, int hints) { final long identity = Binder.clearCallingIdentity(); @@ -2495,15 +2544,9 @@ public class NotificationManagerService extends SystemService { public void updateNotificationChannelFromAssistant(INotificationListener token, String pkg, NotificationChannel channel) throws RemoteException { ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token); - if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) { - // cancel - cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true, - info.userid, REASON_CHANNEL_BANNED, null); - } + Preconditions.checkNotNull(channel); int uid = mPackageManager.getPackageUid(pkg, 0, info.userid); - mRankingHelper.updateNotificationChannelFromAssistant(pkg, uid, channel); - mRankingHandler.requestSort(true); - savePolicyFile(); + updateNotificationChannelInt(pkg, uid, channel, true); } @Override @@ -2528,7 +2571,7 @@ public class NotificationManagerService extends SystemService { final ArrayList<SnoozeCriterion> snoozeCriterionList = adjustment.getSignals().getParcelableArrayList(Adjustment.KEY_SNOOZE_CRITERIA); if (!TextUtils.isEmpty(overrideChannelId)) { - n.setNotificationChannelOverride(mRankingHelper.getNotificationChannel( + n.updateNotificationChannel(mRankingHelper.getNotificationChannel( n.sbn.getPackageName(), n.sbn.getUid(), overrideChannelId, false /* includeDeleted */)); } @@ -2610,13 +2653,13 @@ public class NotificationManagerService extends SystemService { final StatusBarNotification summarySbn = new StatusBarNotification(adjustedSbn.getPackageName(), adjustedSbn.getOpPkg(), - adjustedSbn.getNotificationChannel(), Integer.MAX_VALUE, GroupHelper.AUTOGROUP_KEY, adjustedSbn.getUid(), adjustedSbn.getInitialPid(), summaryNotification, adjustedSbn.getUser(), GroupHelper.AUTOGROUP_KEY, System.currentTimeMillis()); - summaryRecord = new NotificationRecord(getContext(), summarySbn); + summaryRecord = new NotificationRecord(getContext(), summarySbn, + notificationRecord.getChannel()); summaries.put(pkg, summarySbn.getKey()); } } @@ -2882,7 +2925,7 @@ public class NotificationManagerService extends SystemService { final NotificationChannel channel = mRankingHelper.getNotificationChannelWithFallback(pkg, callingUid, notification.getChannel(), false /* includeDeleted */); final StatusBarNotification n = new StatusBarNotification( - pkg, opPkg, channel, id, tag, callingUid, callingPid, notification, + pkg, opPkg, id, tag, callingUid, callingPid, notification, user, null, System.currentTimeMillis()); // Limit the number of notifications that any given package except the android @@ -2946,7 +2989,7 @@ public class NotificationManagerService extends SystemService { Notification.PRIORITY_MAX); // setup local book-keeping - final NotificationRecord r = new NotificationRecord(getContext(), n); + final NotificationRecord r = new NotificationRecord(getContext(), n, channel); synchronized (mNotificationLock) { mEnqueuedNotifications.add(r); } @@ -3490,14 +3533,18 @@ public class NotificationManagerService extends SystemService { boolean forceUpdate = ((Boolean) msg.obj == null) ? false : (boolean) msg.obj; synchronized (mNotificationLock) { final int N = mNotificationList.size(); + // Any field that can change via one of the extractors or by the assistant + // needs to be added here. ArrayList<String> orderBefore = new ArrayList<String>(N); ArrayList<String> groupOverrideBefore = new ArrayList<>(N); int[] visibilities = new int[N]; + boolean[] showBadges = new boolean[N]; for (int i = 0; i < N; i++) { final NotificationRecord r = mNotificationList.get(i); orderBefore.add(r.getKey()); groupOverrideBefore.add(r.sbn.getGroupKey()); visibilities[i] = r.getPackageVisibilityOverride(); + showBadges[i] = r.canShowBadge(); mRankingHelper.extractSignals(r); } mRankingHelper.sort(mNotificationList); @@ -3506,7 +3553,8 @@ public class NotificationManagerService extends SystemService { if (forceUpdate || !orderBefore.get(i).equals(r.getKey()) || visibilities[i] != r.getPackageVisibilityOverride() - || !groupOverrideBefore.get(i).equals(r.sbn.getGroupKey())) { + || !groupOverrideBefore.get(i).equals(r.sbn.getGroupKey()) + || showBadges[i] != r.canShowBadge()) { scheduleSendRankingUpdate(); return; } @@ -3949,14 +3997,14 @@ public class NotificationManagerService extends SystemService { void snoozeNotificationInt(String key, long until, String snoozeCriterionId, ManagedServiceInfo listener) { String listenerName = listener == null ? null : listener.component.toShortString(); + if (until < System.currentTimeMillis() && snoozeCriterionId == null) { + return; + } // TODO: write to event log if (DBG) { Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, until, snoozeCriterionId, listenerName)); } - if (until != SNOOZE_UNTIL_UNSPECIFIED && until < System.currentTimeMillis()) { - return; - } // Needs to post so that it can cancel notifications not yet enqueued. mHandler.post(new Runnable() { @Override @@ -3969,8 +4017,6 @@ public class NotificationManagerService extends SystemService { if (snoozeCriterionId != null) { mNotificationAssistants.notifyAssistantSnoozedLocked(r.sbn, snoozeCriterionId); - } - if (until == SNOOZE_UNTIL_UNSPECIFIED) { mSnoozeHelper.snooze(r); } else { mSnoozeHelper.snooze(r, until); @@ -4246,9 +4292,10 @@ public class NotificationManagerService extends SystemService { Bundle visibilityOverrides = new Bundle(); Bundle suppressedVisualEffects = new Bundle(); Bundle explanation = new Bundle(); - Bundle overrideChannels = new Bundle(); + Bundle channels = new Bundle(); Bundle overridePeople = new Bundle(); Bundle snoozeCriteria = new Bundle(); + Bundle showBadge = new Bundle(); for (int i = 0; i < N; i++) { NotificationRecord record = mNotificationList.get(i); if (!isVisibleToListener(record.sbn, info)) { @@ -4270,9 +4317,10 @@ public class NotificationManagerService extends SystemService { visibilityOverrides.putInt(key, record.getPackageVisibilityOverride()); } overrideGroupKeys.putString(key, record.sbn.getOverrideGroupKey()); - overrideChannels.putParcelable(key, record.getChannel()); + channels.putParcelable(key, record.getChannel()); overridePeople.putStringArrayList(key, record.getPeopleOverride()); snoozeCriteria.putParcelableArrayList(key, record.getSnoozeCriteria()); + showBadge.putBoolean(key, record.canShowBadge()); } final int M = keys.size(); String[] keysAr = keys.toArray(new String[M]); @@ -4283,7 +4331,7 @@ public class NotificationManagerService extends SystemService { } return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides, suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys, - overrideChannels, overridePeople, snoozeCriteria); + channels, overridePeople, snoozeCriteria, showBadge); } private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) { diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index e8c3d97747d7..2a5a25f8e866 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -25,7 +25,6 @@ import android.app.Notification; import android.app.NotificationChannel; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.graphics.Bitmap; @@ -114,12 +113,14 @@ public final class NotificationRecord { private Uri mSound; private long[] mVibration; private AudioAttributes mAttributes; - private NotificationChannel mOverrideChannel; + private NotificationChannel mChannel; private ArrayList<String> mPeopleOverride; private ArrayList<SnoozeCriterion> mSnoozeCriteria; + private boolean mShowBadge; @VisibleForTesting - public NotificationRecord(Context context, StatusBarNotification sbn) + public NotificationRecord(Context context, StatusBarNotification sbn, + NotificationChannel channel) { this.sbn = sbn; mOriginalFlags = sbn.getNotification().flags; @@ -128,6 +129,7 @@ public final class NotificationRecord { mUpdateTimeMs = mCreationTimeMs; mContext = context; stats = new NotificationUsageStats.SingleNotificationStats(); + mChannel = channel; mPreChannelsNotification = isPreChannelsNotification(); mSound = calculateSound(); mVibration = calculateVibration(); @@ -154,7 +156,7 @@ public final class NotificationRecord { private Uri calculateSound() { final Notification n = sbn.getNotification(); - Uri sound = sbn.getNotificationChannel().getSound(); + Uri sound = mChannel.getSound(); if (mPreChannelsNotification && (getChannel().getUserLockedFields() & NotificationChannel.USER_LOCKED_SOUND) == 0) { @@ -386,7 +388,8 @@ public final class NotificationRecord { pw.println(prefix + " mSound= " + mSound); pw.println(prefix + " mVibration= " + mVibration); pw.println(prefix + " mAttributes= " + mAttributes); - pw.println(prefix + " overrideChannel=" + getChannel()); + pw.println(prefix + " mShowBadge=" + mShowBadge); + pw.println(prefix + " channel=" + getChannel()); if (getPeopleOverride() != null) { pw.println(prefix + " overridePeople= " + TextUtils.join(",", getPeopleOverride())); } @@ -640,16 +643,24 @@ public final class NotificationRecord { } public NotificationChannel getChannel() { - return mOverrideChannel == null ? sbn.getNotificationChannel() : mOverrideChannel; + return mChannel; } - protected void setNotificationChannelOverride(NotificationChannel channel) { - mOverrideChannel = channel; - if (mOverrideChannel != null) { + protected void updateNotificationChannel(NotificationChannel channel) { + if (channel != null) { + mChannel = channel; calculateImportance(); } } + public void setShowBadge(boolean showBadge) { + mShowBadge = showBadge; + } + + public boolean canShowBadge() { + return mShowBadge; + } + public Uri getSound() { return mSound; } diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index c2cef09e0599..492d5c639a7d 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -22,6 +22,8 @@ public interface RankingConfig { void setImportance(String packageName, int uid, int importance); int getImportance(String packageName, int uid); + void setShowBadge(String packageName, int uid, boolean showBadge); + boolean canShowBadge(String packageName, int uid); void createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp); diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index e44fb7f6b31c..1861bcb7df85 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -67,10 +67,12 @@ public class RankingHelper implements RankingConfig { private static final String ATT_PRIORITY = "priority"; private static final String ATT_VISIBILITY = "visibility"; private static final String ATT_IMPORTANCE = "importance"; + private static final String ATT_SHOW_BADGE = "show_badge"; private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT; private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE; private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED; + private static final boolean DEFAULT_SHOW_BADGE = true; private final NotificationSignalExtractor[] mSignalExtractors; private final NotificationComparator mPreliminaryComparator; @@ -169,7 +171,8 @@ public class RankingHelper implements RankingConfig { Record r = getOrCreateRecord(name, uid, safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE), safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY), - safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY)); + safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY), + safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE)); // Channels final int innerDepth = parser.getDepth(); @@ -215,11 +218,11 @@ public class RankingHelper implements RankingConfig { private Record getOrCreateRecord(String pkg, int uid) { return getOrCreateRecord(pkg, uid, - DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY); + DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE); } private Record getOrCreateRecord(String pkg, int uid, int importance, int priority, - int visibility) { + int visibility, boolean showBadge) { final String key = recordKey(pkg, uid); Record r = (uid == Record.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) : mRecords.get(key); if (r == null) { @@ -229,6 +232,7 @@ public class RankingHelper implements RankingConfig { r.importance = importance; r.priority = priority; r.visibility = visibility; + r.showBadge = showBadge; createDefaultChannelIfMissing(r); if (r.uid == Record.UNKNOWN_UID) { mRestoredWithoutUids.put(pkg, r); @@ -298,7 +302,7 @@ public class RankingHelper implements RankingConfig { } final boolean hasNonDefaultSettings = r.importance != DEFAULT_IMPORTANCE || r.priority != DEFAULT_PRIORITY || r.visibility != DEFAULT_VISIBILITY - || r.channels.size() > 0; + || r.showBadge != DEFAULT_SHOW_BADGE || r.channels.size() > 0; if (hasNonDefaultSettings) { out.startTag(null, TAG_PACKAGE); out.attribute(null, ATT_NAME, r.pkg); @@ -311,6 +315,7 @@ public class RankingHelper implements RankingConfig { if (r.visibility != DEFAULT_VISIBILITY) { out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility)); } + out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge)); if (!forBackup) { out.attribute(null, ATT_UID, Integer.toString(r.uid)); @@ -396,6 +401,12 @@ public class RankingHelper implements RankingConfig { return Collections.binarySearch(notificationList, target, mFinalComparator); } + private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) { + final String value = parser.getAttributeValue(null, att); + if (TextUtils.isEmpty(value)) return defValue; + return Boolean.parseBoolean(value); + } + private static int safeInt(XmlPullParser parser, String att, int defValue) { final String val = parser.getAttributeValue(null, att); return tryParseInt(val, defValue); @@ -419,6 +430,17 @@ public class RankingHelper implements RankingConfig { } @Override + public boolean canShowBadge(String packageName, int uid) { + return getOrCreateRecord(packageName, uid).showBadge; + } + + @Override + public void setShowBadge(String packageName, int uid, boolean showBadge) { + getOrCreateRecord(packageName, uid).showBadge = showBadge; + updateConfig(); + } + + @Override public void createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp) { Preconditions.checkNotNull(pkg); @@ -454,6 +476,9 @@ public class RankingHelper implements RankingConfig { if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) { channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE); } + if (!r.showBadge) { + channel.setShowBadge(false); + } r.channels.put(channel.getId(), channel); updateConfig(); } @@ -672,7 +697,7 @@ public class RankingHelper implements RankingConfig { final Record r = records.valueAt(i); if (filter == null || filter.matches(r.pkg)) { pw.print(prefix); - pw.print(" "); + pw.print(" AppSettings: "); pw.print(r.pkg); pw.print(" ("); pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid)); @@ -689,6 +714,8 @@ public class RankingHelper implements RankingConfig { pw.print(" visibility="); pw.print(Notification.visibilityToString(r.visibility)); } + pw.print(" showBadge="); + pw.print(Boolean.toString(r.showBadge)); pw.println(); for (NotificationChannel channel : r.channels.values()) { pw.print(prefix); @@ -725,6 +752,9 @@ public class RankingHelper implements RankingConfig { if (r.visibility != DEFAULT_VISIBILITY) { record.put("visibility", Notification.visibilityToString(r.visibility)); } + if (r.showBadge != DEFAULT_SHOW_BADGE) { + record.put("showBadge", Boolean.valueOf(r.showBadge)); + } for (NotificationChannel channel : r.channels.values()) { record.put("channel", channel.toJson()); } @@ -838,6 +868,7 @@ public class RankingHelper implements RankingConfig { int importance = DEFAULT_IMPORTANCE; int priority = DEFAULT_PRIORITY; int visibility = DEFAULT_VISIBILITY; + boolean showBadge = DEFAULT_SHOW_BADGE; ArrayMap<String, NotificationChannel> channels = new ArrayMap<>(); } diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java index e14700a99512..f2aff1102b0b 100644 --- a/services/core/java/com/android/server/notification/SnoozeHelper.java +++ b/services/core/java/com/android/server/notification/SnoozeHelper.java @@ -41,6 +41,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -98,6 +99,26 @@ public class SnoozeHelper { return Collections.EMPTY_LIST; } + protected List<NotificationRecord> getSnoozed() { + List<NotificationRecord> snoozedForUser = new ArrayList<>(); + int[] userIds = mUserProfiles.getCurrentProfileIds(); + final int N = userIds.length; + for (int i = 0; i < N; i++) { + final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs = + mSnoozedNotifications.get(userIds[i]); + if (snoozedPkgs != null) { + final int M = snoozedPkgs.size(); + for (int j = 0; j < M; j++) { + final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j); + if (records != null) { + snoozedForUser.addAll(records.values()); + } + } + } + } + return snoozedForUser; + } + /** * Snoozes a notification and schedules an alarm to repost at that time. */ diff --git a/services/core/java/com/android/server/pm/EphemeralResolver.java b/services/core/java/com/android/server/pm/EphemeralResolver.java index d735e7286889..96a0d18bbc3e 100644 --- a/services/core/java/com/android/server/pm/EphemeralResolver.java +++ b/services/core/java/com/android/server/pm/EphemeralResolver.java @@ -78,6 +78,7 @@ public abstract class EphemeralResolver { int sequence) { final String packageName; final String splitName; + final int versionCode; if (ephemeralResolveInfo != null) { final ArrayList<EphemeralResolveInfo> ephemeralResolveInfoList = new ArrayList<EphemeralResolveInfo>(1); @@ -91,13 +92,16 @@ public abstract class EphemeralResolver { && ephemeralIntentInfo.resolveInfo != null) { packageName = ephemeralIntentInfo.resolveInfo.getPackageName(); splitName = ephemeralIntentInfo.splitName; + versionCode = ephemeralIntentInfo.resolveInfo.getVersionCode(); } else { packageName = null; splitName = null; + versionCode = -1; } } else { packageName = null; splitName = null; + versionCode = -1; } final Intent installerIntent = buildEphemeralInstallerIntent( requestObj.launchIntent, @@ -107,6 +111,7 @@ public abstract class EphemeralResolver { requestObj.userId, packageName, splitName, + versionCode, requestObj.responseObj.token, false /*needsPhaseTwo*/); installerIntent.setComponent(new ComponentName( @@ -123,7 +128,7 @@ public abstract class EphemeralResolver { */ public static Intent buildEphemeralInstallerIntent(Intent launchIntent, Intent origIntent, String callingPackage, String resolvedType, int userId, String ephemeralPackageName, - String ephemeralSplitName, String token, boolean needsPhaseTwo) { + String ephemeralSplitName, int versionCode, String token, boolean needsPhaseTwo) { // Construct the intent that launches the ephemeral installer int flags = launchIntent.getFlags(); final Intent intent = new Intent(); @@ -181,6 +186,7 @@ public abstract class EphemeralResolver { intent.putExtra(Intent.EXTRA_PACKAGE_NAME, ephemeralPackageName); intent.putExtra(Intent.EXTRA_SPLIT_NAME, ephemeralSplitName); + intent.putExtra(Intent.EXTRA_VERSION_CODE, versionCode); } return intent; diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 0130e300dd8b..fc66bb3d6130 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -295,6 +295,15 @@ public class Installer extends SystemService { } } + public void removeIdmap(String overlayApkPath) throws InstallerException { + if (!checkBeforeRemote()) return; + try { + mInstalld.removeIdmap(overlayApkPath); + } catch (Exception e) { + throw InstallerException.from(e); + } + } + public void rmdex(String codePath, String instructionSet) throws InstallerException { assertValidInstructionSet(instructionSet); if (!checkBeforeRemote()) return; diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index a74e14150001..b6611eb9fcfd 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -219,11 +219,12 @@ public class LauncherAppsService extends SystemService { /** * Checks if the caller is in the same group as the userToCheck. */ - private void ensureInUserProfiles(UserHandle userToCheck, String message) { - ensureInUserProfiles(userToCheck.getIdentifier(), message); + private void ensureInUserProfiles( + String callingPackage, UserHandle userToCheck, String message) { + ensureInUserProfiles(callingPackage, userToCheck.getIdentifier(), message); } - private void ensureInUserProfiles(int targetUserId, String message) { + private void ensureInUserProfiles(String callingPackage, int targetUserId, String message) { final int callingUserId = injectCallingUserId(); if (targetUserId == callingUserId) return; @@ -232,7 +233,12 @@ public class LauncherAppsService extends SystemService { try { UserInfo callingUserInfo = mUm.getUserInfo(callingUserId); if (callingUserInfo.isManagedProfile()) { - throw new SecurityException(message + " for another profile " + targetUserId); + // TODO: Make it SecurityException. See b/34650921 + // throw new SecurityException(message + " for another profile " + targetUserId); + + // TODO: Report caller package name. + Slog.wtfStack(TAG, message + " by " + callingPackage + " for another profile " + + targetUserId + " from " + callingUserId); } UserInfo targetUserInfo = mUm.getUserInfo(targetUserId); @@ -281,9 +287,10 @@ public class LauncherAppsService extends SystemService { } @Override - public ParceledListSlice<ResolveInfo> getLauncherActivities(String packageName, UserHandle user) + public ParceledListSlice<ResolveInfo> getLauncherActivities(String callingPackage, + String packageName, UserHandle user) throws RemoteException { - return queryActivitiesForUser( + return queryActivitiesForUser(callingPackage, new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_LAUNCHER) .setPackage(packageName), @@ -291,9 +298,10 @@ public class LauncherAppsService extends SystemService { } @Override - public ActivityInfo resolveActivity(ComponentName component, UserHandle user) + public ActivityInfo resolveActivity( + String callingPackage, ComponentName component, UserHandle user) throws RemoteException { - ensureInUserProfiles(user, "Cannot resolve activity"); + ensureInUserProfiles(callingPackage, user, "Cannot resolve activity"); if (!isUserEnabled(user)) { return null; } @@ -311,15 +319,16 @@ public class LauncherAppsService extends SystemService { } @Override - public ParceledListSlice getShortcutConfigActivities(String packageName, UserHandle user) + public ParceledListSlice getShortcutConfigActivities( + String callingPackage, String packageName, UserHandle user) throws RemoteException { - return queryActivitiesForUser( + return queryActivitiesForUser(callingPackage, new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName), user); } - private ParceledListSlice<ResolveInfo> queryActivitiesForUser(Intent intent, - UserHandle user) { - ensureInUserProfiles(user, "Cannot retrieve activities"); + private ParceledListSlice<ResolveInfo> queryActivitiesForUser(String callingPackage, + Intent intent, UserHandle user) { + ensureInUserProfiles(callingPackage, user, "Cannot retrieve activities"); if (!isUserEnabled(user)) { return null; } @@ -358,9 +367,9 @@ public class LauncherAppsService extends SystemService { } @Override - public boolean isPackageEnabled(String packageName, UserHandle user) + public boolean isPackageEnabled(String callingPackage, String packageName, UserHandle user) throws RemoteException { - ensureInUserProfiles(user, "Cannot check package"); + ensureInUserProfiles(callingPackage, user, "Cannot check package"); if (!isUserEnabled(user)) { return false; } @@ -379,9 +388,10 @@ public class LauncherAppsService extends SystemService { } @Override - public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user) + public ApplicationInfo getApplicationInfo( + String callingPackage, String packageName, int flags, UserHandle user) throws RemoteException { - ensureInUserProfiles(user, "Cannot check package"); + ensureInUserProfiles(callingPackage, user, "Cannot check package"); if (!isUserEnabled(user)) { return null; } @@ -403,7 +413,7 @@ public class LauncherAppsService extends SystemService { private void ensureShortcutPermission(@NonNull String callingPackage, int userId) { verifyCallingPackage(callingPackage); - ensureInUserProfiles(userId, "Cannot access shortcuts"); + ensureInUserProfiles(callingPackage, userId, "Cannot access shortcuts"); if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(), callingPackage)) { @@ -479,7 +489,7 @@ public class LauncherAppsService extends SystemService { public boolean startShortcut(String callingPackage, String packageName, String shortcutId, Rect sourceBounds, Bundle startActivityOptions, int userId) { verifyCallingPackage(callingPackage); - ensureInUserProfiles(userId, "Cannot start activity"); + ensureInUserProfiles(callingPackage, userId, "Cannot start activity"); if (!isUserEnabled(userId)) { throw new IllegalStateException("Cannot start a shortcut for disabled profile " @@ -530,9 +540,10 @@ public class LauncherAppsService extends SystemService { } @Override - public boolean isActivityEnabled(ComponentName component, UserHandle user) + public boolean isActivityEnabled( + String callingPackage, ComponentName component, UserHandle user) throws RemoteException { - ensureInUserProfiles(user, "Cannot check component"); + ensureInUserProfiles(callingPackage , user, "Cannot check component"); if (!isUserEnabled(user)) { return false; } @@ -551,9 +562,10 @@ public class LauncherAppsService extends SystemService { } @Override - public void startActivityAsUser(ComponentName component, Rect sourceBounds, + public void startActivityAsUser(String callingPackage, + ComponentName component, Rect sourceBounds, Bundle opts, UserHandle user) throws RemoteException { - ensureInUserProfiles(user, "Cannot start activity"); + ensureInUserProfiles(callingPackage, user, "Cannot start activity"); if (!isUserEnabled(user)) { throw new IllegalStateException("Cannot start activity for disabled profile " + user); } @@ -604,9 +616,9 @@ public class LauncherAppsService extends SystemService { } @Override - public void showAppDetailsAsUser(ComponentName component, Rect sourceBounds, - Bundle opts, UserHandle user) throws RemoteException { - ensureInUserProfiles(user, "Cannot show app details"); + public void showAppDetailsAsUser(String callingPackage, ComponentName component, + Rect sourceBounds, Bundle opts, UserHandle user) throws RemoteException { + ensureInUserProfiles(callingPackage, user, "Cannot show app details"); if (!isUserEnabled(user)) { throw new IllegalStateException("Cannot show app details for disabled profile " + user); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index aa421b121640..49b96b0b55fe 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -587,6 +587,7 @@ class PackageManagerShellCommand extends ShellCommand { boolean listSystem = false, listThirdParty = false; boolean listInstaller = false; boolean showUid = false; + boolean showVersionCode = false; int uid = -1; int userId = UserHandle.USER_SYSTEM; try { @@ -620,6 +621,9 @@ class PackageManagerShellCommand extends ShellCommand { case "-3": listThirdParty = true; break; + case "--show-versioncode": + showVersionCode = true; + break; case "--user": userId = UserHandle.parseUserArg(getNextArgRequired()); break; @@ -664,8 +668,11 @@ class PackageManagerShellCommand extends ShellCommand { pw.print(info.applicationInfo.sourceDir); pw.print("="); } - pw.print(info.packageName); pw.print( " versionCode:" - + info.applicationInfo.versionCode); + pw.print(info.packageName); + if (showVersionCode) { + pw.print(" versionCode:"); + pw.print(info.applicationInfo.versionCode); + } if (listInstaller) { pw.print(" installer="); pw.print(mInterface.getInstallerPackageName(info.packageName)); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 1eb8b943de9a..984120f4c956 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -33,12 +33,10 @@ import android.app.IStopUserCallback; import android.app.KeyguardManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; -import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; @@ -2347,6 +2345,12 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public boolean removeUserEvenWhenDisallowed(@UserIdInt int userHandle) { + checkManageOrCreateUsersPermission("Only the system can remove users"); + return removeUserUnchecked(userHandle); + } + + @Override public UserInfo createUser(String name, int flags) { checkManageOrCreateUsersPermission(flags); return createUserInternal(name, flags, UserHandle.USER_NULL); @@ -3133,8 +3137,6 @@ public class UserManagerService extends IUserManager.Stub { applyUserRestrictionsLR(userId); } } - - maybeInitializeDemoMode(userId); } /** @@ -3173,29 +3175,6 @@ public class UserManagerService extends IUserManager.Stub { scheduleWriteUser(userData); } - private void maybeInitializeDemoMode(int userId) { - if (UserManager.isDeviceInDemoMode(mContext) && userId != UserHandle.USER_SYSTEM) { - String demoLauncher = - mContext.getResources().getString( - com.android.internal.R.string.config_demoModeLauncherComponent); - if (!TextUtils.isEmpty(demoLauncher)) { - ComponentName componentToEnable = ComponentName.unflattenFromString(demoLauncher); - String demoLauncherPkg = componentToEnable.getPackageName(); - try { - final IPackageManager iPm = AppGlobals.getPackageManager(); - iPm.setComponentEnabledSetting(componentToEnable, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0, - /* userId= */ userId); - iPm.setApplicationEnabledSetting(demoLauncherPkg, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0, - /* userId= */ userId, null); - } catch (RemoteException re) { - // Internal, shouldn't happen - } - } - } - } - /** * Returns the next available user id, filling in any holes in the ids. */ diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index c25ad468cf09..f36054fc9133 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -5896,9 +5896,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { && (result & ACTION_PASS_TO_USER) == 0) { // If we are in call but we decided not to pass the key to // the application, just pass it to the session service. - - MediaSessionLegacyHelper.getHelper(mContext) - .sendVolumeKeyEvent(event, false); + MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent( + event, AudioManager.USE_DEFAULT_STREAM_TYPE, false); break; } } @@ -5911,8 +5910,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { // If we aren't passing to the user and no one else // handled it send it to the session manager to // figure out. - MediaSessionLegacyHelper.getHelper(mContext) - .sendVolumeKeyEvent(event, true); + MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent( + event, AudioManager.USE_DEFAULT_STREAM_TYPE, false); } break; } diff --git a/services/core/java/com/android/server/storage/DiskStatsFileLogger.java b/services/core/java/com/android/server/storage/DiskStatsFileLogger.java index 22299df93ef5..0094ab5559d8 100644 --- a/services/core/java/com/android/server/storage/DiskStatsFileLogger.java +++ b/services/core/java/com/android/server/storage/DiskStatsFileLogger.java @@ -18,6 +18,7 @@ package com.android.server.storage; import android.content.pm.PackageStats; import android.os.Environment; +import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; @@ -118,7 +119,7 @@ public class DiskStatsFileLogger { long appSizeSum = 0L; long cacheSizeSum = 0L; boolean isExternal = Environment.isExternalStorageEmulated(); - for (Map.Entry<String, PackageStats> entry : mergePackagesAcrossUsers().entrySet()) { + for (Map.Entry<String, PackageStats> entry : filterOnlyPrimaryUser().entrySet()) { PackageStats stat = entry.getValue(); long appSize = stat.codeSize + stat.dataSize; long cacheSize = stat.cacheSize; @@ -141,14 +142,17 @@ public class DiskStatsFileLogger { } /** - * A given package may exist for multiple users with distinct sizes. This function merges - * the duplicated packages together and sums up their sizes to get the actual totals for the - * package. + * A given package may exist for multiple users with distinct sizes. This function filters + * the packages that do not belong to user 0 out to ensure that we get good stats for a subset. * @return A mapping of package name to merged package stats. */ - private ArrayMap<String, PackageStats> mergePackagesAcrossUsers() { + private ArrayMap<String, PackageStats> filterOnlyPrimaryUser() { ArrayMap<String, PackageStats> packageMap = new ArrayMap<>(); for (PackageStats stat : mPackageStats) { + if (stat.userHandle != UserHandle.USER_SYSTEM) { + continue; + } + PackageStats existingStats = packageMap.get(stat.packageName); if (existingStats != null) { existingStats.cacheSize += stat.cacheSize; diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java index 0436139f4fda..27e0f292fb65 100644 --- a/services/core/java/com/android/server/wm/AppWindowContainerController.java +++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java @@ -56,7 +56,7 @@ public class AppWindowContainerController private static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 2; private final IApplicationToken mToken; - private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final Handler mHandler; private final Runnable mOnWindowsDrawn = () -> { if (mListener == null) { @@ -186,6 +186,7 @@ public class AppWindowContainerController int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos, WindowManagerService service) { super(listener, service); + mHandler = new Handler(service.mH.getLooper()); mToken = token; synchronized(mWindowMap) { AppWindowToken atoken = mRoot.getAppWindowToken(mToken.asBinder()); diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index ac9859d42fcc..079dc8f2b0ad 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -375,6 +375,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree // affected. mService.getDefaultDisplayContentLocked().getDockedDividerController() .notifyAppVisibilityChanged(); + mService.mTaskSnapshotController.notifyAppVisibilityChanged(this, visible); } } @@ -674,7 +675,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree // well there is no point now. if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Nulling last startingData"); startingData = null; - } else if (mChildren.size() == 1 && startingSurface != null) { + } else if (mChildren.size() == 1 && startingSurface != null && !isRelaunching()) { // If this is the last window except for a starting transition window, // we need to get rid of the starting transition. if (getController() != null) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 3958510f5957..3a74ded144b8 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -57,6 +57,7 @@ import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS; @@ -69,6 +70,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS; @@ -634,6 +636,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ DisplayContent(Display display, WindowManagerService service, WindowLayersController layersController, WallpaperController wallpaperController) { + + if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) { + throw new IllegalArgumentException("Display with ID=" + display.getDisplayId() + + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId()) + + " new=" + display); + } + mDisplay = display; mDisplayId = display.getDisplayId(); mLayersController = layersController; @@ -957,75 +966,43 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo out.set(mContentRect); } - /** - * Adds the stack to this display. - * @see WindowManagerService#addStackToDisplay(int, int, boolean) - */ - Rect addStackToDisplay(int stackId, boolean onTop) { - boolean attachedToDisplay = false; - TaskStack stack = mService.mStackIdToStack.get(stackId); - if (stack == null) { - if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId=" - + mDisplayId); - - stack = getStackById(stackId); - if (stack != null) { - // It's already attached to the display...clear mDeferRemoval and move stack to - // appropriate z-order on display as needed. - stack.mDeferRemoval = false; - // We're not moving the display to front when we're adding stacks, only when - // requested to change the position of stack explicitly. - mTaskStackContainers.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, stack, - false /* includingParents */); - attachedToDisplay = true; - } else { - stack = new TaskStack(mService, stackId); - } + TaskStack addStackToDisplay(int stackId, boolean onTop) { + if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId=" + + mDisplayId); - mService.mStackIdToStack.put(stackId, stack); - if (stackId == DOCKED_STACK_ID) { - mDividerControllerLocked.notifyDockedStackExistsChanged(true); - } + TaskStack stack = getStackById(stackId); + if (stack != null) { + // It's already attached to the display...clear mDeferRemoval and move stack to + // appropriate z-order on display as needed. + stack.mDeferRemoval = false; + // We're not moving the display to front when we're adding stacks, only when + // requested to change the position of stack explicitly. + mTaskStackContainers.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, stack, + false /* includingParents */); } else { - final DisplayContent currentDC = stack.getDisplayContent(); - if (currentDC != null) { - throw new IllegalStateException("Trying to add stackId=" + stackId - + "to displayId=" + mDisplayId + ", but it's already attached to displayId=" - + currentDC.getDisplayId()); - } - } - - if (!attachedToDisplay) { + stack = new TaskStack(mService, stackId); mTaskStackContainers.addStackToDisplay(stack, onTop); } - if (stack.getRawFullscreen()) { - return null; + if (stackId == DOCKED_STACK_ID) { + mDividerControllerLocked.notifyDockedStackExistsChanged(true); } - final Rect bounds = new Rect(); - stack.getRawBounds(bounds); - return bounds; - } - - /** Removes the stack from the display and prepares for changing the parent. */ - private void removeStackFromDisplay(TaskStack stack) { - mTaskStackContainers.removeStackFromDisplay(stack); + return stack; } - /** Moves the stack to this display and returns the updated bounds. */ - Rect moveStackToDisplay(TaskStack stack) { - final DisplayContent currentDisplayContent = stack.getDisplayContent(); - if (currentDisplayContent == null) { + void moveStackToDisplay(TaskStack stack) { + final DisplayContent prevDc = stack.getDisplayContent(); + if (prevDc == null) { throw new IllegalStateException("Trying to move stackId=" + stack.mStackId + " which is not currently attached to any display"); } - if (stack.getDisplayContent().getDisplayId() == mDisplayId) { + if (prevDc.getDisplayId() == mDisplayId) { throw new IllegalArgumentException("Trying to move stackId=" + stack.mStackId + " to its current displayId=" + mDisplayId); } - currentDisplayContent.removeStackFromDisplay(stack); - return addStackToDisplay(stack.mStackId, true /* onTop */); + prevDc.mTaskStackContainers.removeStackFromDisplay(stack); + mTaskStackContainers.addStackToDisplay(stack, true /* onTop */); } @Override @@ -1172,7 +1149,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo super.removeImmediately(); if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this); mDimLayerController.close(); - if (mDisplayId == DEFAULT_DISPLAY) { + if (mDisplayId == DEFAULT_DISPLAY && mService.canDispatchPointerEvents()) { mService.unregisterPointerEventListener(mTapDetector); mService.unregisterPointerEventListener(mService.mMousePositionTracker); } @@ -1250,7 +1227,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final WindowState imeWin = mService.mInputMethodWindow; final boolean imeVisible = imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw() && !mDividerControllerLocked.isImeHideRequested(); - final boolean dockVisible = mService.isStackVisibleLocked(DOCKED_STACK_ID); + final boolean dockVisible = isStackVisible(DOCKED_STACK_ID); final TaskStack imeTargetStack = mService.getImeFocusStackLocked(); final int imeDockSide = (dockVisible && imeTargetStack != null) ? imeTargetStack.getDockSide() : DOCKED_INVALID; @@ -1447,7 +1424,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * @return The docked stack, but only if it is visible, and {@code null} otherwise. */ TaskStack getDockedStackLocked() { - final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID); + final TaskStack stack = getStackById(DOCKED_STACK_ID); return (stack != null && stack.isVisible()) ? stack : null; } @@ -1456,7 +1433,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * visible. */ TaskStack getDockedStackIgnoringVisibility() { - return mService.mStackIdToStack.get(DOCKED_STACK_ID); + return getStackById(DOCKED_STACK_ID); } /** Find the visible, touch-deliverable window under the given point */ @@ -1532,6 +1509,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } + // TODO: This should probably be called any time a visual change is made to the hierarchy like + // moving containers or resizing them. Need to investigate the best way to have it automatically + // happen so we don't run into issues with programmers forgetting to do it. void layoutAndAssignWindowLayersIfNeeded() { mService.mWindowsChanged = true; setLayoutNeeded(); @@ -2282,7 +2262,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final boolean foundTargetWs = (w.mAppToken != null && w.mAppToken.token == appToken) || (mScreenshotApplicationState.appWin != null && wallpaperOnly); - if (foundTargetWs && w.isDisplayedLw() && winAnim.getShown()) { + if (foundTargetWs && winAnim.getShown()) { mScreenshotApplicationState.screenshotReady = true; } @@ -2441,6 +2421,27 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } + void setExitingTokensHasVisible(boolean hasVisible) { + for (int i = mExitingTokens.size() - 1; i >= 0; i--) { + mExitingTokens.get(i).hasVisible = hasVisible; + } + + // Initialize state of exiting applications. + mTaskStackContainers.setExitingTokensHasVisible(hasVisible); + } + + void removeExistingTokensIfPossible() { + for (int i = mExitingTokens.size() - 1; i >= 0; i--) { + final WindowToken token = mExitingTokens.get(i); + if (!token.hasVisible) { + mExitingTokens.remove(i); + } + } + + // Time to remove any exiting applications? + mTaskStackContainers.removeExistingAppTokensIfPossible(); + } + static final class TaskForResizePointSearchResult { boolean searchDone; Task taskForResize; @@ -2650,10 +2651,38 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return false; } + void setExitingTokensHasVisible(boolean hasVisible) { + for (int i = mChildren.size() - 1; i >= 0; --i) { + final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens; + for (int j = appTokens.size() - 1; j >= 0; --j) { + appTokens.get(j).hasVisible = hasVisible; + } + } + } + + void removeExistingAppTokensIfPossible() { + for (int i = mChildren.size() - 1; i >= 0; --i) { + final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens; + for (int j = appTokens.size() - 1; j >= 0; --j) { + final AppWindowToken token = appTokens.get(j); + if (!token.hasVisible && !mService.mClosingApps.contains(token) + && (!token.mIsExiting || token.isEmpty())) { + // Make sure there is no animation running on this token, so any windows + // associated with it will be removed as soon as their animations are + // complete. + token.mAppAnimator.clearAnimation(); + token.mAppAnimator.animating = false; + if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, + "performLayout: App token exiting now removed" + token); + token.removeIfPossible(); + } + } + } + } + @Override int getOrientation() { - if (mService.isStackVisibleLocked(DOCKED_STACK_ID) - || mService.isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) { + if (isStackVisible(DOCKED_STACK_ID) || isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) { // Apps and their containers are not allowed to specify an orientation while the // docked or freeform stack is visible...except for the home stack/task if the // docked stack is minimized and it actually set something. diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index ed1e2d998d63..aaf724ec396f 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -457,8 +457,7 @@ public class DockedStackDividerController implements DimLayerUser { void registerDockedStackListener(IDockedStackListener listener) { mDockedStackListeners.register(listener); notifyDockedDividerVisibilityChanged(wasVisible()); - notifyDockedStackExistsChanged( - mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID) != null); + notifyDockedStackExistsChanged(mDisplayContent.getDockedStackIgnoringVisibility() != null); notifyDockedStackMinimizedChanged(mMinimizedDock, 0 /* animDuration */); notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */); @@ -466,7 +465,7 @@ public class DockedStackDividerController implements DimLayerUser { void setResizeDimLayer(boolean visible, int targetStackId, float alpha) { mService.openSurfaceTransaction(); - final TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(targetStackId); + final TaskStack stack = mDisplayContent.getStackById(targetStackId); final TaskStack dockedStack = mDisplayContent.getDockedStackLocked(); boolean visibleAndValid = visible && stack != null && dockedStack != null; if (visibleAndValid) { @@ -544,8 +543,8 @@ public class DockedStackDividerController implements DimLayerUser { if (homeTask == null || !isWithinDisplay(homeTask)) { return; } - final TaskStack fullscreenStack - = mService.mStackIdToStack.get(FULLSCREEN_WORKSPACE_STACK_ID); + final TaskStack fullscreenStack = + mDisplayContent.getStackById(FULLSCREEN_WORKSPACE_STACK_ID); final boolean homeVisible = homeTask.getTopVisibleAppToken() != null; final boolean homeBehind = (fullscreenStack != null && fullscreenStack.isVisible()) || (homeStack.hasMultipleTaskWithHomeTaskNotTop()); @@ -739,7 +738,7 @@ public class DockedStackDividerController implements DimLayerUser { } private boolean animateForMinimizedDockedStack(long now) { - final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID); + final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID); if (!mAnimationStarted) { mAnimationStarted = true; mAnimationStartTime = now; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 0cc6c701a93e..22abf3056aff 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -382,6 +382,17 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { } } + TaskStack getStackById(int stackId) { + for (int i = mChildren.size() - 1; i >= 0; i--) { + final DisplayContent dc = mChildren.get(i); + final TaskStack stack = dc.getStackById(stackId); + if (stack != null) { + return stack; + } + } + return null; + } + void setSecureSurfaceState(int userId, boolean disabled) { forAllWindows((w) -> { if (w.mHasSurface && userId == UserHandle.getUserId(w.mOwnerUid)) { @@ -535,18 +546,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { final int numDisplays = mChildren.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final DisplayContent displayContent = mChildren.get(displayNdx); - for (i = displayContent.mExitingTokens.size() - 1; i >= 0; i--) { - displayContent.mExitingTokens.get(i).hasVisible = false; - } - } - - for (int stackNdx = mService.mStackIdToStack.size() - 1; stackNdx >= 0; --stackNdx) { - // Initialize state of exiting applications. - final AppTokenList exitingAppTokens = - mService.mStackIdToStack.valueAt(stackNdx).mExitingAppTokens; - for (int tokenNdx = exitingAppTokens.size() - 1; tokenNdx >= 0; --tokenNdx) { - exitingAppTokens.get(tokenNdx).hasVisible = false; - } + displayContent.setExitingTokensHasVisible(false); } mHoldScreen = null; @@ -681,33 +681,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { // Time to remove any exiting tokens? for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final DisplayContent displayContent = mChildren.get(displayNdx); - ArrayList<WindowToken> exitingTokens = displayContent.mExitingTokens; - for (i = exitingTokens.size() - 1; i >= 0; i--) { - WindowToken token = exitingTokens.get(i); - if (!token.hasVisible) { - exitingTokens.remove(i); - } - } - } - - // Time to remove any exiting applications? - for (int stackNdx = mService.mStackIdToStack.size() - 1; stackNdx >= 0; --stackNdx) { - // Initialize state of exiting applications. - final AppTokenList exitingAppTokens = - mService.mStackIdToStack.valueAt(stackNdx).mExitingAppTokens; - for (i = exitingAppTokens.size() - 1; i >= 0; i--) { - final AppWindowToken token = exitingAppTokens.get(i); - if (!token.hasVisible && !mService.mClosingApps.contains(token) && - (!token.mIsExiting || token.isEmpty())) { - // Make sure there is no animation running on this token, so any windows - // associated with it will be removed as soon as their animations are complete - token.mAppAnimator.clearAnimation(); - token.mAppAnimator.animating = false; - if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, - "performLayout: App token exiting now removed" + token); - token.removeIfPossible(); - } - } + displayContent.removeExistingTokensIfPossible(); } if (wallpaperDestroyed) { diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java new file mode 100644 index 000000000000..9a6f3eb5531c --- /dev/null +++ b/services/core/java/com/android/server/wm/StackWindowController.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import android.app.RemoteAction; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Slog; +import android.util.SparseArray; +import com.android.server.UiThread; +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.ref.WeakReference; +import java.util.List; + +import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; +import static com.android.server.wm.WindowContainer.POSITION_TOP; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +/** + * Controller for the stack container. This is created by activity manager to link activity stacks + * to the stack container they use in window manager. + * + * Test class: {@link StackWindowControllerTests} + */ +public class StackWindowController + extends WindowContainerController<TaskStack, StackWindowListener> { + + final int mStackId; + + private final H mHandler; + + public StackWindowController(int stackId, StackWindowListener listener, + int displayId, boolean onTop, Rect outBounds) { + this(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance()); + } + + @VisibleForTesting + public StackWindowController(int stackId, StackWindowListener listener, + int displayId, boolean onTop, Rect outBounds, WindowManagerService service) { + super(listener, service); + mStackId = stackId; + mHandler = new H(new WeakReference<>(this), service.mH.getLooper()); + + synchronized (mWindowMap) { + final DisplayContent dc = mRoot.getDisplayContent(displayId); + if (dc == null) { + throw new IllegalArgumentException("Trying to add stackId=" + stackId + + " to unknown displayId=" + displayId); + } + + final TaskStack stack = dc.addStackToDisplay(stackId, onTop); + stack.setController(this); + getRawBounds(outBounds); + } + } + + @Override + public void removeContainer() { + synchronized (mWindowMap) { + if (mContainer != null) { + mContainer.removeIfPossible(); + super.removeContainer(); + } + } + } + + public void reparent(int displayId, Rect outStackBounds) { + synchronized (mWindowMap) { + if (mContainer == null) { + throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId + + " to displayId=" + displayId); + } + + final DisplayContent targetDc = mRoot.getDisplayContent(displayId); + if (targetDc == null) { + throw new IllegalArgumentException("Trying to move stackId=" + mStackId + + " to unknown displayId=" + displayId); + } + + targetDc.moveStackToDisplay(mContainer); + getRawBounds(outStackBounds); + } + } + + public void positionChildAt(TaskWindowContainerController child, int position, Rect bounds, + Configuration overrideConfig) { + synchronized (mWindowMap) { + if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child + + " at " + position); + if (child.mContainer == null) { + if (DEBUG_STACK) Slog.i(TAG_WM, + "positionChildAt: could not find task=" + this); + return; + } + if (mContainer == null) { + if (DEBUG_STACK) Slog.i(TAG_WM, + "positionChildAt: could not find stack for task=" + mContainer); + return; + } + child.mContainer.positionAt(position, bounds, overrideConfig); + mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); + } + } + + public void positionChildAtTop(TaskWindowContainerController child, boolean includingParents) { + if (child == null) { + // TODO: Fix the call-points that cause this to happen. + return; + } + + synchronized(mWindowMap) { + final Task childTask = child.mContainer; + if (childTask == null) { + Slog.e(TAG_WM, "positionChildAtTop: task=" + child + " not found"); + return; + } + mContainer.positionChildAt(POSITION_TOP, childTask, includingParents); + + if (mService.mAppTransition.isTransitionSet()) { + childTask.setSendingToBottom(false); + } + mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); + } + } + + public void positionChildAtBottom(TaskWindowContainerController child) { + if (child == null) { + // TODO: Fix the call-points that cause this to happen. + return; + } + + synchronized(mWindowMap) { + final Task childTask = child.mContainer; + if (childTask == null) { + Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found"); + return; + } + mContainer.positionChildAt(POSITION_BOTTOM, childTask, false /* includingParents */); + + if (mService.mAppTransition.isTransitionSet()) { + childTask.setSendingToBottom(true); + } + mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); + } + } + + /** + * Re-sizes a stack and its containing tasks. + * + * @param bounds New stack bounds. Passing in null sets the bounds to fullscreen. + * @param configs Configurations for tasks in the resized stack, keyed by task id. + * @param taskBounds Bounds for tasks in the resized stack, keyed by task id. + * @return True if the stack is now fullscreen. + */ + public boolean resize(Rect bounds, SparseArray<Configuration> configs, + SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) { + synchronized (mWindowMap) { + if (mContainer == null) { + throw new IllegalArgumentException("resizeStack: stack " + this + " not found."); + } + // We might trigger a configuration change. Save the current task bounds for freezing. + mContainer.prepareFreezingTaskBounds(); + if (mContainer.setBounds(bounds, configs, taskBounds, taskTempInsetBounds) + && mContainer.isVisible()) { + mContainer.getDisplayContent().setLayoutNeeded(); + mService.mWindowPlacerLocked.performSurfacePlacement(); + } + return mContainer.getRawFullscreen(); + } + } + + // TODO: This and similar methods should be separated into PinnedStackWindowContainerController + public void animateResizePinnedStack(final Rect bounds, final int animationDuration) { + synchronized (mWindowMap) { + if (mContainer == null) { + throw new IllegalArgumentException("Pinned stack container not found :("); + } + final Rect originalBounds = new Rect(); + mContainer.getBounds(originalBounds); + mContainer.setAnimatingBounds(bounds); + UiThread.getHandler().post(new Runnable() { + @Override + public void run() { + mService.mBoundsAnimationController.animateBounds( + mContainer, originalBounds, bounds, animationDuration); + } + }); + } + } + + /** Sets the current picture-in-picture aspect ratio. */ + public void setPictureInPictureAspectRatio(float aspectRatio) { + synchronized (mWindowMap) { + if (!mService.mSupportsPictureInPicture || mContainer == null) { + return; + } + + final int displayId = mContainer.getDisplayContent().getDisplayId(); + final Rect toBounds = mService.getPictureInPictureBounds(displayId, aspectRatio); + animateResizePinnedStack(toBounds, -1 /* duration */); + } + } + + public void getStackDockedModeBounds(Rect outBounds, boolean ignoreVisibility) { + synchronized (mWindowMap) { + if (mContainer != null) { + mContainer.getStackDockedModeBoundsLocked(outBounds, ignoreVisibility); + return; + } + outBounds.setEmpty(); + } + } + + public void prepareFreezingTaskBounds() { + synchronized (mWindowMap) { + if (mContainer == null) { + throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this + + " not found."); + } + mContainer.prepareFreezingTaskBounds(); + } + } + + /** Sets the current picture-in-picture actions. */ + public void setPictureInPictureActions(List<RemoteAction> actions) { + synchronized (mWindowMap) { + if (!mService.mSupportsPictureInPicture || mContainer == null) { + return; + } + + mContainer.getDisplayContent().getPinnedStackController().setActions(actions); + } + } + + private void getRawBounds(Rect outBounds) { + if (mContainer.getRawFullscreen()) { + outBounds.setEmpty(); + } else { + mContainer.getRawBounds(outBounds); + } + } + + public void getBounds(Rect outBounds) { + synchronized (mWindowMap) { + if (mContainer != null) { + mContainer.getBounds(outBounds); + return; + } + outBounds.setEmpty(); + } + } + + public Rect getBoundsForNewConfiguration() { + synchronized(mWindowMap) { + final Rect outBounds = new Rect(); + mContainer.getBoundsForNewConfiguration(outBounds); + return outBounds; + } + } + + void requestResize(Rect bounds) { + mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget(); + } + + @Override + public String toString() { + return "{StackWindowController stackId=" + mStackId + "}"; + } + + private static final class H extends Handler { + + static final int REQUEST_RESIZE = 0; + + private final WeakReference<StackWindowController> mController; + + H(WeakReference<StackWindowController> controller, Looper looper) { + super(looper); + mController = controller; + } + + @Override + public void handleMessage(Message msg) { + final StackWindowController controller = mController.get(); + final StackWindowListener listener = (controller != null) + ? controller.mListener : null; + if (listener == null) { + return; + } + switch (msg.what) { + case REQUEST_RESIZE: + listener.requestResize((Rect) msg.obj); + break; + } + } + } +} diff --git a/services/core/java/com/android/server/wm/StackWindowListener.java b/services/core/java/com/android/server/wm/StackWindowListener.java new file mode 100644 index 000000000000..c763c175687f --- /dev/null +++ b/services/core/java/com/android/server/wm/StackWindowListener.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import android.graphics.Rect; + +/** + * Interface used by the creator of {@link StackWindowController} to listen to changes with + * the stack container. + */ +public interface StackWindowListener extends WindowContainerListener { + + /** Called when the stack container would like its controller to resize. */ + void requestResize(Rect bounds); +} diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 680d0f2881a6..d96e1eff2363 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -27,9 +27,9 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK; import android.app.ActivityManager.StackId; +import android.app.ActivityManager.TaskDescription; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; @@ -81,6 +81,10 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU // Resize mode of the task. See {@link ActivityInfo#resizeMode} private int mResizeMode; + // Whether the task supports picture-in-picture. + // See {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE} + private boolean mSupportsPictureInPicture; + // Whether the task is currently being drag-resized private boolean mDragResizing; private int mDragResizeMode; @@ -90,8 +94,11 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU // Whether this task is an on-top launcher task, which is determined by the root activity. private boolean mIsOnTopLauncher; + private TaskDescription mTaskDescription; + Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds, - Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode, boolean homeTask, + Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode, + boolean supportsPictureInPicture, boolean homeTask, TaskDescription taskDescription, TaskWindowContainerController controller) { mTaskId = taskId; mStack = stack; @@ -99,9 +106,11 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU mService = service; mIsOnTopLauncher = isOnTopLauncher; mResizeMode = resizeMode; + mSupportsPictureInPicture = supportsPictureInPicture; mHomeTask = homeTask; setController(controller); setBounds(bounds, overrideConfig); + mTaskDescription = taskDescription; } DisplayContent getDisplayContent() { @@ -323,7 +332,8 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU } boolean isResizeable() { - return ActivityInfo.isResizeableMode(mResizeMode) || mService.mForceResizableTasks; + return ActivityInfo.isResizeableMode(mResizeMode) || mSupportsPictureInPicture + || mService.mForceResizableTasks; } /** @@ -556,11 +566,10 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU displayContent.rotateBounds(mRotation, newRotation, mTmpRect2); if (setBounds(mTmpRect2, getOverrideConfiguration()) != BOUNDS_CHANGE_NONE) { - // Post message to inform activity manager of the bounds change simulating a one-way - // call. We do this to prevent a deadlock between window manager lock and activity - // manager lock been held. - mService.mH.obtainMessage(RESIZE_TASK, mTaskId, - RESIZE_MODE_SYSTEM_SCREEN_ROTATION, mBounds).sendToTarget(); + final TaskWindowContainerController controller = getController(); + if (controller != null) { + controller.requestResize(mBounds, RESIZE_MODE_SYSTEM_SCREEN_ROTATION); + } } } @@ -647,6 +656,14 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU } } + void setTaskDescription(TaskDescription taskDescription) { + mTaskDescription = taskDescription; + } + + TaskDescription getTaskDescription() { + return mTaskDescription; + } + @Override boolean fillsParent() { return mFillsParent || !StackId.isTaskResizeAllowed(mStack.mStackId); @@ -688,6 +705,5 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU pw.println(triplePrefix + "Activity #" + i + " " + wtoken); wtoken.dump(pw, triplePrefix); } - } } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 15878f6e414a..2b74f84701b4 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -26,6 +26,8 @@ import android.os.Environment; import android.util.ArraySet; import android.view.WindowManagerPolicy.StartingSurface; +import com.google.android.collect.Sets; + import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; @@ -66,10 +68,27 @@ class TaskSnapshotController { if (!ENABLE_TASK_SNAPSHOTS) { return; } + handleClosingApps(mService.mClosingApps); + } + + + /** + * Called when the visibility of an app changes outside of the regular app transition flow. + */ + void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) { + if (!ENABLE_TASK_SNAPSHOTS) { + return; + } + if (!visible) { + handleClosingApps(Sets.newArraySet(appWindowToken)); + } + } + + private void handleClosingApps(ArraySet<AppWindowToken> closingApps) { // We need to take a snapshot of the task if and only if all activities of the task are // either closing or hidden. - getClosingTasks(mService.mClosingApps, mTmpTasks); + getClosingTasks(closingApps, mTmpTasks); for (int i = mTmpTasks.size() - 1; i >= 0; i--) { final Task task = mTmpTasks.valueAt(i); if (!canSnapshotTask(task)) { diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index 4a094237b037..cfcbbd0358b5 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -26,12 +26,16 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import android.app.ActivityManager.TaskDescription; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.GraphicBuffer; +import android.graphics.Paint; import android.graphics.Rect; import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Slog; @@ -43,6 +47,7 @@ import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.WindowManagerPolicy.StartingSurface; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.view.BaseIWindow; /** @@ -61,6 +66,7 @@ class TaskSnapshotSurface implements StartingSurface { private final WindowManagerService mService; private boolean mHasDrawn; private boolean mReportNextDraw; + private Paint mFillBackgroundPaint = new Paint(); static TaskSnapshotSurface create(WindowManagerService service, AppWindowToken token, GraphicBuffer snapshot) { @@ -73,6 +79,7 @@ class TaskSnapshotSurface implements StartingSurface { final Rect tmpRect = new Rect(); final Rect tmpFrame = new Rect(); final Configuration tmpConfiguration = new Configuration(); + int fillBackgroundColor = Color.WHITE; synchronized (service.mWindowMap) { layoutParams.type = TYPE_APPLICATION_STARTING; layoutParams.format = snapshot.getFormat(); @@ -90,6 +97,12 @@ class TaskSnapshotSurface implements StartingSurface { layoutParams.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; layoutParams.setTitle(String.format(TITLE_FORMAT, token.mTask.mTaskId)); + if (token.mTask != null) { + final TaskDescription taskDescription = token.mTask.getTaskDescription(); + if (taskDescription != null) { + fillBackgroundColor = taskDescription.getBackgroundColor(); + } + } } try { final int res = session.addToDisplay(window, window.mSeq, layoutParams, @@ -103,7 +116,7 @@ class TaskSnapshotSurface implements StartingSurface { // Local call. } final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window, - surface); + surface, fillBackgroundColor); window.setOuter(snapshotSurface); try { session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, tmpFrame, @@ -116,11 +129,14 @@ class TaskSnapshotSurface implements StartingSurface { return snapshotSurface; } - private TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface) { + @VisibleForTesting + TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface, + int fillBackgroundColor) { mService = service; mSession = WindowManagerGlobal.getWindowSession(); mWindow = window; mSurface = surface; + mFillBackgroundPaint.setColor(fillBackgroundColor); } @Override @@ -136,7 +152,9 @@ class TaskSnapshotSurface implements StartingSurface { // TODO: Just wrap the buffer here without any copying. final Canvas c = mSurface.lockHardwareCanvas(); - c.drawBitmap(Bitmap.createHardwareBitmap(snapshot), 0, 0, null); + final Bitmap b = Bitmap.createHardwareBitmap(snapshot); + fillEmptyBackground(c, b); + c.drawBitmap(b, 0, 0, null); mSurface.unlockCanvasAndPost(c); final boolean reportNextDraw; synchronized (mService.mWindowMap) { @@ -149,6 +167,21 @@ class TaskSnapshotSurface implements StartingSurface { mSurface.release(); } + @VisibleForTesting + void fillEmptyBackground(Canvas c, Bitmap b) { + final boolean fillHorizontally = c.getWidth() > b.getWidth(); + final boolean fillVertically = c.getHeight() > b.getHeight(); + if (fillHorizontally) { + c.drawRect(b.getWidth(), 0, c.getWidth(), fillVertically + ? b.getHeight() + : c.getHeight(), + mFillBackgroundPaint); + } + if (fillVertically) { + c.drawRect(0, b.getHeight(), c.getWidth(), c.getHeight(), mFillBackgroundPaint); + } + } + private void reportDrawn() { synchronized (mService.mWindowMap) { mReportNextDraw = false; @@ -160,7 +193,7 @@ class TaskSnapshotSurface implements StartingSurface { } } - private static Handler sHandler = new Handler() { + private static Handler sHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 53292bb2259d..d3eae8ca5830 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -35,17 +35,12 @@ import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVID import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.H.RESIZE_STACK; import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM; import android.app.ActivityManager.StackId; -import android.app.IActivityManager; import android.content.res.Configuration; -import android.graphics.Point; -import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; -import android.os.Debug; import android.os.RemoteException; import android.util.EventLog; import android.util.Slog; @@ -53,11 +48,9 @@ import android.util.SparseArray; import android.view.DisplayInfo; import android.view.Surface; -import android.view.WindowManagerPolicy; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; import com.android.internal.policy.DockedDividerUtils; -import com.android.internal.policy.PipSnapAlgorithm; import com.android.server.EventLogTags; import java.io.PrintWriter; @@ -206,14 +199,6 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye } } - boolean isFullscreenBounds(Rect bounds) { - if (mDisplayContent == null || bounds == null) { - return true; - } - mDisplayContent.getLogicalDisplayRect(mTmpRect); - return mTmpRect.equals(bounds); - } - /** * Overrides the adjusted bounds, i.e. sets temporary layout bounds which are different from * the normal task bounds. @@ -658,7 +643,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye final Rect oldBounds = new Rect(mBounds); Rect bounds = null; - final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID); + final TaskStack dockedStack = dc.getDockedStackIgnoringVisibility(); if (mStackId == DOCKED_STACK_ID || (dockedStack != null && StackId.isResizeableByDockedStack(mStackId) && !dockedStack.fillsParent())) { @@ -697,7 +682,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye return; } - final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID); + final TaskStack dockedStack = mDisplayContent.getDockedStackIgnoringVisibility(); if (dockedStack == null) { // Not sure why you are calling this method when there is no docked stack... throw new IllegalStateException( @@ -805,8 +790,12 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye final Rect bounds = new Rect(); getStackDockedModeBoundsLocked(bounds, true /*ignoreVisibility*/); - mService.mH.obtainMessage(RESIZE_STACK, DOCKED_STACK_ID, - 1 /*allowResizeInDockedMode*/, bounds).sendToTarget(); + getController().requestResize(bounds); + } + + @Override + StackWindowController getController() { + return (StackWindowController) super.getController(); } @Override diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java index 3c438ca3195f..11667c0b6ac7 100644 --- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java +++ b/services/core/java/com/android/server/wm/TaskWindowContainerController.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import android.app.ActivityManager.TaskDescription; import android.app.ActivityManager.TaskSnapshot; import android.content.res.Configuration; import android.graphics.Rect; @@ -26,6 +27,8 @@ import android.util.EventLog; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import java.lang.ref.WeakReference; + import static com.android.server.EventLogTags.WM_TASK_CREATED; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; @@ -42,44 +45,40 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; public class TaskWindowContainerController extends WindowContainerController<Task, TaskWindowContainerListener> { - private static final int REPORT_SNAPSHOT_CHANGED = 0; - private final int mTaskId; + private final H mHandler; - private final Handler mHandler = new Handler(Looper.getMainLooper()) { - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case REPORT_SNAPSHOT_CHANGED: - if (mListener != null) { - mListener.onSnapshotChanged((TaskSnapshot) msg.obj); - } - break; - } - } - }; + public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener, + StackWindowController stackController, int userId, Rect bounds, + Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture, + boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers, + TaskDescription taskDescription) { + this(taskId, listener, stackController, userId, bounds, overrideConfig, resizeMode, + supportsPictureInPicture, homeTask, isOnTopLauncher, toTop, showForAllUsers, + taskDescription, WindowManagerService.getInstance()); + } public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener, - int stackId, int userId, Rect bounds, Configuration overrideConfig, int resizeMode, - boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers) { - super(listener, WindowManagerService.getInstance()); + StackWindowController stackController, int userId, Rect bounds, + Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture, + boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers, + TaskDescription taskDescription, WindowManagerService service) { + super(listener, service); mTaskId = taskId; + mHandler = new H(new WeakReference<>(this), service.mH.getLooper()); synchronized(mWindowMap) { if (DEBUG_STACK) Slog.i(TAG_WM, "TaskWindowContainerController: taskId=" + taskId - + " stackId=" + stackId + " bounds=" + bounds); + + " stack=" + stackController + " bounds=" + bounds); - // TODO: Pass controller for the stack to get the container object when stack is - // switched to use controller. - final TaskStack stack = mService.mStackIdToStack.get(stackId); + final TaskStack stack = stackController.mContainer; if (stack == null) { - throw new IllegalArgumentException("TaskWindowContainerController: invalid stackId=" - + stackId); + throw new IllegalArgumentException("TaskWindowContainerController: invalid stack=" + + stackController); } - EventLog.writeEvent(WM_TASK_CREATED, taskId, stackId); + EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId); final Task task = createTask(taskId, stack, userId, bounds, overrideConfig, resizeMode, - homeTask, isOnTopLauncher); + supportsPictureInPicture, homeTask, isOnTopLauncher, taskDescription); final int position = toTop ? POSITION_TOP : POSITION_BOTTOM; stack.addTask(task, position, showForAllUsers, true /* moveParents */); } @@ -87,10 +86,10 @@ public class TaskWindowContainerController @VisibleForTesting Task createTask(int taskId, TaskStack stack, int userId, Rect bounds, - Configuration overrideConfig, int resizeMode, boolean homeTask, - boolean isOnTopLauncher) { + Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture, + boolean homeTask, boolean isOnTopLauncher, TaskDescription taskDescription) { return new Task(taskId, stack, userId, mService, bounds, overrideConfig, isOnTopLauncher, - resizeMode, homeTask, this); + resizeMode, supportsPictureInPicture, homeTask, taskDescription, this); } @Override @@ -122,21 +121,22 @@ public class TaskWindowContainerController } } - public void reparent(int stackId, int position) { + public void reparent(StackWindowController stackController, int position) { synchronized (mWindowMap) { if (DEBUG_STACK) Slog.i(TAG_WM, "reparent: moving taskId=" + mTaskId - + " to stackId=" + stackId + " at " + position); + + " to stack=" + stackController + " at " + position); if (mContainer == null) { if (DEBUG_STACK) Slog.i(TAG_WM, "reparent: could not find taskId=" + mTaskId); return; } - final TaskStack stack = mService.mStackIdToStack.get(stackId); + final TaskStack stack = stackController.mContainer; if (stack == null) { - throw new IllegalArgumentException("reparent: could not find stackId=" + stackId); + throw new IllegalArgumentException("reparent: could not find stack=" + + stackController); } mContainer.reparent(stack, position); - mService.mWindowPlacerLocked.performSurfacePlacement(); + mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); } } @@ -156,65 +156,8 @@ public class TaskWindowContainerController } if (mContainer.resizeLocked(bounds, overrideConfig, forced) && relayout) { - mContainer.getDisplayContent().setLayoutNeeded(); - mService.mWindowPlacerLocked.performSurfacePlacement(); - } - } - } - - // TODO: Move to positionChildAt() in stack controller once we have a stack controller. - public void positionAt(int position, Rect bounds, Configuration overrideConfig) { - synchronized (mWindowMap) { - if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning taskId=" + mTaskId - + " at " + position); - if (mContainer == null) { - if (DEBUG_STACK) Slog.i(TAG_WM, - "positionAt: could not find taskId=" + mTaskId); - return; + mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); } - final TaskStack stack = mContainer.mStack; - if (stack == null) { - if (DEBUG_STACK) Slog.i(TAG_WM, - "positionAt: could not find stack for task=" + mContainer); - return; - } - mContainer.positionAt(position, bounds, overrideConfig); - final DisplayContent displayContent = stack.getDisplayContent(); - displayContent.setLayoutNeeded(); - mService.mWindowPlacerLocked.performSurfacePlacement(); - } - } - - // TODO: Replace with moveChildToTop in stack controller? - public void moveToTop(boolean includingParents) { - synchronized(mWindowMap) { - if (mContainer == null) { - Slog.e(TAG_WM, "moveToTop: taskId=" + mTaskId + " not found"); - return; - } - final TaskStack stack = mContainer.mStack; - stack.positionChildAt(POSITION_TOP, mContainer, includingParents); - - if (mService.mAppTransition.isTransitionSet()) { - mContainer.setSendingToBottom(false); - } - stack.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); - } - } - - // TODO: Replace with moveChildToBottom in stack controller? - public void moveToBottom() { - synchronized(mWindowMap) { - if (mContainer == null) { - Slog.e(TAG_WM, "moveTaskToBottom: taskId=" + mTaskId + " not found"); - return; - } - final TaskStack stack = mContainer.mStack; - stack.positionChildAt(POSITION_BOTTOM, mContainer, false /* includingParents */); - if (mService.mAppTransition.isTransitionSet()) { - mContainer.setSendingToBottom(true); - } - stack.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); } } @@ -263,12 +206,57 @@ public class TaskWindowContainerController } } + public void setTaskDescription(TaskDescription taskDescription) { + synchronized (mWindowMap) { + if (mContainer == null) { + Slog.w(TAG_WM, "setTaskDescription: taskId " + mTaskId + " not found."); + return; + } + mContainer.setTaskDescription(taskDescription); + } + } + void reportSnapshotChanged(TaskSnapshot snapshot) { - mHandler.obtainMessage(REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget(); + mHandler.obtainMessage(H.REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget(); + } + + void requestResize(Rect bounds, int resizeMode) { + mHandler.obtainMessage(H.REQUEST_RESIZE, resizeMode, 0, bounds).sendToTarget(); } @Override public String toString() { return "{TaskWindowContainerController taskId=" + mTaskId + "}"; } + + private static final class H extends Handler { + + static final int REPORT_SNAPSHOT_CHANGED = 0; + static final int REQUEST_RESIZE = 1; + + private final WeakReference<TaskWindowContainerController> mController; + + H(WeakReference<TaskWindowContainerController> controller, Looper looper) { + super(looper); + mController = controller; + } + + @Override + public void handleMessage(Message msg) { + final TaskWindowContainerController controller = mController.get(); + final TaskWindowContainerListener listener = (controller != null) + ? controller.mListener : null; + if (listener == null) { + return; + } + switch (msg.what) { + case REPORT_SNAPSHOT_CHANGED: + listener.onSnapshotChanged((TaskSnapshot) msg.obj); + break; + case REQUEST_RESIZE: + listener.requestResize((Rect) msg.obj, msg.arg1); + break; + } + } + } } diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java b/services/core/java/com/android/server/wm/TaskWindowContainerListener.java index 61b202dc0060..af67de38e5b3 100644 --- a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java +++ b/services/core/java/com/android/server/wm/TaskWindowContainerListener.java @@ -17,14 +17,17 @@ package com.android.server.wm; import android.app.ActivityManager.TaskSnapshot; +import android.graphics.Rect; /** - * Interface used by the creator of the controller to listen to changes with the container. + * Interface used by the creator of {@link TaskWindowContainerController} to listen to changes with + * the task container. */ public interface TaskWindowContainerListener extends WindowContainerListener { - /** - * Called when the snapshot of this task has changed. - */ + /** Called when the snapshot of this task has changed. */ void onSnapshotChanged(TaskSnapshot snapshot); + + /** Called when the task container would like its controller to resize. */ + void requestResize(Rect bounds, int resizeMode); } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index feceb8ecb573..c32e68908100 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -447,7 +447,7 @@ class WallpaperController { private void findWallpaperTarget(DisplayContent dc) { mFindResults.reset(); - if (mService.isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) { + if (dc.isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) { // In freeform mode we set the wallpaper as its own target, so we don't need an // additional window to make it visible. mFindResults.setUseTopWallpaperAsTarget(true); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c8f4bd23204d..1987f9013b21 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -706,12 +706,7 @@ public class WindowManagerService extends IWindowManager.Stub final WindowAnimator mAnimator; - private final BoundsAnimationController mBoundsAnimationController; - - /** All of the TaskStacks in the window manager, unordered. For an ordered list call - * DisplayContent.getStacks(). */ - // TODO: Don't believe this is needed with the WindowContainer model. - SparseArray<TaskStack> mStackIdToStack = new SparseArray<>(); + final BoundsAnimationController mBoundsAnimationController; private final PointerEventDispatcher mPointerEventDispatcher; @@ -2573,16 +2568,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - @Override - public Rect getBoundsForNewConfiguration(int stackId) { - synchronized(mWindowMap) { - final TaskStack stack = mStackIdToStack.get(stackId); - final Rect outBounds = new Rect(); - stack.getBoundsForNewConfiguration(outBounds); - return outBounds; - } - } - void setFocusTaskRegionLocked() { final Task focusedTask = mFocusedApp != null ? mFocusedApp.mTask : null; if (focusedTask != null) { @@ -2827,11 +2812,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - boolean isStackVisibleLocked(int stackId) { - final TaskStack stack = mStackIdToStack.get(stackId); - return (stack != null && stack.isVisible()); - } - public void setDockedStackCreateState(int mode, Rect bounds) { synchronized (mWindowMap) { setDockedStackCreateStateLocked(mode, bounds); @@ -2880,17 +2860,19 @@ public class WindowManagerService extends IWindowManager.Stub } final Rect stackBounds; - final DisplayContent displayContent; - final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID); + final DisplayContent displayContent = mRoot.getDisplayContent(displayId); + if (displayContent == null) { + return null; + } + + final TaskStack stack = displayContent.getStackById(PINNED_STACK_ID); if (stack != null) { // If the stack exists, then use its final bounds to calculate the new aspect ratio // bounds. - displayContent = stack.getDisplayContent(); stackBounds = new Rect(); stack.getAnimatingBounds(stackBounds); } else { // Otherwise, just calculate the aspect ratio bounds from the default bounds - displayContent = mRoot.getDisplayContent(displayId); stackBounds = displayContent.getPinnedStackController().getDefaultBounds(); } return displayContent.getPinnedStackController().getAspectRatioBounds(stackBounds, @@ -2898,119 +2880,10 @@ public class WindowManagerService extends IWindowManager.Stub } } - /** - * Sets the current picture-in-picture aspect ratio. - */ - public void setPictureInPictureAspectRatio(float aspectRatio) { - synchronized (mWindowMap) { - final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID); - if (!mSupportsPictureInPicture || stack == null) { - return; - } - - final int displayId = stack.getDisplayContent().getDisplayId(); - final Rect toBounds = getPictureInPictureBounds(displayId, aspectRatio); - animateResizePinnedStack(toBounds, -1 /* duration */); - } - } - - /** - * Sets the current picture-in-picture actions. - */ - public void setPictureInPictureActions(List<RemoteAction> actions) { - synchronized (mWindowMap) { - final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID); - if (!mSupportsPictureInPicture || stack == null) { - return; - } - - stack.getDisplayContent().getPinnedStackController().setActions(actions); - } - } - - /** - * Place a TaskStack on a DisplayContent. Will create a new TaskStack if none is found with - * specified stackId. - * @param stackId The unique identifier of the new stack. - * @param displayId The unique identifier of the DisplayContent. - * @param onTop If true the stack will be place at the top of the display, - * else at the bottom. - * @return The bounds that the stack has after adding. null means fullscreen. - */ - public Rect addStackToDisplay(int stackId, int displayId, boolean onTop) { - final long origId = Binder.clearCallingIdentity(); - try { - synchronized (mWindowMap) { - final DisplayContent dc = mRoot.getDisplayContent(displayId); - if (dc == null) { - throw new IllegalArgumentException("Trying to add stackId=" + stackId - + " to unknown displayId=" + displayId); - } - - return dc.addStackToDisplay(stackId, onTop); - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - - /** - * Move a TaskStack from current DisplayContent to specified one. - * @param stackId The unique identifier of the new stack. - * @param displayId The unique identifier of the new display. - */ - public Rect moveStackToDisplay(int stackId, int displayId) { - final long origId = Binder.clearCallingIdentity(); - try { - synchronized (mWindowMap) { - TaskStack stack = mStackIdToStack.get(stackId); - if (stack == null) { - throw new IllegalArgumentException("Trying to move unknown stackId=" + stackId - + " to displayId=" + displayId); - } - - final DisplayContent targetDisplayContent = mRoot.getDisplayContent(displayId); - if (targetDisplayContent == null) { - throw new IllegalArgumentException("Trying to move stackId=" + stackId - + " to unknown displayId=" + displayId); - } - - return targetDisplayContent.moveStackToDisplay(stack); - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - - /** - * Remove a TaskStack completely. - * @param stackId The unique identifier of the stack. - */ - public void removeStack(int stackId) { - synchronized (mWindowMap) { - final TaskStack stack = mStackIdToStack.get(stackId); - if (stack != null) { - stack.removeIfPossible(); - mStackIdToStack.remove(stackId); - } - } - } - - public void getStackDockedModeBounds(int stackId, Rect bounds, boolean ignoreVisibility) { - synchronized (mWindowMap) { - final TaskStack stack = mStackIdToStack.get(stackId); - if (stack != null) { - stack.getStackDockedModeBoundsLocked(bounds, ignoreVisibility); - return; - } - bounds.setEmpty(); - } - } - @Override public void getStackBounds(int stackId, Rect bounds) { synchronized (mWindowMap) { - final TaskStack stack = mStackIdToStack.get(stackId); + final TaskStack stack = mRoot.getStackById(stackId); if (stack != null) { stack.getBounds(bounds); return; @@ -3035,43 +2908,6 @@ public class WindowManagerService extends IWindowManager.Stub } /** - * Re-sizes a stack and its containing tasks. - * @param stackId Id of stack to resize. - * @param bounds New stack bounds. Passing in null sets the bounds to fullscreen. - * @param configs Configurations for tasks in the resized stack, keyed by task id. - * @param taskBounds Bounds for tasks in the resized stack, keyed by task id. - * @return True if the stack is now fullscreen. - * */ - public boolean resizeStack(int stackId, Rect bounds, - SparseArray<Configuration> configs, SparseArray<Rect> taskBounds, - SparseArray<Rect> taskTempInsetBounds) { - synchronized (mWindowMap) { - final TaskStack stack = mStackIdToStack.get(stackId); - if (stack == null) { - throw new IllegalArgumentException("resizeStack: stackId " + stackId - + " not found."); - } - if (stack.setBounds(bounds, configs, taskBounds, taskTempInsetBounds) - && stack.isVisible()) { - stack.getDisplayContent().setLayoutNeeded(); - mWindowPlacerLocked.performSurfacePlacement(); - } - return stack.getRawFullscreen(); - } - } - - public void prepareFreezingTaskBounds(int stackId) { - synchronized (mWindowMap) { - final TaskStack stack = mStackIdToStack.get(stackId); - if (stack == null) { - throw new IllegalArgumentException("prepareFreezingTaskBounds: stackId " + stackId - + " not found."); - } - stack.prepareFreezingTaskBounds(); - } - } - - /** * Starts deferring layout passes. Useful when doing multiple changes but to optimize * performance, only one layout pass should be done. This can be called multiple times, and * layouting will be resumed once the last caller has called {@link #continueSurfaceLayout} @@ -3505,8 +3341,9 @@ public class WindowManagerService extends IWindowManager.Stub // Notify whether the docked stack exists for the current user final DisplayContent displayContent = getDefaultDisplayContentLocked(); - displayContent.mDividerControllerLocked - .notifyDockedStackExistsChanged(hasDockedTasksForUser(newUserId)); + final TaskStack stack = displayContent.getDockedStackIgnoringVisibility(); + displayContent.mDividerControllerLocked.notifyDockedStackExistsChanged( + stack != null && stack.hasTaskForUser(newUserId)); // If the display is already prepared, update the density. // Otherwise, we'll update it when it's prepared. @@ -3519,15 +3356,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - /** Returns whether there is a docked task for the current user. */ - boolean hasDockedTasksForUser(int userId) { - final TaskStack stack = mStackIdToStack.get(DOCKED_STACK_ID); - if (stack == null) { - return false; - } - return stack.hasTaskForUser(userId); - } - /* Called by WindowState */ boolean isCurrentProfileLocked(int userId) { if (userId == mCurrentUserId) return true; @@ -5417,8 +5245,6 @@ public class WindowManagerService extends IWindowManager.Stub public static final int UPDATE_DOCKED_STACK_DIVIDER = 41; - public static final int RESIZE_STACK = 42; - public static final int RESIZE_TASK = 43; public static final int TEAR_DOWN_DRAG_AND_DROP_INPUT = 44; public static final int WINDOW_REPLACEMENT_TIMEOUT = 46; @@ -5844,23 +5670,6 @@ public class WindowManagerService extends IWindowManager.Stub } } break; - case RESIZE_TASK: { - try { - mActivityManager.resizeTask(msg.arg1, (Rect) msg.obj, msg.arg2); - } catch (RemoteException e) { - // This will not happen since we are in the same process. - } - } - break; - case RESIZE_STACK: { - try { - mActivityManager.resizeStack( - msg.arg1, (Rect) msg.obj, msg.arg2 == 1, false, false, -1); - } catch (RemoteException e) { - // This will not happen since we are in the same process. - } - } - break; case WINDOW_REPLACEMENT_TIMEOUT: { synchronized (mWindowMap) { for (int i = mWindowReplacementTimeouts.size() - 1; i >= 0; i--) { @@ -7646,26 +7455,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - public void animateResizePinnedStack(final Rect bounds, final int animationDuration) { - synchronized (mWindowMap) { - final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID); - if (stack == null) { - Slog.w(TAG, "animateResizePinnedStack: stackId " + PINNED_STACK_ID + " not found."); - return; - } - final Rect originalBounds = new Rect(); - stack.getBounds(originalBounds); - stack.setAnimatingBounds(bounds); - UiThread.getHandler().post(new Runnable() { - @Override - public void run() { - mBoundsAnimationController.animateBounds( - stack, originalBounds, bounds, animationDuration); - } - }); - } - } - public void setForceResizableTasks(boolean forceResizableTasks) { synchronized (mWindowMap) { mForceResizableTasks = forceResizableTasks; @@ -8123,7 +7912,8 @@ public class WindowManagerService extends IWindowManager.Stub @Override public boolean isStackVisible(int stackId) { synchronized (mWindowMap) { - return WindowManagerService.this.isStackVisibleLocked(stackId); + final DisplayContent dc = getDefaultDisplayContentLocked(); + return dc.isStackVisible(stackId); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 10aebe662828..13358da8b496 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2304,6 +2304,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final WindowState win = mService.windowForClientLocked(mSession, mClient, false); Slog.i(TAG, "WIN DEATH: " + win); if (win != null) { + final DisplayContent dc = getDisplayContent(); if (win.mAppToken != null && win.mAppToken.findMainWindow() == win) { mService.mTaskSnapshotController.onAppDied(win.mAppToken); } @@ -2313,7 +2314,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // just in case they have the divider at an unstable position. Better // also reset drag resizing state, because the owner can't do it // anymore. - final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID); + final TaskStack stack = dc.getDockedStackIgnoringVisibility(); if (stack != null) { stack.resetDockedStackToMiddle(); } diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp index b2372a3eb84c..fab309bfb148 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.cpp +++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp @@ -110,7 +110,7 @@ void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t static void nativeInit(JNIEnv* env, jobject obj) { gPowerManagerServiceObj = env->NewGlobalRef(obj); - gPowerHal = IPower::getService("power"); + gPowerHal = IPower::getService(); if (gPowerHal == nullptr) { ALOGE("Couldn't load PowerHAL module"); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index a8f09294dd53..c5e07b29ca7c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -6147,6 +6147,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private boolean isProfileOwnerPackage(String packageName, int userId) { + synchronized (this) { + return mOwners.hasProfileOwner(userId) + && mOwners.getProfileOwnerPackage(userId).equals(packageName); + } + } + public boolean isProfileOwner(ComponentName who, int userId) { final ComponentName profileOwner = getProfileOwner(userId); return who != null && who.equals(profileOwner); @@ -9121,21 +9128,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final long ident = mInjector.binderClearCallingIdentity(); try { final UserHandle callingUserHandle = UserHandle.of(callingUserId); - if (mUserManager.hasUserRestriction( - UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserHandle)) { - // The DO can initiate provisioning if the restriction was set by the DO. - if (!isDeviceOwnerPackage(packageName, callingUserId) - || isAdminAffectedByRestriction(mOwners.getDeviceOwnerComponent(), - UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) { - // Caller is not DO or the restriction was set by the system. + final ComponentName ownerAdmin = getOwnerComponent(packageName, callingUserId); + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, + callingUserHandle)) { + // An admin can initiate provisioning if it has set the restriction. + if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin, + UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) { return CODE_ADD_MANAGED_PROFILE_DISALLOWED; } } - - // TODO: Allow it if the caller is the DO? DO could just call removeUser() before - // provisioning, so not strictly required... - boolean canRemoveProfile = !mUserManager.hasUserRestriction( - UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, callingUserHandle); + boolean canRemoveProfile = true; + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, + callingUserHandle)) { + // We can remove a profile if the admin itself has set the restriction. + if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin, + UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, + callingUserId)) { + canRemoveProfile = false; + } + } if (!mUserManager.canAddMoreManagedProfiles(callingUserId, canRemoveProfile)) { return CODE_CANNOT_ADD_MANAGED_PROFILE; } @@ -9145,6 +9156,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return CODE_OK; } + private ComponentName getOwnerComponent(String packageName, int userId) { + if (isDeviceOwnerPackage(packageName, userId)) { + return mOwners.getDeviceOwnerComponent(); + } + if (isProfileOwnerPackage(packageName, userId)) { + return mOwners.getProfileOwnerComponent(userId); + } + return null; + } + private int checkManagedUserProvisioningPreCondition(int callingUserId) { if (!hasFeatureManagedUsers()) { return CODE_MANAGED_USERS_NOT_SUPPORTED; diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java index 785c3fa7ed5b..af19acb133f7 100644 --- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java +++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java @@ -58,6 +58,7 @@ import android.os.UserManager; import android.provider.CallLog; import android.provider.MediaStore; import android.provider.Settings; +import android.text.TextUtils; import android.util.KeyValueListParser; import android.util.Slog; import com.android.internal.os.BackgroundThread; @@ -105,7 +106,8 @@ public class RetailDemoModeService extends SystemService { private static final String DEMO_SESSION_COUNT = "retail_demo_session_count"; private static final String DEMO_SESSION_DURATION = "retail_demo_session_duration"; - boolean mDeviceInDemoMode = false; + boolean mDeviceInDemoMode; + boolean mIsCarrierDemoMode; int mCurrentUserId = UserHandle.USER_SYSTEM; long mUserInactivityTimeout; long mWarningDialogTimeout; @@ -135,7 +137,8 @@ public class RetailDemoModeService extends SystemService { if (!mDeviceInDemoMode) { return; } - switch (intent.getAction()) { + final String action = intent.getAction(); + switch (action) { case Intent.ACTION_SCREEN_OFF: mHandler.removeMessages(MSG_TURN_SCREEN_ON); mHandler.sendEmptyMessageDelayed(MSG_TURN_SCREEN_ON, SCREEN_WAKEUP_DELAY); @@ -166,7 +169,7 @@ public class RetailDemoModeService extends SystemService { mInjector.acquireWakeLock(); break; case MSG_INACTIVITY_TIME_OUT: - if (isDemoLauncherDisabled()) { + if (!mIsCarrierDemoMode && isDemoLauncherDisabled()) { Slog.i(TAG, "User inactivity timeout reached"); showInactivityCountdownDialog(); } @@ -177,12 +180,30 @@ public class RetailDemoModeService extends SystemService { } removeMessages(MSG_START_NEW_SESSION); removeMessages(MSG_INACTIVITY_TIME_OUT); - if (mCurrentUserId != UserHandle.USER_SYSTEM) { + if (!mIsCarrierDemoMode && mCurrentUserId != UserHandle.USER_SYSTEM) { logSessionDuration(); } - final UserInfo demoUser = mInjector.getUserManager().createUser(DEMO_USER_NAME, - UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL); - if (demoUser != null) { + + final UserManager um = mInjector.getUserManager(); + UserInfo demoUser = null; + if (mIsCarrierDemoMode) { + // Re-use the existing demo user in carrier demo mode. + for (UserInfo user : um.getUsers()) { + if (user.isDemo()) { + demoUser = user; + break; + } + } + } + + if (demoUser == null) { + // User in carrier demo mode should survive reboots. + final int flags = UserInfo.FLAG_DEMO + | (mIsCarrierDemoMode ? 0 : UserInfo.FLAG_EPHEMERAL); + demoUser = um.createUser(DEMO_USER_NAME, flags); + } + + if (demoUser != null && mCurrentUserId != demoUser.id) { setupDemoUser(demoUser); mInjector.switchUser(demoUser.id); } @@ -211,7 +232,7 @@ public class RetailDemoModeService extends SystemService { } public void register() { - ContentResolver cr = mInjector.getContentResolver(); + final ContentResolver cr = mInjector.getContentResolver(); cr.registerContentObserver(mDeviceDemoModeUri, false, this, UserHandle.USER_SYSTEM); cr.registerContentObserver(mDeviceProvisionedUri, false, this, UserHandle.USER_SYSTEM); cr.registerContentObserver(mRetailDemoConstantsUri, false, this, @@ -224,30 +245,31 @@ public class RetailDemoModeService extends SystemService { refreshTimeoutConstants(); return; } - if (mDeviceDemoModeUri.equals(uri)) { - mDeviceInDemoMode = UserManager.isDeviceInDemoMode(getContext()); - if (mDeviceInDemoMode) { + + // If device is provisioned and left demo mode - run the cleanup in demo folder + if (isDeviceProvisioned()) { + if (UserManager.isDeviceInDemoMode(getContext())) { startDemoMode(); } else { mInjector.systemPropertiesSet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "0"); + + // Run on the bg thread to not block the fg thread + BackgroundThread.getHandler().post(new Runnable() { + @Override + public void run() { + if (!deletePreloadsFolderContents()) { + Slog.w(TAG, "Failed to delete preloads folder contents"); + } + } + }); + + stopDemoMode(); + if (mInjector.isWakeLockHeld()) { mInjector.releaseWakeLock(); } } } - // If device is provisioned and left demo mode - run the cleanup in demo folder - if (!mDeviceInDemoMode && isDeviceProvisioned()) { - // Run on the bg thread to not block the fg thread - BackgroundThread.getHandler().post(new Runnable() { - @Override - public void run() { - if (!deletePreloadsFolderContents()) { - Slog.w(TAG, "Failed to delete preloads folder contents"); - } - } - }); - stopDemoMode(); - } } private void refreshTimeoutConstants() { @@ -300,23 +322,22 @@ public class RetailDemoModeService extends SystemService { } boolean isDemoLauncherDisabled() { - IPackageManager pm = mInjector.getIPackageManager(); int enabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; - String demoLauncherComponent = getContext().getResources() - .getString(R.string.config_demoModeLauncherComponent); try { - enabledState = pm.getComponentEnabledSetting( - ComponentName.unflattenFromString(demoLauncherComponent), - mCurrentUserId); - } catch (RemoteException exc) { - Slog.e(TAG, "Unable to talk to Package Manager", exc); + final IPackageManager iPm = mInjector.getIPackageManager(); + final String demoLauncherComponent = + getContext().getString(R.string.config_demoModeLauncherComponent); + enabledState = iPm.getComponentEnabledSetting( + ComponentName.unflattenFromString(demoLauncherComponent), mCurrentUserId); + } catch (RemoteException re) { + Slog.e(TAG, "Error retrieving demo launcher enabled setting", re); } return enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED; } private void setupDemoUser(UserInfo userInfo) { - UserManager um = mInjector.getUserManager(); - UserHandle user = UserHandle.of(userInfo.id); + final UserManager um = mInjector.getUserManager(); + final UserHandle user = UserHandle.of(userInfo.id); um.setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, user); um.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true, user); um.setUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, true, user); @@ -327,6 +348,7 @@ public class RetailDemoModeService extends SystemService { um.setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, false, user); // Disallow rebooting in safe mode - controlled by user 0 um.setUserRestriction(UserManager.DISALLOW_SAFE_BOOT, true, UserHandle.SYSTEM); + Settings.Secure.putIntForUser(mInjector.getContentResolver(), Settings.Secure.SKIP_FIRST_USE_HINTS, 1, userInfo.id); Settings.Global.putInt(mInjector.getContentResolver(), @@ -334,6 +356,47 @@ public class RetailDemoModeService extends SystemService { grantRuntimePermissionToCamera(user); clearPrimaryCallLog(); + + if (!mIsCarrierDemoMode) { + // Enable demo launcher. + final String demoLauncher = getContext().getString( + R.string.config_demoModeLauncherComponent); + if (!TextUtils.isEmpty(demoLauncher)) { + final ComponentName componentToEnable = + ComponentName.unflattenFromString(demoLauncher); + final String packageName = componentToEnable.getPackageName(); + try { + final IPackageManager iPm = AppGlobals.getPackageManager(); + iPm.setComponentEnabledSetting(componentToEnable, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id); + iPm.setApplicationEnabledSetting(packageName, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id, null); + } catch (RemoteException re) { + // Internal, shouldn't happen + } + } + } else { + // Set the carrier demo mode setting for the demo user. + final String carrierDemoModeSetting = getContext().getString( + R.string.config_carrierDemoModeSetting); + Settings.Secure.putIntForUser(getContext().getContentResolver(), + carrierDemoModeSetting, 1, userInfo.id); + + // Enable packages for carrier demo mode. + final String packageList = getContext().getString( + R.string.config_carrierDemoModePackages); + final String[] packageNames = packageList == null ? new String[0] + : TextUtils.split(packageList, ","); + final IPackageManager iPm = AppGlobals.getPackageManager(); + for (String packageName : packageNames) { + try { + iPm.setApplicationEnabledSetting(packageName, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id, null); + } catch (RemoteException re) { + Slog.e(TAG, "Error enabling application: " + packageName, re); + } + } + } } private void grantRuntimePermissionToCamera(UserHandle user) { @@ -385,13 +448,17 @@ public class RetailDemoModeService extends SystemService { } private void registerBroadcastReceiver() { - if (mBroadcastReceiver == null) { - final IntentFilter filter = new IntentFilter(); + if (mBroadcastReceiver != null) { + return; + } + + final IntentFilter filter = new IntentFilter(); + if (!mIsCarrierDemoMode) { filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(ACTION_RESET_DEMO); - mBroadcastReceiver = new IntentReceiver(); - getContext().registerReceiver(mBroadcastReceiver, filter); } + filter.addAction(ACTION_RESET_DEMO); + mBroadcastReceiver = new IntentReceiver(); + getContext().registerReceiver(mBroadcastReceiver, filter); } private void unregisterBroadcastReceiver() { @@ -427,6 +494,8 @@ public class RetailDemoModeService extends SystemService { } private void startDemoMode() { + mDeviceInDemoMode = true; + mPreloadAppsInstaller = mInjector.getPreloadAppsInstaller(); mInjector.initializeWakeLock(); if (mCameraIdsWithFlash == null) { @@ -434,6 +503,12 @@ public class RetailDemoModeService extends SystemService { } registerBroadcastReceiver(); + final String carrierDemoModeSetting = + getContext().getString(R.string.config_carrierDemoModeSetting); + mIsCarrierDemoMode = !TextUtils.isEmpty(carrierDemoModeSetting) + && (Settings.Secure.getInt(getContext().getContentResolver(), + carrierDemoModeSetting, 0) == 1); + mInjector.systemPropertiesSet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "1"); mHandler.sendEmptyMessage(MSG_START_NEW_SESSION); @@ -471,13 +546,12 @@ public class RetailDemoModeService extends SystemService { public void onBootPhase(int bootPhase) { switch (bootPhase) { case PHASE_THIRD_PARTY_APPS_CAN_START: - SettingsObserver settingsObserver = new SettingsObserver(mHandler); + final SettingsObserver settingsObserver = new SettingsObserver(mHandler); settingsObserver.register(); settingsObserver.refreshTimeoutConstants(); break; case PHASE_BOOT_COMPLETED: if (UserManager.isDeviceInDemoMode(getContext())) { - mDeviceInDemoMode = true; startDemoMode(); } break; @@ -497,33 +571,39 @@ public class RetailDemoModeService extends SystemService { Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode"); return; } - if (!mInjector.isWakeLockHeld()) { + if (!mIsCarrierDemoMode && !mInjector.isWakeLockHeld()) { mInjector.acquireWakeLock(); } mCurrentUserId = userId; mInjector.getActivityManagerInternal().updatePersistentConfigurationForUser( mInjector.getSystemUsersConfiguration(), userId); + mInjector.turnOffAllFlashLights(mCameraIdsWithFlash); muteVolumeStreams(); if (!mInjector.getWifiManager().isWifiEnabled()) { mInjector.getWifiManager().setWifiEnabled(true); } + // Disable lock screen for demo users. mInjector.getLockPatternUtils().setLockScreenDisabled(true, userId); - mInjector.getNotificationManager().notifyAsUser(TAG, - 1, mInjector.createResetNotification(), UserHandle.of(userId)); - synchronized (mActivityLock) { - mUserUntouched = true; - } - mInjector.logSessionCount(1); - mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT); - mHandler.post(new Runnable() { - @Override - public void run() { - mPreloadAppsInstaller.installApps(userId); + if (!mIsCarrierDemoMode) { + // Show reset notification (except in carrier demo mode). + mInjector.getNotificationManager().notifyAsUser(TAG, + 1, mInjector.createResetNotification(), UserHandle.of(userId)); + + synchronized (mActivityLock) { + mUserUntouched = true; } - }); + mInjector.logSessionCount(1); + mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT); + mHandler.post(new Runnable() { + @Override + public void run() { + mPreloadAppsInstaller.installApps(userId); + } + }); + } } private RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() { @@ -531,7 +611,7 @@ public class RetailDemoModeService extends SystemService { @Override public void onUserActivity() { - if (!mDeviceInDemoMode) { + if (!mDeviceInDemoMode || mIsCarrierDemoMode) { return; } long timeOfActivity = SystemClock.uptimeMillis(); @@ -682,7 +762,7 @@ public class RetailDemoModeService extends SystemService { } boolean isWakeLockHeld() { - return mWakeLock.isHeld(); + return mWakeLock != null && mWakeLock.isHeld(); } void acquireWakeLock() { diff --git a/services/tests/notification/src/com/android/server/notification/BadgeExtractorTest.java b/services/tests/notification/src/com/android/server/notification/BadgeExtractorTest.java new file mode 100644 index 000000000000..b26bac3188eb --- /dev/null +++ b/services/tests/notification/src/com/android/server/notification/BadgeExtractorTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.notification; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.Notification; +import android.app.Notification.Builder; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.UserHandle; +import android.service.notification.StatusBarNotification; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BadgeExtractorTest { + + @Mock RankingConfig mConfig; + + private String mPkg = "com.android.server.notification"; + private int mId = 1001; + private String mTag = null; + private int mUid = 1000; + private int mPid = 2000; + private UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser()); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + private NotificationRecord getNotificationRecord(NotificationChannel channel) { + final Builder builder = new Builder(getContext()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setPriority(Notification.PRIORITY_HIGH) + .setDefaults(Notification.DEFAULT_SOUND); + + Notification n = builder.build(); + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, mId, mTag, mUid, + mPid, n, mUser, null, System.currentTimeMillis()); + NotificationRecord r = new NotificationRecord(getContext(), sbn, channel); + return r; + } + + private Context getContext() { + return InstrumentationRegistry.getTargetContext(); + } + + // + // Tests + // + + @Test + public void testAppYesChannelNo() throws Exception { + BadgeExtractor extractor = new BadgeExtractor(); + extractor.setConfig(mConfig); + + when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(true); + NotificationChannel channel = + new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED); + channel.setShowBadge(false); + + NotificationRecord r = getNotificationRecord(channel); + + extractor.process(r); + + assertFalse(r.canShowBadge()); + } + + @Test + public void testAppNoChannelYes() throws Exception { + BadgeExtractor extractor = new BadgeExtractor(); + extractor.setConfig(mConfig); + + when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(false); + NotificationChannel channel = + new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_HIGH); + channel.setShowBadge(true); + + NotificationRecord r = getNotificationRecord(channel); + + extractor.process(r); + + assertFalse(r.canShowBadge()); + } + + @Test + public void testAppYesChannelYes() throws Exception { + BadgeExtractor extractor = new BadgeExtractor(); + extractor.setConfig(mConfig); + + when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(true); + NotificationChannel channel = + new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED); + channel.setShowBadge(true); + + NotificationRecord r = getNotificationRecord(channel); + + extractor.process(r); + + assertTrue(r.canShowBadge()); + } + + @Test + public void testAppNoChannelNo() throws Exception { + BadgeExtractor extractor = new BadgeExtractor(); + extractor.setConfig(mConfig); + + when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(false); + NotificationChannel channel = + new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED); + channel.setShowBadge(false); + + NotificationRecord r = getNotificationRecord(channel); + + extractor.process(r); + + assertFalse(r.canShowBadge()); + } +} diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java index ad436724a37e..468a26b57d7e 100644 --- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -230,9 +230,9 @@ public class BuzzBeepBlinkTest { n.flags |= Notification.FLAG_INSISTENT; } - StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, channel, id, mTag, mUid, + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid, mPid, n, mUser, null, System.currentTimeMillis()); - NotificationRecord r = new NotificationRecord(getContext(), sbn); + NotificationRecord r = new NotificationRecord(getContext(), sbn, channel); mService.addNotification(r); return r; } diff --git a/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java b/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java index f48d785b660d..936531b07059 100644 --- a/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java +++ b/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java @@ -70,9 +70,7 @@ public class GroupHelperTest { if (groupKey != null) { nb.setGroup(groupKey); } - NotificationChannel channel = - new NotificationChannel("test", "test", NotificationManager.IMPORTANCE_LOW); - return new StatusBarNotification(pkg, pkg, channel, id, tag, 0, 0, nb.build(), user, null, + return new StatusBarNotification(pkg, pkg, id, tag, 0, 0, nb.build(), user, null, System.currentTimeMillis()); } diff --git a/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java b/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java index eee9cf19bb7f..f8a32bbeaf79 100644 --- a/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java +++ b/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java @@ -69,9 +69,9 @@ public class ImportanceExtractorTest { .setDefaults(Notification.DEFAULT_SOUND); Notification n = builder.build(); - StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, channel, mId, mTag, mUid, + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, mId, mTag, mUid, mPid, n, mUser, null, System.currentTimeMillis()); - NotificationRecord r = new NotificationRecord(getContext(), sbn); + NotificationRecord r = new NotificationRecord(getContext(), sbn, channel); return r; } diff --git a/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java index 403b65c44a2f..aa08b41d69c0 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java @@ -105,8 +105,8 @@ public class NotificationComparatorTest { .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) .build(); mRecordMinCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg, - callPkg, getDefaultChannel(), 1, "minCall", callUid, callUid, n1, - new UserHandle(userId), "", 2000)); + callPkg, 1, "minCall", callUid, callUid, n1, + new UserHandle(userId), "", 2000), getDefaultChannel()); mRecordMinCall.setUserImportance(NotificationManager.IMPORTANCE_MIN); Notification n2 = new Notification.Builder(mContext) @@ -114,8 +114,8 @@ public class NotificationComparatorTest { .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) .build(); mRecordHighCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg, - callPkg, getDefaultChannel(), 1, "highcall", callUid, callUid, n2, - new UserHandle(userId), "", 1999)); + callPkg, 1, "highcall", callUid, callUid, n2, + new UserHandle(userId), "", 1999), getDefaultChannel()); mRecordHighCall.setUserImportance(NotificationManager.IMPORTANCE_HIGH); Notification n3 = new Notification.Builder(mContext) @@ -124,43 +124,43 @@ public class NotificationComparatorTest { .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) .build(); mRecordDefaultMedia = new NotificationRecord(mContext, new StatusBarNotification(pkg2, - pkg2, getDefaultChannel(), 1, "media", uid2, uid2, n3, new UserHandle(userId), - "", 1499)); + pkg2, 1, "media", uid2, uid2, n3, new UserHandle(userId), + "", 1499), getDefaultChannel()); mRecordDefaultMedia.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT); Notification n4 = new Notification.Builder(mContext) .setStyle(new Notification.MessagingStyle("sender!")).build(); mRecordInlineReply = new NotificationRecord(mContext, new StatusBarNotification(pkg2, - pkg2, getDefaultChannel(), 1, "inlinereply", uid2, uid2, n4, new UserHandle(userId), - "", 1599)); + pkg2, 1, "inlinereply", uid2, uid2, n4, new UserHandle(userId), + "", 1599), getDefaultChannel()); mRecordInlineReply.setUserImportance(NotificationManager.IMPORTANCE_HIGH); mRecordInlineReply.setPackagePriority(Notification.PRIORITY_MAX); Notification n5 = new Notification.Builder(mContext) .setCategory(Notification.CATEGORY_MESSAGE).build(); mRecordSms = new NotificationRecord(mContext, new StatusBarNotification(smsPkg, - smsPkg, getDefaultChannel(), 1, "sms", smsUid, smsUid, n5, new UserHandle(userId), - "", 1299)); + smsPkg, 1, "sms", smsUid, smsUid, n5, new UserHandle(userId), + "", 1299), getDefaultChannel()); mRecordSms.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT); Notification n6 = new Notification.Builder(mContext).build(); mRecordStarredContact = new NotificationRecord(mContext, new StatusBarNotification(pkg2, - pkg2, getDefaultChannel(), 1, "starred", uid2, uid2, n6, new UserHandle(userId), - "", 1259)); + pkg2, 1, "starred", uid2, uid2, n6, new UserHandle(userId), + "", 1259), getDefaultChannel()); mRecordStarredContact.setContactAffinity(ValidateNotificationPeople.STARRED_CONTACT); mRecordStarredContact.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT); Notification n7 = new Notification.Builder(mContext).build(); mRecordContact = new NotificationRecord(mContext, new StatusBarNotification(pkg2, - pkg2, getDefaultChannel(), 1, "contact", uid2, uid2, n7, new UserHandle(userId), - "", 1259)); + pkg2, 1, "contact", uid2, uid2, n7, new UserHandle(userId), + "", 1259), getDefaultChannel()); mRecordContact.setContactAffinity(ValidateNotificationPeople.VALID_CONTACT); mRecordContact.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT); Notification n8 = new Notification.Builder(mContext).build(); mRecordUrgent = new NotificationRecord(mContext, new StatusBarNotification(pkg2, - pkg2, getDefaultChannel(), 1, "urgent", uid2, uid2, n8, new UserHandle(userId), - "", 1258)); + pkg2, 1, "urgent", uid2, uid2, n8, new UserHandle(userId), + "", 1258), getDefaultChannel()); mRecordUrgent.setUserImportance(NotificationManager.IMPORTANCE_HIGH); Notification n9 = new Notification.Builder(mContext) @@ -169,15 +169,15 @@ public class NotificationComparatorTest { |Notification.FLAG_FOREGROUND_SERVICE, true) .build(); mRecordCheater = new NotificationRecord(mContext, new StatusBarNotification(pkg2, - pkg2, getDefaultChannel(), 1, "cheater", uid2, uid2, n9, new UserHandle(userId), - "", 9258)); + pkg2, 1, "cheater", uid2, uid2, n9, new UserHandle(userId), + "", 9258), getDefaultChannel()); mRecordCheater.setUserImportance(NotificationManager.IMPORTANCE_LOW); Notification n10 = new Notification.Builder(mContext) .setStyle(new Notification.InboxStyle().setSummaryText("message!")).build(); mRecordEmail = new NotificationRecord(mContext, new StatusBarNotification(pkg2, - pkg2, getDefaultChannel(), 1, "email", uid2, uid2, n10, new UserHandle(userId), - "", 1599)); + pkg2, 1, "email", uid2, uid2, n10, new UserHandle(userId), + "", 1599), getDefaultChannel()); mRecordEmail.setUserImportance(NotificationManager.IMPORTANCE_HIGH); } diff --git a/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java index b6166f6eb8b5..f0f4c4d66936 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -59,6 +59,7 @@ public class NotificationListenerServiceTest { assertEquals(getChannel(key, i), ranking.getChannel()); assertEquals(getPeople(key, i), ranking.getAdditionalPeople()); assertEquals(getSnoozeCriteria(key, i), ranking.getSnoozeCriteria()); + assertEquals(getShowBadge(i), ranking.canShowBadge()); } } @@ -68,9 +69,10 @@ public class NotificationListenerServiceTest { Bundle overrideGroupKeys = new Bundle(); Bundle suppressedVisualEffects = new Bundle(); Bundle explanation = new Bundle(); - Bundle overrideChannels = new Bundle(); + Bundle channels = new Bundle(); Bundle overridePeople = new Bundle(); Bundle snoozeCriteria = new Bundle(); + Bundle showBadge = new Bundle(); int[] importance = new int[mKeys.length]; for (int i = 0; i < mKeys.length; i++) { @@ -83,14 +85,15 @@ public class NotificationListenerServiceTest { suppressedVisualEffects.putInt(key, getSuppressedVisualEffects(i)); importance[i] = getImportance(i); explanation.putString(key, getExplanation(key)); - overrideChannels.putParcelable(key, getChannel(key, i)); + channels.putParcelable(key, getChannel(key, i)); overridePeople.putStringArrayList(key, getPeople(key, i)); snoozeCriteria.putParcelableArrayList(key, getSnoozeCriteria(key, i)); + showBadge.putBoolean(key, getShowBadge(i)); } NotificationRankingUpdate update = new NotificationRankingUpdate(mKeys, interceptedKeys.toArray(new String[0]), visibilityOverrides, suppressedVisualEffects, importance, explanation, overrideGroupKeys, - overrideChannels, overridePeople, snoozeCriteria); + channels, overridePeople, snoozeCriteria, showBadge); return update; } @@ -122,6 +125,10 @@ public class NotificationListenerServiceTest { return new NotificationChannel(key, key, getImportance(index)); } + private boolean getShowBadge(int index) { + return index % 3 == 0; + } + private ArrayList<String> getPeople(String key, int index) { ArrayList<String> people = new ArrayList<>(); for (int i = 0; i < index; i++) { diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java index 9b74fcc864e9..250aab871049 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -132,9 +132,9 @@ public class NotificationManagerServiceTest { .setPriority(Notification.PRIORITY_HIGH) .build(); StatusBarNotification sbn = new StatusBarNotification(mContext.getPackageName(), - mContext.getPackageName(), channel, 1, "tag", uid, 0, + mContext.getPackageName(), 1, "tag", uid, 0, n, new UserHandle(uid), null, 0); - return new NotificationRecord(mContext, sbn); + return new NotificationRecord(mContext, sbn, channel); } @Test @@ -361,17 +361,4 @@ public class NotificationManagerServiceTest { mBinderService.getActiveNotifications(sbn.getPackageName()); assertEquals(1, notifs.length); } - - @Test - @UiThreadTest - public void testSnoozeNotificationImmediatelyAfterEnqueue() throws Exception { - final StatusBarNotification sbn = generateNotificationRecord(null).sbn; - mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag", - sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId()); - mBinderService.snoozeNotificationFromListener(null, sbn.getKey()); - waitForIdle(); - StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(sbn.getPackageName()); - assertEquals(0, notifs.length); - } } diff --git a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java index fc94271f8f86..15dcc266bbb7 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java @@ -136,10 +136,10 @@ public class NotificationRecordTest { Notification n = builder.build(); if (preO) { - return new StatusBarNotification(pkg, pkg, defaultChannel, id1, tag1, uid, uid, n, + return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n, mUser, null, uid); } else { - return new StatusBarNotification(pkg2, pkg2, channel, id2, tag2, uid2, uid2, n, + return new StatusBarNotification(pkg2, pkg2, id2, tag2, uid2, uid2, n, mUser, null, uid2); } } @@ -155,7 +155,7 @@ public class NotificationRecordTest { StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */, true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */); - NotificationRecord record = new NotificationRecord(mMockContext, sbn); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, record.getSound()); } @@ -166,7 +166,7 @@ public class NotificationRecordTest { StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */, false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */); - NotificationRecord record = new NotificationRecord(mMockContext, sbn); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); assertEquals(CUSTOM_SOUND, record.getSound()); } @@ -178,7 +178,7 @@ public class NotificationRecordTest { StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */, true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */); - NotificationRecord record = new NotificationRecord(mMockContext, sbn); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); assertEquals(CUSTOM_SOUND, record.getSound()); } @@ -189,7 +189,7 @@ public class NotificationRecordTest { StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */, true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */); - NotificationRecord record = new NotificationRecord(mMockContext, sbn); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); assertEquals(CUSTOM_SOUND, record.getSound()); } @@ -200,7 +200,7 @@ public class NotificationRecordTest { StatusBarNotification sbn = getNotification(true /*preO */, false /* noisy */, false /* defaultSound */, true /* buzzy */, true /* defaultBuzz */); - NotificationRecord record = new NotificationRecord(mMockContext, sbn); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); assertNotNull(record.getVibration()); } @@ -211,7 +211,7 @@ public class NotificationRecordTest { StatusBarNotification sbn = getNotification(true /*preO */, false /* noisy */, false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */); - NotificationRecord record = new NotificationRecord(mMockContext, sbn); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); assertEquals(CUSTOM_VIBRATION, record.getVibration()); } @@ -223,7 +223,7 @@ public class NotificationRecordTest { StatusBarNotification sbn = getNotification(true /*preO */, false /* noisy */, false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */); - NotificationRecord record = new NotificationRecord(mMockContext, sbn); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); assertTrue(!Objects.equals(CUSTOM_VIBRATION, record.getVibration())); } @@ -234,7 +234,7 @@ public class NotificationRecordTest { StatusBarNotification sbn = getNotification(false /*preO */, false /* noisy */, false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */); - NotificationRecord record = new NotificationRecord(mMockContext, sbn); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); assertEquals(CUSTOM_CHANNEL_VIBRATION, record.getVibration()); } @@ -245,7 +245,7 @@ public class NotificationRecordTest { StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */, false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */); - NotificationRecord record = new NotificationRecord(mMockContext, sbn); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); assertEquals(CUSTOM_ATTRIBUTES, record.getAudioAttributes()); } @@ -256,7 +256,7 @@ public class NotificationRecordTest { StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */, false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */); - NotificationRecord record = new NotificationRecord(mMockContext, sbn); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); assertEquals(CUSTOM_ATTRIBUTES, record.getAudioAttributes()); } @@ -264,7 +264,7 @@ public class NotificationRecordTest { public void testImportance_preUpgrade() throws Exception { StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */, true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */); - NotificationRecord record = new NotificationRecord(mMockContext, sbn); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); assertEquals(NotificationManager.IMPORTANCE_HIGH, record.getImportance()); } @@ -275,7 +275,7 @@ public class NotificationRecordTest { StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */, true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */); - NotificationRecord record = new NotificationRecord(mMockContext, sbn); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); assertEquals(NotificationManager.IMPORTANCE_LOW, record.getImportance()); } @@ -283,7 +283,7 @@ public class NotificationRecordTest { public void testImportance_upgrade() throws Exception { StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */, true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */); - NotificationRecord record = new NotificationRecord(mMockContext, sbn); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); assertEquals(NotificationManager.IMPORTANCE_DEFAULT, record.getImportance()); } } diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java index 59ac427f4251..0320d8acad74 100644 --- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java @@ -105,8 +105,8 @@ public class RankingHelperTest { .setWhen(1205) .build(); mRecordGroupGSortA = new NotificationRecord(getContext(), new StatusBarNotification( - "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiGroupGSortA, user, - null, System.currentTimeMillis())); + "package", "package", 1, null, 0, 0, mNotiGroupGSortA, user, + null, System.currentTimeMillis()), getDefaultChannel()); mNotiGroupGSortB = new Notification.Builder(getContext()) .setContentTitle("B") @@ -115,24 +115,24 @@ public class RankingHelperTest { .setWhen(1200) .build(); mRecordGroupGSortB = new NotificationRecord(getContext(), new StatusBarNotification( - "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiGroupGSortB, user, - null, System.currentTimeMillis())); + "package", "package", 1, null, 0, 0, mNotiGroupGSortB, user, + null, System.currentTimeMillis()), getDefaultChannel()); mNotiNoGroup = new Notification.Builder(getContext()) .setContentTitle("C") .setWhen(1201) .build(); mRecordNoGroup = new NotificationRecord(getContext(), new StatusBarNotification( - "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiNoGroup, user, - null, System.currentTimeMillis())); + "package", "package", 1, null, 0, 0, mNotiNoGroup, user, + null, System.currentTimeMillis()), getDefaultChannel()); mNotiNoGroup2 = new Notification.Builder(getContext()) .setContentTitle("D") .setWhen(1202) .build(); mRecordNoGroup2 = new NotificationRecord(getContext(), new StatusBarNotification( - "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiNoGroup2, user, - null, System.currentTimeMillis())); + "package", "package", 1, null, 0, 0, mNotiNoGroup2, user, + null, System.currentTimeMillis()), getDefaultChannel()); mNotiNoGroupSortA = new Notification.Builder(getContext()) .setContentTitle("E") @@ -140,8 +140,8 @@ public class RankingHelperTest { .setSortKey("A") .build(); mRecordNoGroupSortA = new NotificationRecord(getContext(), new StatusBarNotification( - "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiNoGroupSortA, user, - null, System.currentTimeMillis())); + "package", "package", 1, null, 0, 0, mNotiNoGroupSortA, user, + null, System.currentTimeMillis()), getDefaultChannel()); final ApplicationInfo legacy = new ApplicationInfo(); legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1; @@ -254,8 +254,12 @@ public class RankingHelperTest { mHelper.createNotificationChannel(pkg, uid, channel1, true); mHelper.createNotificationChannel(pkg, uid, channel2, false); + mHelper.setShowBadge(pkg, uid, true); + mHelper.setShowBadge(pkg2, uid2, false); + ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, channel1.getId(), channel2.getId(), NotificationChannel.DEFAULT_CHANNEL_ID); + mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid}); XmlPullParser parser = Xml.newPullParser(); parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())), @@ -263,6 +267,8 @@ public class RankingHelperTest { parser.nextTag(); mHelper.readXml(parser, false); + assertFalse(mHelper.canShowBadge(pkg2, uid2)); + assertTrue(mHelper.canShowBadge(pkg, uid)); assertEquals(channel1, mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false)); compareChannels(channel2, mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false)); @@ -758,4 +764,11 @@ public class RankingHelperTest { mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid}); assertEquals(2, mHelper.getNotificationChannels(pkg, uid, false).getList().size()); } + + @Test + public void testRecordDefaults() throws Exception { + assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid)); + assertEquals(true, mHelper.canShowBadge(pkg, uid)); + assertEquals(1, mHelper.getNotificationChannels(pkg, uid, false).getList().size()); + } } diff --git a/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java index 460fcdfd960d..69724f48a111 100644 --- a/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java +++ b/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java @@ -33,6 +33,7 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.mockito.Matchers.any; @@ -42,6 +43,7 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @SmallTest @@ -190,6 +192,39 @@ public class SnoozeHelperTest { verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r); } + @Test + public void testGetSnoozedByUser() throws Exception { + NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); + NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM); + NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM); + NotificationRecord r4 = getNotificationRecord("pkg2", 3, "three", UserHandle.CURRENT); + mSnoozeHelper.snooze(r, 1000); + mSnoozeHelper.snooze(r2, 1000); + mSnoozeHelper.snooze(r3, 1000); + mSnoozeHelper.snooze(r4, 1000); + when(mUserProfiles.getCurrentProfileIds()).thenReturn( + new int[] {UserHandle.USER_SYSTEM}); + assertEquals(3, mSnoozeHelper.getSnoozed().size()); + when(mUserProfiles.getCurrentProfileIds()).thenReturn( + new int[] {UserHandle.USER_CURRENT}); + assertEquals(1, mSnoozeHelper.getSnoozed().size()); + } + + @Test + public void testGetSnoozedByUser_managedProfiles() throws Exception { + when(mUserProfiles.getCurrentProfileIds()).thenReturn( + new int[] {UserHandle.USER_SYSTEM, UserHandle.USER_CURRENT}); + NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); + NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM); + NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM); + NotificationRecord r4 = getNotificationRecord("pkg2", 3, "three", UserHandle.CURRENT); + mSnoozeHelper.snooze(r, 1000); + mSnoozeHelper.snooze(r2, 1000); + mSnoozeHelper.snooze(r3, 1000); + mSnoozeHelper.snooze(r4, 1000); + assertEquals(4, mSnoozeHelper.getSnoozed().size()); + } + private NotificationRecord getNotificationRecord(String pkg, int id, String tag, UserHandle user) { Notification n = new Notification.Builder(getContext()) @@ -199,8 +234,8 @@ public class SnoozeHelperTest { .setWhen(1205) .build(); return new NotificationRecord(getContext(), new StatusBarNotification( - pkg, pkg, getDefaultChannel(), id, tag, 0, 0, n, user, null, - System.currentTimeMillis())); + pkg, pkg, id, tag, 0, 0, n, user, null, + System.currentTimeMillis()), getDefaultChannel()); } private NotificationChannel getDefaultChannel() { diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index 43c895754890..4ca29cdac02b 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -63,7 +63,10 @@ import android.net.RecommendationResult; import android.net.ScoredNetwork; import android.net.Uri; import android.net.WifiKey; +import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiSsid; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -82,6 +85,8 @@ import android.support.test.runner.AndroidJUnit4; import com.android.server.devicepolicy.MockUtils; +import com.google.android.collect.Lists; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -96,9 +101,12 @@ import org.mockito.stubbing.Answer; import java.io.FileDescriptor; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.Function; /** * Tests for {@link NetworkScoreService}. @@ -106,19 +114,27 @@ import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) @MediumTest public class NetworkScoreServiceTest { + private static final String SSID = "ssid"; + private static final String SSID_2 = "ssid_2"; + private static final String SSID_3 = "ssid_3"; private static final ScoredNetwork SCORED_NETWORK = - new ScoredNetwork(new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")), + new ScoredNetwork(new NetworkKey(new WifiKey(quote(SSID), "00:00:00:00:00:00")), + null /* rssiCurve*/); + private static final ScoredNetwork SCORED_NETWORK_2 = + new ScoredNetwork(new NetworkKey(new WifiKey(quote(SSID_2), "00:00:00:00:00:00")), null /* rssiCurve*/); private static final NetworkScorerAppData NEW_SCORER = new NetworkScorerAppData("newPackageName", 1, "newScoringServiceClass"); - @Mock private PackageManager mPackageManager; @Mock private NetworkScorerAppManager mNetworkScorerAppManager; @Mock private Context mContext; @Mock private Resources mResources; @Mock private INetworkScoreCache.Stub mNetworkScoreCache, mNetworkScoreCache2; @Mock private IBinder mIBinder, mIBinder2; @Mock private INetworkRecommendationProvider mRecommendationProvider; + @Mock private Function<List<ScoredNetwork>, List<ScoredNetwork>> mCurrentNetworkFilter; + @Mock private Function<List<ScoredNetwork>, List<ScoredNetwork>> mScanResultsFilter; + @Mock private WifiInfo mWifiInfo; @Captor private ArgumentCaptor<List<ScoredNetwork>> mScoredNetworkCaptor; private ContentResolver mContentResolver; @@ -127,6 +143,11 @@ public class NetworkScoreServiceTest { private RemoteCallback mRemoteCallback; private OnResultListener mOnResultListener; private HandlerThread mHandlerThread; + private List<ScanResult> mScanResults; + + private static String quote(String str) { + return String.format("\"%s\"", str); + } @Before public void setUp() throws Exception { @@ -136,6 +157,8 @@ public class NetworkScoreServiceTest { mContentResolver = InstrumentationRegistry.getContext().getContentResolver(); when(mContext.getContentResolver()).thenReturn(mContentResolver); when(mContext.getResources()).thenReturn(mResources); + when(mWifiInfo.getSSID()).thenReturn(SCORED_NETWORK.networkKey.wifiKey.ssid); + when(mWifiInfo.getBSSID()).thenReturn(SCORED_NETWORK.networkKey.wifiKey.bssid); mHandlerThread = new HandlerThread("NetworkScoreServiceTest"); mHandlerThread.start(); mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager, @@ -150,6 +173,21 @@ public class NetworkScoreServiceTest { Settings.Global.putLong(mContentResolver, Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, -1L); mNetworkScoreService.refreshRecommendationRequestTimeoutMs(); + populateScanResults(); + } + + private void populateScanResults() { + mScanResults = new ArrayList<>(); + mScanResults.add(createScanResult(SSID, SCORED_NETWORK.networkKey.wifiKey.bssid)); + mScanResults.add(createScanResult(SSID_2, SCORED_NETWORK_2.networkKey.wifiKey.bssid)); + mScanResults.add(createScanResult(SSID_3, "10:10:00:00:10:10")); + } + + private ScanResult createScanResult(String ssid, String bssid) { + ScanResult result = new ScanResult(); + result.wifiSsid = WifiSsid.createFromAsciiEncoded(ssid); + result.BSSID = bssid; + return result; } @After @@ -622,6 +660,173 @@ public class NetworkScoreServiceTest { assertEquals(NEW_SCORER.packageName, mNetworkScoreService.getActiveScorerPackage()); } + @Test + public void testCacheUpdatingConsumer_nullFilter() throws Exception { + List<ScoredNetwork> scoredNetworkList = Lists.newArrayList(SCORED_NETWORK); + NetworkScoreService.FilteringCacheUpdatingConsumer consumer = + new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext, + new ArrayList<>(scoredNetworkList), NetworkKey.TYPE_WIFI, + mCurrentNetworkFilter, mScanResultsFilter); + + consumer.accept(mNetworkScoreCache, null /*cookie*/); + + verify(mNetworkScoreCache).updateScores(scoredNetworkList); + verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter); + } + + @Test + public void testCacheUpdatingConsumer_noneFilter() throws Exception { + List<ScoredNetwork> scoredNetworkList = Lists.newArrayList(SCORED_NETWORK); + NetworkScoreService.FilteringCacheUpdatingConsumer + consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext, + new ArrayList<>(scoredNetworkList), + NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter); + + consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE); + + verify(mNetworkScoreCache).updateScores(scoredNetworkList); + verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter); + } + + @Test + public void testCacheUpdatingConsumer_unknownFilter() throws Exception { + List<ScoredNetwork> scoredNetworkList = Lists.newArrayList(SCORED_NETWORK); + NetworkScoreService.FilteringCacheUpdatingConsumer + consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext, + new ArrayList<>(scoredNetworkList), + NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter); + + consumer.accept(mNetworkScoreCache, -1 /*cookie*/); + + verify(mNetworkScoreCache).updateScores(scoredNetworkList); + verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter); + } + + @Test + public void testCacheUpdatingConsumer_nonIntFilter() throws Exception { + List<ScoredNetwork> scoredNetworkList = Lists.newArrayList(SCORED_NETWORK); + NetworkScoreService.FilteringCacheUpdatingConsumer + consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext, + new ArrayList<>(scoredNetworkList), + NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter); + + consumer.accept(mNetworkScoreCache, "not an int" /*cookie*/); + + verify(mNetworkScoreCache).updateScores(scoredNetworkList); + verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter); + } + + @Test + public void testCacheUpdatingConsumer_emptyScoreList() throws Exception { + NetworkScoreService.FilteringCacheUpdatingConsumer + consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext, + Collections.emptyList(), + NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter); + + consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE); + + verifyZeroInteractions(mNetworkScoreCache, mCurrentNetworkFilter, mScanResultsFilter); + } + + @Test + public void testCacheUpdatingConsumer_currentNetworkFilter() throws Exception { + List<ScoredNetwork> scoredNetworkList = + Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2); + NetworkScoreService.FilteringCacheUpdatingConsumer + consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext, + new ArrayList<>(scoredNetworkList), + NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter); + + List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList); + filteredList.remove(SCORED_NETWORK); + when(mCurrentNetworkFilter.apply(scoredNetworkList)).thenReturn(filteredList); + consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK); + + verify(mNetworkScoreCache).updateScores(filteredList); + verifyZeroInteractions(mScanResultsFilter); + } + + @Test + public void testCacheUpdatingConsumer_scanResultsFilter() throws Exception { + List<ScoredNetwork> scoredNetworkList = + Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2); + NetworkScoreService.FilteringCacheUpdatingConsumer + consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext, + new ArrayList<>(scoredNetworkList), + NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter); + + List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList); + filteredList.remove(SCORED_NETWORK); + when(mScanResultsFilter.apply(scoredNetworkList)).thenReturn(filteredList); + consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS); + + verify(mNetworkScoreCache).updateScores(filteredList); + verifyZeroInteractions(mCurrentNetworkFilter); + } + + @Test + public void testCurrentNetworkScoreCacheFilter_nullWifiInfo() throws Exception { + NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter = + new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> null /*WifiInfo*/); + + List<ScoredNetwork> actualList = + cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2)); + + assertTrue(actualList.isEmpty()); + } + + @Test + public void testCurrentNetworkScoreCacheFilter_scoreFiltered() throws Exception { + NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter = + new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo); + + List<ScoredNetwork> actualList = + cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2)); + + List<ScoredNetwork> expectedList = Collections.singletonList(SCORED_NETWORK); + assertEquals(expectedList, actualList); + } + + @Test + public void testCurrentNetworkScoreCacheFilter_currentNetworkNotInList() throws Exception { + when(mWifiInfo.getSSID()).thenReturn("\"notInList\""); + NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter = + new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo); + + List<ScoredNetwork> actualList = + cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2)); + + assertTrue(actualList.isEmpty()); + } + + @Test + public void testScanResultsScoreCacheFilter_emptyScanResults() throws Exception { + NetworkScoreService.ScanResultsScoreCacheFilter cacheFilter = + new NetworkScoreService.ScanResultsScoreCacheFilter(Collections::emptyList); + + List<ScoredNetwork> actualList = + cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2)); + + assertTrue(actualList.isEmpty()); + } + + @Test + public void testScanResultsScoreCacheFilter_scoresFiltered() throws Exception { + NetworkScoreService.ScanResultsScoreCacheFilter cacheFilter = + new NetworkScoreService.ScanResultsScoreCacheFilter(() -> mScanResults); + + ScoredNetwork unmatchedScore = + new ScoredNetwork(new NetworkKey(new WifiKey(quote("newSsid"), + "00:00:00:00:00:00")), null /* rssiCurve*/); + + List<ScoredNetwork> actualList = + cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2, + unmatchedScore)); + + List<ScoredNetwork> expectedList = Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2); + assertEquals(expectedList, actualList); + } + // "injects" the mock INetworkRecommendationProvider into the NetworkScoreService. private void injectProvider() { final ComponentName componentName = new ComponentName(NEW_SCORER.packageName, diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java index a600e69bd345..c8c8c0ea514f 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java @@ -45,9 +45,11 @@ import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.RegisteredServicesCacheListener; import android.content.pm.ResolveInfo; +import android.content.pm.Signature; import android.content.pm.UserInfo; import android.content.pm.RegisteredServicesCache.ServiceInfo; import android.database.Cursor; @@ -81,11 +83,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class AccountManagerServiceTest extends AndroidTestCase { private static final String TAG = AccountManagerServiceTest.class.getSimpleName(); + private static final long ONE_DAY_IN_MILLISECOND = 86400000; @Mock private Context mMockContext; @Mock private AppOpsManager mMockAppOpsManager; @@ -104,6 +108,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { private static final String PREN_DB = "pren.db"; private static final String DE_DB = "de.db"; private static final String CE_DB = "ce.db"; + private PackageInfo mPackageInfo; private AccountManagerService mAms; private TestInjector mTestInjector; @@ -115,7 +120,16 @@ public class AccountManagerServiceTest extends AndroidTestCase { .thenReturn(PackageManager.SIGNATURE_MATCH); final UserInfo ui = new UserInfo(UserHandle.USER_SYSTEM, "user0", 0); when(mMockUserManager.getUserInfo(eq(ui.id))).thenReturn(ui); + when(mMockContext.createPackageContextAsUser( + anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext); when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + + mPackageInfo = new PackageInfo(); + mPackageInfo.signatures = new Signature[1]; + mPackageInfo.signatures[0] = new Signature(new byte[] {'a', 'b', 'c', 'd'}); + mPackageInfo.applicationInfo = new ApplicationInfo(); + mPackageInfo.applicationInfo.privateFlags = ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; + when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(mPackageInfo); when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mMockAppOpsManager); when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager); when(mMockContext.getSystemServiceName(AppOpsManager.class)).thenReturn( @@ -1212,6 +1226,1144 @@ public class AccountManagerServiceTest extends AndroidTestCase { assertTrue(needUpdate); } + @SmallTest + public void testHasFeaturesWithNullResponse() throws Exception { + unlockSystemUser(); + try { + mAms.hasFeatures( + null, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, + new String[] {"feature1", "feature2"}, // features + "testPackage"); // opPackageName + fail("IllegalArgumentException expected. But no exception was thrown."); + } catch (IllegalArgumentException e) { + } catch(Exception e){ + fail(String.format("Expect IllegalArgumentException, but got %s.", e)); + } + } + + @SmallTest + public void testHasFeaturesWithNullAccount() throws Exception { + unlockSystemUser(); + try { + mAms.hasFeatures( + mMockAccountManagerResponse, // response + null, // account + new String[] {"feature1", "feature2"}, // features + "testPackage"); // opPackageName + fail("IllegalArgumentException expected. But no exception was thrown."); + } catch (IllegalArgumentException e) { + } catch(Exception e){ + fail(String.format("Expect IllegalArgumentException, but got %s.", e)); + } + } + + @SmallTest + public void testHasFeaturesWithNullFeature() throws Exception { + unlockSystemUser(); + try { + mAms.hasFeatures( + mMockAccountManagerResponse, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account + null, // features + "testPackage"); // opPackageName + fail("IllegalArgumentException expected. But no exception was thrown."); + } catch (IllegalArgumentException e) { + } catch(Exception e){ + fail(String.format("Expect IllegalArgumentException, but got %s.", e)); + } + + } + + @SmallTest + public void testHasFeaturesReadAccountsNotPermitted() throws Exception { + unlockSystemUser(); + when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn( + PackageManager.PERMISSION_DENIED); + when(mMockPackageManager.checkSignatures(anyInt(), anyInt())) + .thenReturn(PackageManager.SIGNATURE_NO_MATCH); + try { + mAms.hasFeatures( + mMockAccountManagerResponse, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account + new String[] {"feature1", "feature2"}, // features + "testPackage"); // opPackageName + fail("SecurityException expected. But no exception was thrown."); + } catch (SecurityException e) { + } catch(Exception e){ + fail(String.format("Expect SecurityException, but got %s.", e)); + } + } + + @SmallTest + public void testHasFeaturesReturnNullResult() throws Exception { + unlockSystemUser(); + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + mAms.hasFeatures( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_ERROR, // account + AccountManagerServiceTestFixtures.ACCOUNT_FEATURES, // features + "testPackage"); // opPackageName + waitForLatch(latch); + verify(mMockAccountManagerResponse).onError( + eq(AccountManager.ERROR_CODE_INVALID_RESPONSE), anyString()); + verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class)); + } + + @SmallTest + public void testHasFeaturesSuccess() throws Exception { + unlockSystemUser(); + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + mAms.hasFeatures( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account + AccountManagerServiceTestFixtures.ACCOUNT_FEATURES, // features + "testPackage"); // opPackageName + waitForLatch(latch); + verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture()); + Bundle result = mBundleCaptor.getValue(); + boolean hasFeatures = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT); + assertTrue(hasFeatures); + } + + @SmallTest + public void testRemoveAccountAsUserWithNullResponse() throws Exception { + unlockSystemUser(); + try { + mAms.removeAccountAsUser( + null, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, + true, // expectActivityLaunch + UserHandle.USER_SYSTEM); + fail("IllegalArgumentException expected. But no exception was thrown."); + } catch (IllegalArgumentException e) { + } catch(Exception e){ + fail(String.format("Expect IllegalArgumentException, but got %s.", e)); + } + } + + @SmallTest + public void testRemoveAccountAsUserWithNullAccount() throws Exception { + unlockSystemUser(); + try { + mAms.removeAccountAsUser( + mMockAccountManagerResponse, // response + null, // account + true, // expectActivityLaunch + UserHandle.USER_SYSTEM); + fail("IllegalArgumentException expected. But no exception was thrown."); + } catch (IllegalArgumentException e) { + } catch(Exception e){ + fail(String.format("Expect IllegalArgumentException, but got %s.", e)); + } + } + + @SmallTest + public void testRemoveAccountAsUserAccountNotManagedByCaller() throws Exception { + unlockSystemUser(); + when(mMockPackageManager.checkSignatures(anyInt(), anyInt())) + .thenReturn(PackageManager.SIGNATURE_NO_MATCH); + try { + mAms.removeAccountAsUser( + mMockAccountManagerResponse, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, + true, // expectActivityLaunch + UserHandle.USER_SYSTEM); + fail("SecurityException expected. But no exception was thrown."); + } catch (SecurityException e) { + } catch(Exception e){ + fail(String.format("Expect SecurityException, but got %s.", e)); + } + } + + @SmallTest + public void testRemoveAccountAsUserUserCannotModifyAccount() throws Exception { + unlockSystemUser(); + Bundle bundle = new Bundle(); + bundle.putBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, true); + when(mMockUserManager.getUserRestrictions(any(UserHandle.class))).thenReturn(bundle); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + + mAms.removeAccountAsUser( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, + true, // expectActivityLaunch + UserHandle.USER_SYSTEM); + waitForLatch(latch); + verify(mMockAccountManagerResponse).onError( + eq(AccountManager.ERROR_CODE_USER_RESTRICTED), anyString()); + verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class)); + } + + @SmallTest + public void testRemoveAccountAsUserUserCannotModifyAccountType() throws Exception { + unlockSystemUser(); + when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn( + mMockDevicePolicyManager); + when(mMockDevicePolicyManager.getAccountTypesWithManagementDisabledAsUser(anyInt())) + .thenReturn(new String[]{AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, "BBB"}); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + + mAms.removeAccountAsUser( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, + true, // expectActivityLaunch + UserHandle.USER_SYSTEM); + waitForLatch(latch); + verify(mMockAccountManagerResponse).onError( + eq(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE), anyString()); + verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class)); + } + + @SmallTest + public void testRemoveAccountAsUserRemovalAllowed() throws Exception { + unlockSystemUser(); + mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p1", null); + Account[] addedAccounts = + mAms.getAccounts(UserHandle.USER_SYSTEM, mContext.getOpPackageName()); + assertEquals(1, addedAccounts.length); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + + mAms.removeAccountAsUser( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, + true, // expectActivityLaunch + UserHandle.USER_SYSTEM); + waitForLatch(latch); + + verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture()); + Bundle result = mBundleCaptor.getValue(); + boolean allowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT); + assertTrue(allowed); + Account[] accounts = mAms.getAccounts(UserHandle.USER_SYSTEM, mContext.getOpPackageName()); + assertEquals(0, accounts.length); + } + + @SmallTest + public void testRemoveAccountAsUserRemovalNotAllowed() throws Exception { + unlockSystemUser(); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + + mAms.removeAccountAsUser( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_ERROR, + true, // expectActivityLaunch + UserHandle.USER_SYSTEM); + waitForLatch(latch); + + verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture()); + Bundle result = mBundleCaptor.getValue(); + boolean allowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT); + assertFalse(allowed); + } + + @SmallTest + public void testRemoveAccountAsUserReturnWithValidIntent() throws Exception { + unlockSystemUser(); + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.applicationInfo = new ApplicationInfo(); + when(mMockPackageManager.resolveActivityAsUser( + any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo); + when(mMockPackageManager.checkSignatures( + anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + + mAms.removeAccountAsUser( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, + true, // expectActivityLaunch + UserHandle.USER_SYSTEM); + waitForLatch(latch); + + verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture()); + Bundle result = mBundleCaptor.getValue(); + Intent intent = result.getParcelable(AccountManager.KEY_INTENT); + assertNotNull(intent); + } + + @SmallTest + public void testGetAuthTokenLabelWithNullAccountType() throws Exception { + unlockSystemUser(); + try { + mAms.getAuthTokenLabel( + mMockAccountManagerResponse, // response + null, // accountType + "authTokenType"); + fail("IllegalArgumentException expected. But no exception was thrown."); + } catch (IllegalArgumentException e) { + } catch(Exception e){ + fail(String.format("Expect IllegalArgumentException, but got %s.", e)); + } + } + + @SmallTest + public void testGetAuthTokenLabelWithNullAuthTokenType() throws Exception { + unlockSystemUser(); + try { + mAms.getAuthTokenLabel( + mMockAccountManagerResponse, // response + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType + null); // authTokenType + fail("IllegalArgumentException expected. But no exception was thrown."); + } catch (IllegalArgumentException e) { + } catch(Exception e){ + fail(String.format("Expect IllegalArgumentException, but got %s.", e)); + } + } + + @SmallTest + public void testGetAuthTokenWithNullResponse() throws Exception { + unlockSystemUser(); + try { + mAms.getAuthToken( + null, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, + "authTokenType", // authTokenType + true, // notifyOnAuthFailure + true, // expectActivityLaunch + createGetAuthTokenOptions()); + fail("IllegalArgumentException expected. But no exception was thrown."); + } catch (IllegalArgumentException e) { + } catch(Exception e){ + fail(String.format("Expect IllegalArgumentException, but got %s.", e)); + } + } + + @SmallTest + public void testGetAuthTokenWithNullAccount() throws Exception { + unlockSystemUser(); + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + mAms.getAuthToken( + response, // response + null, // account + "authTokenType", // authTokenType + true, // notifyOnAuthFailure + true, // expectActivityLaunch + createGetAuthTokenOptions()); + waitForLatch(latch); + + verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class)); + verify(mMockAccountManagerResponse).onError( + eq(AccountManager.ERROR_CODE_BAD_ARGUMENTS), anyString()); + } + + @SmallTest + public void testGetAuthTokenWithNullAuthTokenType() throws Exception { + unlockSystemUser(); + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + mAms.getAuthToken( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, + null, // authTokenType + true, // notifyOnAuthFailure + true, // expectActivityLaunch + createGetAuthTokenOptions()); + waitForLatch(latch); + + verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class)); + verify(mMockAccountManagerResponse).onError( + eq(AccountManager.ERROR_CODE_BAD_ARGUMENTS), anyString()); + } + + @SmallTest + public void testGetAuthTokenWithInvalidPackage() throws Exception { + unlockSystemUser(); + String[] list = new String[]{"test"}; + when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list); + try { + mAms.getAuthToken( + mMockAccountManagerResponse, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, + "authTokenType", // authTokenType + true, // notifyOnAuthFailure + true, // expectActivityLaunch + createGetAuthTokenOptions()); + fail("SecurityException expected. But no exception was thrown."); + } catch (SecurityException e) { + } catch(Exception e){ + fail(String.format("Expect SecurityException, but got %s.", e)); + } + } + + @SmallTest + public void testGetAuthTokenFromInternal() throws Exception { + unlockSystemUser(); + when(mMockContext.createPackageContextAsUser( + anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE}; + when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list); + mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null); + + mAms.setAuthToken(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, + "authTokenType", AccountManagerServiceTestFixtures.AUTH_TOKEN); + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + mAms.getAuthToken( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, + "authTokenType", // authTokenType + true, // notifyOnAuthFailure + true, // expectActivityLaunch + createGetAuthTokenOptions()); + waitForLatch(latch); + + verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture()); + Bundle result = mBundleCaptor.getValue(); + assertEquals(result.getString(AccountManager.KEY_AUTHTOKEN), + AccountManagerServiceTestFixtures.AUTH_TOKEN); + assertEquals(result.getString(AccountManager.KEY_ACCOUNT_NAME), + AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS); + assertEquals(result.getString(AccountManager.KEY_ACCOUNT_TYPE), + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1); + } + + @SmallTest + public void testGetAuthTokenSuccess() throws Exception { + unlockSystemUser(); + when(mMockContext.createPackageContextAsUser( + anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE}; + when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + mAms.getAuthToken( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, + "authTokenType", // authTokenType + true, // notifyOnAuthFailure + false, // expectActivityLaunch + createGetAuthTokenOptions()); + waitForLatch(latch); + + verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture()); + Bundle result = mBundleCaptor.getValue(); + assertEquals(result.getString(AccountManager.KEY_AUTHTOKEN), + AccountManagerServiceTestFixtures.AUTH_TOKEN); + assertEquals(result.getString(AccountManager.KEY_ACCOUNT_NAME), + AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS); + assertEquals(result.getString(AccountManager.KEY_ACCOUNT_TYPE), + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1); + } + + @SmallTest + public void testGetAuthTokenReturnWithInvalidIntent() throws Exception { + unlockSystemUser(); + when(mMockContext.createPackageContextAsUser( + anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE}; + when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list); + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.applicationInfo = new ApplicationInfo(); + when(mMockPackageManager.resolveActivityAsUser( + any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo); + when(mMockPackageManager.checkSignatures( + anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_NO_MATCH); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + mAms.getAuthToken( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, + "authTokenType", // authTokenType + true, // notifyOnAuthFailure + false, // expectActivityLaunch + createGetAuthTokenOptions()); + waitForLatch(latch); + verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class)); + verify(mMockAccountManagerResponse).onError( + eq(AccountManager.ERROR_CODE_REMOTE_EXCEPTION), anyString()); + } + + @SmallTest + public void testGetAuthTokenReturnWithValidIntent() throws Exception { + unlockSystemUser(); + when(mMockContext.createPackageContextAsUser( + anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE}; + when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list); + + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.applicationInfo = new ApplicationInfo(); + when(mMockPackageManager.resolveActivityAsUser( + any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo); + when(mMockPackageManager.checkSignatures( + anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + mAms.getAuthToken( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, + "authTokenType", // authTokenType + false, // notifyOnAuthFailure + true, // expectActivityLaunch + createGetAuthTokenOptions()); + waitForLatch(latch); + verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture()); + Bundle result = mBundleCaptor.getValue(); + Intent intent = result.getParcelable(AccountManager.KEY_INTENT); + assertNotNull(intent); + assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_RESULT)); + assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK)); + } + + @SmallTest + public void testGetAuthTokenError() throws Exception { + unlockSystemUser(); + when(mMockContext.createPackageContextAsUser( + anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext); + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); + String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE}; + when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list); + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + mAms.getAuthToken( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_ERROR, + "authTokenType", // authTokenType + true, // notifyOnAuthFailure + false, // expectActivityLaunch + createGetAuthTokenOptions()); + waitForLatch(latch); + verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, + AccountManagerServiceTestFixtures.ERROR_MESSAGE); + verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class)); + + } + + @SmallTest + public void testAddAccountAsUserWithNullResponse() throws Exception { + unlockSystemUser(); + try { + mAms.addAccountAsUser( + null, // response + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, + "authTokenType", + null, // requiredFeatures + true, // expectActivityLaunch + null, // optionsIn + UserHandle.USER_SYSTEM); + fail("IllegalArgumentException expected. But no exception was thrown."); + } catch (IllegalArgumentException e) { + } catch(Exception e){ + fail(String.format("Expect IllegalArgumentException, but got %s.", e)); + } + } + + @SmallTest + public void testAddAccountAsUserWithNullAccountType() throws Exception { + unlockSystemUser(); + try { + mAms.addAccountAsUser( + mMockAccountManagerResponse, // response + null, // accountType + "authTokenType", + null, // requiredFeatures + true, // expectActivityLaunch + null, // optionsIn + UserHandle.USER_SYSTEM); + fail("IllegalArgumentException expected. But no exception was thrown."); + } catch (IllegalArgumentException e) { + } catch(Exception e){ + fail(String.format("Expect IllegalArgumentException, but got %s.", e)); + } + } + + @SmallTest + public void testAddAccountAsUserUserCannotModifyAccountNoDPM() throws Exception { + unlockSystemUser(); + Bundle bundle = new Bundle(); + bundle.putBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, true); + when(mMockUserManager.getUserRestrictions(any(UserHandle.class))).thenReturn(bundle); + LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + + mAms.addAccountAsUser( + mMockAccountManagerResponse, // response + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType + "authTokenType", + null, // requiredFeatures + true, // expectActivityLaunch + null, // optionsIn + UserHandle.USER_SYSTEM); + verify(mMockAccountManagerResponse).onError( + eq(AccountManager.ERROR_CODE_USER_RESTRICTED), anyString()); + verify(mMockContext).startActivityAsUser(mIntentCaptor.capture(), eq(UserHandle.SYSTEM)); + + // verify the intent for default CantAddAccountActivity is sent. + Intent intent = mIntentCaptor.getValue(); + assertEquals(intent.getComponent().getClassName(), CantAddAccountActivity.class.getName()); + assertEquals(intent.getIntExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, 0), + AccountManager.ERROR_CODE_USER_RESTRICTED); + } + + @SmallTest + public void testAddAccountAsUserUserCannotModifyAccountWithDPM() throws Exception { + unlockSystemUser(); + Bundle bundle = new Bundle(); + bundle.putBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, true); + when(mMockUserManager.getUserRestrictions(any(UserHandle.class))).thenReturn(bundle); + LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + LocalServices.addService( + DevicePolicyManagerInternal.class, mMockDevicePolicyManagerInternal); + when(mMockDevicePolicyManagerInternal.createUserRestrictionSupportIntent( + anyInt(), anyString())).thenReturn(new Intent()); + when(mMockDevicePolicyManagerInternal.createShowAdminSupportIntent( + anyInt(), anyBoolean())).thenReturn(new Intent()); + + mAms.addAccountAsUser( + mMockAccountManagerResponse, // response + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType + "authTokenType", + null, // requiredFeatures + true, // expectActivityLaunch + null, // optionsIn + UserHandle.USER_SYSTEM); + + verify(mMockAccountManagerResponse).onError( + eq(AccountManager.ERROR_CODE_USER_RESTRICTED), anyString()); + verify(mMockContext).startActivityAsUser(any(Intent.class), eq(UserHandle.SYSTEM)); + verify(mMockDevicePolicyManagerInternal).createUserRestrictionSupportIntent( + anyInt(), anyString()); + } + + @SmallTest + public void testAddAccountAsUserUserCannotModifyAccountForTypeNoDPM() throws Exception { + unlockSystemUser(); + when(mMockDevicePolicyManager.getAccountTypesWithManagementDisabledAsUser(anyInt())) + .thenReturn(new String[]{AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, "BBB"}); + LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + + mAms.addAccountAsUser( + mMockAccountManagerResponse, // response + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType + "authTokenType", + null, // requiredFeatures + true, // expectActivityLaunch + null, // optionsIn + UserHandle.USER_SYSTEM); + + verify(mMockAccountManagerResponse).onError( + eq(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE), anyString()); + verify(mMockContext).startActivityAsUser(mIntentCaptor.capture(), eq(UserHandle.SYSTEM)); + + // verify the intent for default CantAddAccountActivity is sent. + Intent intent = mIntentCaptor.getValue(); + assertEquals(intent.getComponent().getClassName(), CantAddAccountActivity.class.getName()); + assertEquals(intent.getIntExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, 0), + AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE); + } + + @SmallTest + public void testAddAccountAsUserUserCannotModifyAccountForTypeWithDPM() throws Exception { + unlockSystemUser(); + when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn( + mMockDevicePolicyManager); + when(mMockDevicePolicyManager.getAccountTypesWithManagementDisabledAsUser(anyInt())) + .thenReturn(new String[]{AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, "BBB"}); + + LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + LocalServices.addService( + DevicePolicyManagerInternal.class, mMockDevicePolicyManagerInternal); + when(mMockDevicePolicyManagerInternal.createUserRestrictionSupportIntent( + anyInt(), anyString())).thenReturn(new Intent()); + when(mMockDevicePolicyManagerInternal.createShowAdminSupportIntent( + anyInt(), anyBoolean())).thenReturn(new Intent()); + + mAms.addAccountAsUser( + mMockAccountManagerResponse, // response + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType + "authTokenType", + null, // requiredFeatures + true, // expectActivityLaunch + null, // optionsIn + UserHandle.USER_SYSTEM); + + verify(mMockAccountManagerResponse).onError( + eq(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE), anyString()); + verify(mMockContext).startActivityAsUser(any(Intent.class), eq(UserHandle.SYSTEM)); + verify(mMockDevicePolicyManagerInternal).createShowAdminSupportIntent( + anyInt(), anyBoolean()); + } + + @SmallTest + public void testAddAccountAsUserSuccess() throws Exception { + unlockSystemUser(); + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + mAms.addAccountAsUser( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType + "authTokenType", + null, // requiredFeatures + true, // expectActivityLaunch + createAddAccountOptions(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS), + UserHandle.USER_SYSTEM); + waitForLatch(latch); + // Verify notification is cancelled + verify(mMockNotificationManager).cancelNotificationWithTag( + anyString(), anyString(), anyInt(), anyInt()); + + verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture()); + Bundle result = mBundleCaptor.getValue(); + // Verify response data + assertNull(result.getString(AccountManager.KEY_AUTHTOKEN, null)); + assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS, + result.getString(AccountManager.KEY_ACCOUNT_NAME)); + assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, + result.getString(AccountManager.KEY_ACCOUNT_TYPE)); + + Bundle optionBundle = result.getParcelable( + AccountManagerServiceTestFixtures.KEY_OPTIONS_BUNDLE); + // Assert addAccountAsUser added calling uid and pid into the option bundle + assertTrue(optionBundle.containsKey(AccountManager.KEY_CALLER_UID)); + assertTrue(optionBundle.containsKey(AccountManager.KEY_CALLER_PID)); + } + + @SmallTest + public void testAddAccountAsUserReturnWithInvalidIntent() throws Exception { + unlockSystemUser(); + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.applicationInfo = new ApplicationInfo(); + when(mMockPackageManager.resolveActivityAsUser( + any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo); + when(mMockPackageManager.checkSignatures( + anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_NO_MATCH); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + mAms.addAccountAsUser( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType + "authTokenType", + null, // requiredFeatures + true, // expectActivityLaunch + createAddAccountOptions(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE), + UserHandle.USER_SYSTEM); + + waitForLatch(latch); + verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class)); + verify(mMockAccountManagerResponse).onError( + eq(AccountManager.ERROR_CODE_REMOTE_EXCEPTION), anyString()); + } + + @SmallTest + public void testAddAccountAsUserReturnWithValidIntent() throws Exception { + unlockSystemUser(); + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.applicationInfo = new ApplicationInfo(); + when(mMockPackageManager.resolveActivityAsUser( + any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo); + when(mMockPackageManager.checkSignatures( + anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + + mAms.addAccountAsUser( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType + "authTokenType", + null, // requiredFeatures + true, // expectActivityLaunch + createAddAccountOptions(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE), + UserHandle.USER_SYSTEM); + + waitForLatch(latch); + + verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture()); + Bundle result = mBundleCaptor.getValue(); + Intent intent = result.getParcelable(AccountManager.KEY_INTENT); + assertNotNull(intent); + assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_RESULT)); + assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK)); + } + + @SmallTest + public void testAddAccountAsUserError() throws Exception { + unlockSystemUser(); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + + mAms.addAccountAsUser( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType + "authTokenType", + null, // requiredFeatures + true, // expectActivityLaunch + createAddAccountOptions(AccountManagerServiceTestFixtures.ACCOUNT_NAME_ERROR), + UserHandle.USER_SYSTEM); + + waitForLatch(latch); + verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, + AccountManagerServiceTestFixtures.ERROR_MESSAGE); + verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class)); + } + + @SmallTest + public void testConfirmCredentialsAsUserWithNullResponse() throws Exception { + unlockSystemUser(); + try { + mAms.confirmCredentialsAsUser( + null, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, + new Bundle(), // options + false, // expectActivityLaunch + UserHandle.USER_SYSTEM); + fail("IllegalArgumentException expected. But no exception was thrown."); + } catch (IllegalArgumentException e) { + } catch(Exception e){ + fail(String.format("Expect IllegalArgumentException, but got %s.", e)); + } + } + + @SmallTest + public void testConfirmCredentialsAsUserWithNullAccount() throws Exception { + unlockSystemUser(); + try { + mAms.confirmCredentialsAsUser( + mMockAccountManagerResponse, // response + null, // account + new Bundle(), // options + false, // expectActivityLaunch + UserHandle.USER_SYSTEM); + fail("IllegalArgumentException expected. But no exception was thrown."); + } catch (IllegalArgumentException e) { + } catch(Exception e){ + fail(String.format("Expect IllegalArgumentException, but got %s.", e)); + } + } + + @SmallTest + public void testConfirmCredentialsAsUserSuccess() throws Exception { + unlockSystemUser(); + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + mAms.confirmCredentialsAsUser( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, + new Bundle(), // options + true, // expectActivityLaunch + UserHandle.USER_SYSTEM); + waitForLatch(latch); + + verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture()); + Bundle result = mBundleCaptor.getValue(); + // Verify response data + assertTrue(result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)); + assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS, + result.getString(AccountManager.KEY_ACCOUNT_NAME)); + assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, + result.getString(AccountManager.KEY_ACCOUNT_TYPE)); + } + + @SmallTest + public void testConfirmCredentialsAsUserReturnWithInvalidIntent() throws Exception { + unlockSystemUser(); + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.applicationInfo = new ApplicationInfo(); + when(mMockPackageManager.resolveActivityAsUser( + any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo); + when(mMockPackageManager.checkSignatures( + anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_NO_MATCH); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + mAms.confirmCredentialsAsUser( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, + new Bundle(), // options + true, // expectActivityLaunch + UserHandle.USER_SYSTEM); + waitForLatch(latch); + + verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class)); + verify(mMockAccountManagerResponse).onError( + eq(AccountManager.ERROR_CODE_REMOTE_EXCEPTION), anyString()); + } + + @SmallTest + public void testConfirmCredentialsAsUserReturnWithValidIntent() throws Exception { + unlockSystemUser(); + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.applicationInfo = new ApplicationInfo(); + when(mMockPackageManager.resolveActivityAsUser( + any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo); + when(mMockPackageManager.checkSignatures( + anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + + mAms.confirmCredentialsAsUser( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, + new Bundle(), // options + true, // expectActivityLaunch + UserHandle.USER_SYSTEM); + + waitForLatch(latch); + + verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture()); + Bundle result = mBundleCaptor.getValue(); + Intent intent = result.getParcelable(AccountManager.KEY_INTENT); + assertNotNull(intent); + assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_RESULT)); + assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK)); + } + + @SmallTest + public void testConfirmCredentialsAsUserError() throws Exception { + unlockSystemUser(); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + + mAms.confirmCredentialsAsUser( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_ERROR, + new Bundle(), // options + true, // expectActivityLaunch + UserHandle.USER_SYSTEM); + + waitForLatch(latch); + verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, + AccountManagerServiceTestFixtures.ERROR_MESSAGE); + verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class)); + } + + @SmallTest + public void testUpdateCredentialsWithNullResponse() throws Exception { + unlockSystemUser(); + try { + mAms.updateCredentials( + null, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, + "authTokenType", + false, // expectActivityLaunch + new Bundle()); // options + fail("IllegalArgumentException expected. But no exception was thrown."); + } catch (IllegalArgumentException e) { + } catch(Exception e){ + fail(String.format("Expect IllegalArgumentException, but got %s.", e)); + } + } + + @SmallTest + public void testUpdateCredentialsWithNullAccount() throws Exception { + unlockSystemUser(); + try { + mAms.updateCredentials( + mMockAccountManagerResponse, // response + null, // account + "authTokenType", + false, // expectActivityLaunch + new Bundle()); // options + fail("IllegalArgumentException expected. But no exception was thrown."); + } catch (IllegalArgumentException e) { + } catch(Exception e){ + fail(String.format("Expect IllegalArgumentException, but got %s.", e)); + } + } + + @SmallTest + public void testUpdateCredentialsSuccess() throws Exception { + unlockSystemUser(); + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + + mAms.updateCredentials( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, + "authTokenType", + false, // expectActivityLaunch + new Bundle()); // options + + waitForLatch(latch); + + verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture()); + Bundle result = mBundleCaptor.getValue(); + // Verify response data + assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS, + result.getString(AccountManager.KEY_ACCOUNT_NAME)); + assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, + result.getString(AccountManager.KEY_ACCOUNT_TYPE)); + } + + @SmallTest + public void testUpdateCredentialsReturnWithInvalidIntent() throws Exception { + unlockSystemUser(); + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.applicationInfo = new ApplicationInfo(); + when(mMockPackageManager.resolveActivityAsUser( + any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo); + when(mMockPackageManager.checkSignatures( + anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_NO_MATCH); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + + mAms.updateCredentials( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, + "authTokenType", + true, // expectActivityLaunch + new Bundle()); // options + + waitForLatch(latch); + + verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class)); + verify(mMockAccountManagerResponse).onError( + eq(AccountManager.ERROR_CODE_REMOTE_EXCEPTION), anyString()); + } + + @SmallTest + public void testUpdateCredentialsReturnWithValidIntent() throws Exception { + unlockSystemUser(); + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.applicationInfo = new ApplicationInfo(); + when(mMockPackageManager.resolveActivityAsUser( + any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo); + when(mMockPackageManager.checkSignatures( + anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + + mAms.updateCredentials( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, + "authTokenType", + true, // expectActivityLaunch + new Bundle()); // options + + waitForLatch(latch); + + verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture()); + Bundle result = mBundleCaptor.getValue(); + Intent intent = result.getParcelable(AccountManager.KEY_INTENT); + assertNotNull(intent); + assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_RESULT)); + assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK)); + } + + @SmallTest + public void testUpdateCredentialsError() throws Exception { + unlockSystemUser(); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + + mAms.updateCredentials( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_ERROR, + "authTokenType", + false, // expectActivityLaunch + new Bundle()); // options + + waitForLatch(latch); + verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, + AccountManagerServiceTestFixtures.ERROR_MESSAGE); + verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class)); + } + + @SmallTest + public void testEditPropertiesWithNullResponse() throws Exception { + unlockSystemUser(); + try { + mAms.editProperties( + null, // response + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, + false); // expectActivityLaunch + fail("IllegalArgumentException expected. But no exception was thrown."); + } catch (IllegalArgumentException e) { + } catch(Exception e){ + fail(String.format("Expect IllegalArgumentException, but got %s.", e)); + } + } + + @SmallTest + public void testEditPropertiesWithNullAccountType() throws Exception { + unlockSystemUser(); + try { + mAms.editProperties( + mMockAccountManagerResponse, // response + null, // accountType + false); // expectActivityLaunch + fail("IllegalArgumentException expected. But no exception was thrown."); + } catch (IllegalArgumentException e) { + } catch(Exception e){ + fail(String.format("Expect IllegalArgumentException, but got %s.", e)); + } + } + + @SmallTest + public void testEditPropertiesAccountNotManagedByCaller() throws Exception { + unlockSystemUser(); + when(mMockPackageManager.checkSignatures(anyInt(), anyInt())) + .thenReturn(PackageManager.SIGNATURE_NO_MATCH); + try { + mAms.editProperties( + mMockAccountManagerResponse, // response + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, + false); // expectActivityLaunch + fail("SecurityException expected. But no exception was thrown."); + } catch (SecurityException e) { + } catch(Exception e){ + fail(String.format("Expect SecurityException, but got %s.", e)); + } + } + + @SmallTest + public void testEditPropertiesSuccess() throws Exception { + unlockSystemUser(); + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + + mAms.editProperties( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, + false); // expectActivityLaunch + + waitForLatch(latch); + + verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture()); + Bundle result = mBundleCaptor.getValue(); + // Verify response data + assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS, + result.getString(AccountManager.KEY_ACCOUNT_NAME)); + assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, + result.getString(AccountManager.KEY_ACCOUNT_TYPE)); + } + private void waitForLatch(CountDownLatch latch) { try { latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS); @@ -1220,6 +2372,21 @@ public class AccountManagerServiceTest extends AndroidTestCase { } } + private Bundle createAddAccountOptions(String accountName) { + Bundle options = new Bundle(); + options.putString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME, accountName); + return options; + } + + private Bundle createGetAuthTokenOptions() { + Bundle options = new Bundle(); + options.putString(AccountManager.KEY_ANDROID_PACKAGE_NAME, + AccountManagerServiceTestFixtures.CALLER_PACKAGE); + options.putLong(AccountManagerServiceTestFixtures.KEY_TOKEN_EXPIRY, + System.currentTimeMillis() + ONE_DAY_IN_MILLISECOND); + return options; + } + private Bundle encryptBundleWithCryptoHelper(Bundle sessionBundle) { Bundle encryptedBundle = null; try { @@ -1386,6 +2553,12 @@ public class AccountManagerServiceTest extends AndroidTestCase { public String getOpPackageName() { return mMockContext.getOpPackageName(); } + + @Override + public Context createPackageContextAsUser(String packageName, int flags, UserHandle user) + throws PackageManager.NameNotFoundException { + return mMockContext.createPackageContextAsUser(packageName, flags, user); + } } static class TestAccountAuthenticatorCache extends AccountAuthenticatorCache { diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java index 9a2c1903fd21..614680e94cac 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java @@ -31,7 +31,8 @@ public final class AccountManagerServiceTestFixtures { "account_manager_service_test:account_status_token_key"; public static final String KEY_ACCOUNT_PASSWORD = "account_manager_service_test:account_password_key"; - + public static final String KEY_OPTIONS_BUNDLE = + "account_manager_service_test:option_bundle_key"; public static final String ACCOUNT_NAME_SUCCESS = "success_on_return@fixture.com"; public static final String ACCOUNT_NAME_INTERVENE = "intervene@fixture.com"; public static final String ACCOUNT_NAME_ERROR = "error@fixture.com"; @@ -47,7 +48,20 @@ public final class AccountManagerServiceTestFixtures { public static final String ACCOUNT_STATUS_TOKEN = "com.android.server.accounts.account_manager_service_test.account.status.token"; - + public static final String AUTH_TOKEN_LABEL = + "com.android.server.accounts.account_manager_service_test.auth.token.label"; + public static final String AUTH_TOKEN = + "com.android.server.accounts.account_manager_service_test.auth.token"; + public static final String KEY_TOKEN_EXPIRY = + "com.android.server.accounts.account_manager_service_test.auth.token.expiry"; + public static final String ACCOUNT_FEATURE1 = + "com.android.server.accounts.account_manager_service_test.feature1"; + public static final String ACCOUNT_FEATURE2 = + "com.android.server.accounts.account_manager_service_test.feature2"; + public static final String[] ACCOUNT_FEATURES = + new String[]{ACCOUNT_FEATURE1, ACCOUNT_FEATURE2}; + public static final String CALLER_PACKAGE = + "com.android.server.accounts.account_manager_service_test.caller.package"; public static final String ACCOUNT_PASSWORD = "com.android.server.accounts.account_manager_service_test.account.password"; public static final String KEY_RESULT = "account_manager_service_test:result"; diff --git a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java index 8ec61763cdb8..02b34ab3549a 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java +++ b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java @@ -45,8 +45,15 @@ public class TestAccountType1Authenticator extends AbstractAccountAuthenticator @Override public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { - throw new UnsupportedOperationException( - "editProperties is not yet supported by the TestAccountAuthenticator"); + Bundle result = new Bundle(); + result.putString(AccountManager.KEY_ACCOUNT_NAME, + AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1); + result.putString( + AccountManager.KEY_AUTHTOKEN, + Integer.toString(mTokenCounter.incrementAndGet())); + return result; } @Override @@ -59,10 +66,38 @@ public class TestAccountType1Authenticator extends AbstractAccountAuthenticator if (!mAccountType.equals(accountType)) { throw new IllegalArgumentException("Request to the wrong authenticator!"); } + String accountName = null; + + if (options != null) { + accountName = options.getString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME); + } Bundle result = new Bundle(); - result.putString(AccountManager.KEY_ACCOUNT_NAME, "test_account@test.com"); - result.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccountType); + if (accountName.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) { + // fill bundle with a success result. + result.putString(AccountManager.KEY_ACCOUNT_NAME, accountName); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccountType); + result.putString(AccountManager.KEY_AUTHTOKEN, + Integer.toString(mTokenCounter.incrementAndGet())); + result.putParcelable(AccountManagerServiceTestFixtures.KEY_OPTIONS_BUNDLE, options); + } else if (accountName.equals( + AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) { + // Specify data to be returned by the eventual activity. + Intent eventualActivityResultData = new Intent(); + eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_NAME, accountName); + eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType); + // Fill result with Intent. + Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class); + intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT, eventualActivityResultData); + intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response); + + result.putParcelable(AccountManager.KEY_INTENT, intent); + } else { + fillResultWithError( + result, + AccountManager.ERROR_CODE_INVALID_RESPONSE, + AccountManagerServiceTestFixtures.ERROR_MESSAGE); + } return result; } @@ -71,8 +106,38 @@ public class TestAccountType1Authenticator extends AbstractAccountAuthenticator AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException { - throw new UnsupportedOperationException( - "confirmCredentials is not yet supported by the TestAccountAuthenticator"); + if (!mAccountType.equals(account.type)) { + throw new IllegalArgumentException("Request to the wrong authenticator!"); + } + Bundle result = new Bundle(); + + if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) { + // fill bundle with a success result. + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); + result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); + } else if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) { + // Specify data to be returned by the eventual activity. + Intent eventualActivityResultData = new Intent(); + eventualActivityResultData.putExtra(AccountManager.KEY_BOOLEAN_RESULT, true); + eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name); + eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_TYPE, account.type); + + // Fill result with Intent. + Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class); + intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT, + eventualActivityResultData); + intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response); + + result.putParcelable(AccountManager.KEY_INTENT, intent); + } else { + // fill with error + fillResultWithError( + result, + AccountManager.ERROR_CODE_INVALID_RESPONSE, + AccountManagerServiceTestFixtures.ERROR_MESSAGE); + } + return result; } @Override @@ -81,14 +146,53 @@ public class TestAccountType1Authenticator extends AbstractAccountAuthenticator Account account, String authTokenType, Bundle options) throws NetworkErrorException { - throw new UnsupportedOperationException( - "getAuthToken is not yet supported by the TestAccountAuthenticator"); + if (!mAccountType.equals(account.type)) { + throw new IllegalArgumentException("Request to the wrong authenticator!"); + } + Bundle result = new Bundle(); + + long expiryMillis = (options == null) + ? 0 : options.getLong(AccountManagerServiceTestFixtures.KEY_TOKEN_EXPIRY); + if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) { + // fill bundle with a success result. + result.putString( + AccountManager.KEY_AUTHTOKEN, AccountManagerServiceTestFixtures.AUTH_TOKEN); + result.putLong( + AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY, + expiryMillis); + result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); + } else if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) { + // Specify data to be returned by the eventual activity. + Intent eventualActivityResultData = new Intent(); + eventualActivityResultData.putExtra( + AccountManager.KEY_AUTHTOKEN, AccountManagerServiceTestFixtures.AUTH_TOKEN); + eventualActivityResultData.putExtra( + AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY, + expiryMillis); + eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name); + eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_TYPE, account.type); + + // Fill result with Intent. + Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class); + intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT, + eventualActivityResultData); + intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response); + + result.putParcelable(AccountManager.KEY_INTENT, intent); + + } else { + fillResultWithError( + result, + AccountManager.ERROR_CODE_INVALID_RESPONSE, + AccountManagerServiceTestFixtures.ERROR_MESSAGE); + } + return result; } @Override public String getAuthTokenLabel(String authTokenType) { - throw new UnsupportedOperationException( - "getAuthTokenLabel is not yet supported by the TestAccountAuthenticator"); + return AccountManagerServiceTestFixtures.AUTH_TOKEN_LABEL; } @Override @@ -101,8 +205,31 @@ public class TestAccountType1Authenticator extends AbstractAccountAuthenticator throw new IllegalArgumentException("Request to the wrong authenticator!"); } Bundle result = new Bundle(); - result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); - result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); + + if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) { + // fill bundle with a success result. + result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); + } else if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) { + // Specify data to be returned by the eventual activity. + Intent eventualActivityResultData = new Intent(); + eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name); + eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_TYPE, account.type); + + // Fill result with Intent. + Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class); + intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT, + eventualActivityResultData); + intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response); + + result.putParcelable(AccountManager.KEY_INTENT, intent); + } else { + // fill with error + fillResultWithError( + result, + AccountManager.ERROR_CODE_INVALID_RESPONSE, + AccountManagerServiceTestFixtures.ERROR_MESSAGE); + } return result; } @@ -111,8 +238,17 @@ public class TestAccountType1Authenticator extends AbstractAccountAuthenticator AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException { - throw new UnsupportedOperationException( - "hasFeatures is not yet supported by the TestAccountAuthenticator"); + Bundle result = new Bundle(); + if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) { + // fill bundle with a success result. + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); + } else { + // return null for error + result = null; + } + + response.onResult(result); + return null; } @Override @@ -300,6 +436,22 @@ public class TestAccountType1Authenticator extends AbstractAccountAuthenticator return null; } + @Override + public Bundle getAccountRemovalAllowed( + AccountAuthenticatorResponse response, Account account) throws NetworkErrorException { + Bundle result = new Bundle(); + if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) { + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); + } else if (account.name.equals( + AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) { + Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class); + result.putParcelable(AccountManager.KEY_INTENT, intent); + } else { + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); + } + return result; + } + private void fillResultWithError(Bundle result, Bundle options) { int errorCode = AccountManager.ERROR_CODE_INVALID_RESPONSE; String errorMsg = "Default Error Message"; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 8da47c8b7408..8fad3eff38fa 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -2358,6 +2358,23 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; } + private void setup_nonSplitUser_withDo_primaryUser() throws Exception { + setDeviceOwner(); + setup_nonSplitUser_afterDeviceSetup_primaryUser(); + setUpPackageManagerForFakeAdmin(adminAnotherPackage, DpmMockContext.ANOTHER_UID, admin2); + } + + private void setup_nonSplitUser_withDo_primaryUser_ManagedProfile() throws Exception { + setup_nonSplitUser_withDo_primaryUser(); + final int MANAGED_PROFILE_USER_ID = 18; + final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 1308); + addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); + when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, + false /* we can't remove a managed profile */)).thenReturn(false); + when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, + true)).thenReturn(true); + } + public void testIsProvisioningAllowed_nonSplitUser_afterDeviceSetup_primaryUser() throws Exception { setup_nonSplitUser_afterDeviceSetup_primaryUser(); @@ -2387,144 +2404,124 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_NOT_SYSTEM_USER_SPLIT); } - public void testIsProvisioningAllowed_nonSplitUser_withDo_primaryUser() throws Exception { - setDeviceOwner(); - setup_nonSplitUser_afterDeviceSetup_primaryUser(); - setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); + public void testProvisioning_nonSplitUser_withDo_primaryUser() throws Exception { + setup_nonSplitUser_withDo_primaryUser(); mContext.packageName = admin1.getPackageName(); + mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); - final ComponentName adminDifferentPackage = - new ComponentName("another.package", "whatever.random.class"); - final int ANOTHER_UID = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, 948); - setUpPackageManagerForFakeAdmin(adminDifferentPackage, ANOTHER_UID, admin2); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, + DevicePolicyManager.CODE_HAS_DEVICE_OWNER); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false); // COMP mode is allowed. + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + DevicePolicyManager.CODE_OK); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + // And other DPCs can also provision a managed profile (DO + BYOD case). + assertCheckProvisioningPreCondition( + DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + DpmMockContext.ANOTHER_PACKAGE_NAME, + DevicePolicyManager.CODE_OK); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true, + DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); + } + + public void testProvisioning_nonSplitUser_withDo_primaryUser_restrictedByDo() throws Exception { + setup_nonSplitUser_withDo_primaryUser(); + mContext.packageName = admin1.getPackageName(); + mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); + // The DO should be allowed to initiate provisioning if it set the restriction itself, but + // other packages should be forbidden. when(mContext.userManager.hasUserRestriction( eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) .thenReturn(true); - - // The DO should be allowed to initiate provisioning if it set the restriction itself. when(mContext.userManager.getUserRestrictionSource( eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + DevicePolicyManager.CODE_OK); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + assertCheckProvisioningPreCondition( + DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + DpmMockContext.ANOTHER_PACKAGE_NAME, + DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false, + DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); + } - // But another app should not - mContext.binder.callingUid = ANOTHER_UID; - mContext.packageName = adminDifferentPackage.getPackageName(); - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); - + public void testProvisioning_nonSplitUser_withDo_primaryUser_restrictedBySystem() + throws Exception { + setup_nonSplitUser_withDo_primaryUser(); + mContext.packageName = admin1.getPackageName(); + mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); // The DO should not be allowed to initiate provisioning if the restriction is set by // another entity. + when(mContext.userManager.hasUserRestriction( + eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), + eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) + .thenReturn(true); when(mContext.userManager.getUserRestrictionSource( eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM); - mContext.binder.callingUid = DpmMockContext.CALLER_UID; - mContext.packageName = admin1.getPackageName(); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); - mContext.binder.callingUid = ANOTHER_UID; - mContext.packageName = adminDifferentPackage.getPackageName(); - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); + assertCheckProvisioningPreCondition( + DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + DpmMockContext.ANOTHER_PACKAGE_NAME, + DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false, + DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); } - public void testIsProvisioningAllowed_nonSplitUser_comp() throws Exception { - setDeviceOwner(); - setup_nonSplitUser_afterDeviceSetup_primaryUser(); - setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID); - - final ComponentName adminDifferentPackage = - new ComponentName("another.package", "whatever.class"); - final int ANOTHER_UID = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, 948); - setUpPackageManagerForFakeAdmin(adminDifferentPackage, ANOTHER_UID, admin2); - - final int MANAGED_PROFILE_USER_ID = 18; - final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 1308); - addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1); - - when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE, - false /* we can't remove a managed profile */)).thenReturn(false); - when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE, - true)).thenReturn(true); - - // We can delete the managed profile to create a new one, so provisioning is allowed. - mContext.packageName = admin1.getPackageName(); - mContext.binder.callingUid = DpmMockContext.CALLER_UID; - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); - - mContext.packageName = adminDifferentPackage.getPackageName(); - mContext.binder.callingUid = ANOTHER_UID; - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); - - when(mContext.userManager.hasUserRestriction( - eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE), - eq(UserHandle.of(DpmMockContext.CALLER_USER_HANDLE)))) - .thenReturn(true); - - // Now, we can't remove the profile any more to create a new one. + public void testCheckProvisioningPreCondition_nonSplitUser_comp() throws Exception { + setup_nonSplitUser_withDo_primaryUser_ManagedProfile(); mContext.packageName = admin1.getPackageName(); - mContext.binder.callingUid = DpmMockContext.CALLER_UID; - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); - - mContext.packageName = adminDifferentPackage.getPackageName(); - mContext.binder.callingUid = ANOTHER_UID; - assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); - } - - public void - testCheckProvisioningPreCondition_nonSplitUser_withDo_primaryUser() throws Exception { - setDeviceOwner(); - setup_nonSplitUser_afterDeviceSetup_primaryUser(); mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); - assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, - DevicePolicyManager.CODE_HAS_DEVICE_OWNER); - - // COMP mode is allowed. + // We can delete the managed profile to create a new one, so provisioning is allowed. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_OK); - - // And other DPCs can also provisioning a managed profile (DO + BYOD case). + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); assertCheckProvisioningPreCondition( DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - "some.other.dpc.package.name", + DpmMockContext.ANOTHER_PACKAGE_NAME, DevicePolicyManager.CODE_OK); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true, + DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); + } + public void testCheckProvisioningPreCondition_nonSplitUser_comp_cannot_remove_profile() + throws Exception { + setup_nonSplitUser_withDo_primaryUser_ManagedProfile(); + mContext.packageName = admin1.getPackageName(); + mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); when(mContext.userManager.hasUserRestriction( - eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), - eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) + eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE), + eq(UserHandle.SYSTEM))) .thenReturn(true); - - // The DO should be allowed to initiate provisioning if it set the restriction itself, but - // other packages should be forbidden. when(mContext.userManager.getUserRestrictionSource( - eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), - eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) + eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE), + eq(UserHandle.SYSTEM))) .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER); - assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_OK); + + // We can't remove the profile to create a new one. assertCheckProvisioningPreCondition( DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - "some.other.dpc.package.name", - DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); + DpmMockContext.ANOTHER_PACKAGE_NAME, + DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false, + DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); - // The DO should not be allowed to initiate provisioning if the restriction is set by - // another entity. - when(mContext.userManager.getUserRestrictionSource( - eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), - eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) - .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM); + // But the device owner can still do it because it has set the restriction itself. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); - assertCheckProvisioningPreCondition( - DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - "some.other.dpc.package.name", - DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); + DevicePolicyManager.CODE_OK); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); } private void setup_splitUser_firstBoot_systemUser() throws Exception { @@ -3287,6 +3284,21 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.isProvisioningAllowed(action)); } + private void assertProvisioningAllowed(String action, boolean expected, String packageName, + int uid) { + String previousPackageName = mContext.packageName; + int previousUid = mMockContext.binder.callingUid; + + // Call assertProvisioningAllowed with the packageName / uid passed as arguments. + mContext.packageName = packageName; + mMockContext.binder.callingUid = uid; + assertProvisioningAllowed(action, expected); + + // Set the previous package name / calling uid to go back to the initial state. + mContext.packageName = previousPackageName; + mMockContext.binder.callingUid = previousUid; + } + private void assertCheckProvisioningPreCondition(String action, int provisioningCondition) { assertCheckProvisioningPreCondition(action, admin1.getPackageName(), provisioningCondition); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index 44bf547460dd..5c4a65871917 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -99,6 +99,10 @@ public class DpmMockContext extends MockContext { */ public static final int SYSTEM_PID = 11111; + public static final String ANOTHER_PACKAGE_NAME = "com.another.package.name"; + + public static final int ANOTHER_UID = UserHandle.getUid(UserHandle.USER_SYSTEM, 18434); + public static class MockBinder { public int callingUid = CALLER_UID; public int callingPid = CALLER_PID; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java index 8a1197618acd..ed6779c41491 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java @@ -45,6 +45,7 @@ public abstract class DpmTestBase extends AndroidTestCase { public ComponentName admin1; public ComponentName admin2; public ComponentName admin3; + public ComponentName adminAnotherPackage; public ComponentName adminNoPerm; @Override @@ -59,6 +60,8 @@ public abstract class DpmTestBase extends AndroidTestCase { admin1 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin1.class); admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class); admin3 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin3.class); + adminAnotherPackage = new ComponentName(DpmMockContext.ANOTHER_PACKAGE_NAME, + "whatever.random.class"); adminNoPerm = new ComponentName(mRealTestContext, DummyDeviceAdmins.AdminNoPerm.class); } diff --git a/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java b/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java index 2aca702b809c..13a3c2f23988 100644 --- a/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java @@ -155,7 +155,7 @@ public class DiskStatsFileLoggerTest extends AndroidTestCase { } @Test - public void testDuplicatePackageNameIsMergedAcrossMultipleUsers() throws Exception { + public void testDuplicatePackageNameIsNotMergedAcrossMultipleUsers() throws Exception { PackageStats app = new PackageStats("com.test.app"); app.dataSize = 1000; app.externalDataSize = 1000; @@ -175,19 +175,19 @@ public class DiskStatsFileLoggerTest extends AndroidTestCase { logger.dumpToFile(mOutputFile); JSONObject output = getOutputFileAsJson(); - assertThat(output.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(2200); - assertThat(output.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(22); + assertThat(output.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(2000); + assertThat(output.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(20); JSONArray packageNames = output.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY); assertThat(packageNames.length()).isEqualTo(1); assertThat(packageNames.getString(0)).isEqualTo("com.test.app"); JSONArray appSizes = output.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY); assertThat(appSizes.length()).isEqualTo(1); - assertThat(appSizes.getLong(0)).isEqualTo(2200); + assertThat(appSizes.getLong(0)).isEqualTo(2000); JSONArray cacheSizes = output.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY); assertThat(cacheSizes.length()).isEqualTo(1); - assertThat(cacheSizes.getLong(0)).isEqualTo(22); + assertThat(cacheSizes.getLong(0)).isEqualTo(20); } private JSONObject getOutputFileAsJson() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java index 26accc3bcd41..2af4163770ae 100644 --- a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java @@ -18,12 +18,10 @@ package com.android.server.wm; import org.junit.Test; -import android.os.Binder; -import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.IApplicationToken; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -74,6 +72,67 @@ public class AppWindowContainerControllerTests extends WindowTestsBase { controller.removeContainer(sDisplayContent.getDisplayId()); // Assert orientation is unspecified to after container is removed. assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, controller.getOrientation()); + + // Reset display frozen state + sWm.mDisplayFrozen = false; + } + + private void assertHasStartingWindow(AppWindowToken atoken) { + assertNotNull(atoken.startingSurface); + assertNotNull(atoken.startingData); + assertNotNull(atoken.startingWindow); + } + + private void assertNoStartingWindow(AppWindowToken atoken) { + assertNull(atoken.startingSurface); + assertNull(atoken.startingWindow); + assertNull(atoken.startingData); + } + + @Test + public void testCreateRemoveStartingWindow() throws Exception { + final TestAppWindowContainerController controller = createAppWindowController(); + controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false); + waitUntilHandlerIdle(); + final AppWindowToken atoken = controller.getAppWindowToken(); + assertHasStartingWindow(atoken); + controller.removeStartingWindow(); + waitUntilHandlerIdle(); + assertNoStartingWindow(atoken); + } + + @Test + public void testTransferStartingWindow() throws Exception { + final TestAppWindowContainerController controller1 = createAppWindowController(); + final TestAppWindowContainerController controller2 = createAppWindowController(); + controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false); + waitUntilHandlerIdle(); + controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(), + true, true, false); + waitUntilHandlerIdle(); + assertNoStartingWindow(controller1.getAppWindowToken()); + assertHasStartingWindow(controller2.getAppWindowToken()); + } + + @Test + public void testTransferStartingWindowWhileCreating() throws Exception { + final TestAppWindowContainerController controller1 = createAppWindowController(); + final TestAppWindowContainerController controller2 = createAppWindowController(); + sPolicy.setRunnableWhenAddingSplashScreen(() -> { + + // Surprise, ...! Transfer window in the middle of the creation flow. + controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(), + true, true, false); + }); + controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false); + waitUntilHandlerIdle(); + assertNoStartingWindow(controller1.getAppWindowToken()); + assertHasStartingWindow(controller2.getAppWindowToken()); } private TestAppWindowContainerController createAppWindowController() { diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java index 1d9875f308e9..154fa9140d82 100644 --- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java @@ -119,6 +119,7 @@ public class AppWindowTokenTests extends WindowTestsBase { sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */); assertEquals(SCREEN_ORIENTATION_REVERSE_LANDSCAPE, sWm.mLastOrientation); assertTrue(appWindow.resizeReported); + appWindow.removeImmediately(); } @Test @@ -148,5 +149,6 @@ public class AppWindowTokenTests extends WindowTestsBase { sWm.updateRotation(false, false); sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */); assertTrue(appWindow.resizeReported); + appWindow.removeImmediately(); } } diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java index 85931e8ac878..e54e319fa6a1 100644 --- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java @@ -236,7 +236,7 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(dc, token.getDisplayContent()); // Move stack to first display. - sWm.moveStackToDisplay(stack.mStackId, sDisplayContent.getDisplayId()); + sDisplayContent.moveStackToDisplay(stack); assertEquals(sDisplayContent.getDisplayId(), stack.getDisplayContent().getDisplayId()); assertEquals(sDisplayContent, stack.getParent().getParent()); assertEquals(sDisplayContent, stack.getDisplayContent()); diff --git a/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java new file mode 100644 index 000000000000..7a789d4aacbd --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; + +import android.graphics.Rect; +import android.hardware.display.DisplayManagerGlobal; +import android.view.Display; +import android.view.DisplayInfo; +import org.junit.Test; +import org.junit.runner.RunWith; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Test class for {@link StackWindowController}. + * + * Build/Install/Run: + * bit FrameworksServicesTests:com.android.server.wm.StackWindowControllerTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class StackWindowControllerTests extends WindowTestsBase { + @Test + public void testRemoveContainer() throws Exception { + final StackWindowController stackController = + createStackControllerOnDisplay(sDisplayContent); + final TestTaskWindowContainerController taskController = + new TestTaskWindowContainerController(stackController); + + final TaskStack stack = stackController.mContainer; + final Task task = taskController.mContainer; + assertNotNull(stack); + assertNotNull(task); + stackController.removeContainer(); + // Assert that the container was removed. + assertNull(stackController.mContainer); + assertNull(taskController.mContainer); + assertNull(stack.getDisplayContent()); + assertNull(task.getDisplayContent()); + assertNull(task.mStack); + } + + @Test + public void testRemoveContainer_deferRemoval() throws Exception { + final StackWindowController stackController = + createStackControllerOnDisplay(sDisplayContent); + final TestTaskWindowContainerController taskController = + new TestTaskWindowContainerController(stackController); + + final TaskStack stack = stackController.mContainer; + final TestTask task = (TestTask) taskController.mContainer; + // Stack removal is deferred if one of its child is animating. + task.setLocalIsAnimating(true); + + stackController.removeContainer(); + // For the case of deferred removal the stack controller will no longer be connected to the + // container, but the task controller will still be connected to the its container until + // the stack window container is removed. + assertNull(stackController.mContainer); + assertNull(stack.getController()); + assertNotNull(taskController.mContainer); + assertNotNull(task.getController()); + + stack.removeImmediately(); + assertNull(taskController.mContainer); + assertNull(task.getController()); + } + + @Test + public void testReparent() throws Exception { + // Create first stack on primary display. + final StackWindowController stack1Controller = + createStackControllerOnDisplay(sDisplayContent); + final TaskStack stack1 = stack1Controller.mContainer; + final TestTaskWindowContainerController taskController = + new TestTaskWindowContainerController(stack1Controller); + final TestTask task1 = (TestTask) taskController.mContainer; + task1.mOnDisplayChangedCalled = false; + + // Create second display and put second stack on it. + final Display display = new Display(DisplayManagerGlobal.getInstance(), + sDisplayContent.getDisplayId() + 1, new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS); + final DisplayContent dc = new DisplayContent(display, sWm, sLayersController, + new WallpaperController(sWm)); + sWm.mRoot.addChild(dc, 1); + + final StackWindowController stack2Controller = + createStackControllerOnDisplay(dc); + final TaskStack stack2 = stack2Controller.mContainer; + + // Reparent + stack1Controller.reparent(dc.getDisplayId(), new Rect()); + assertEquals(dc, stack1.getDisplayContent()); + final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1); + final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2); + assertEquals(stack1PositionInParent, stack2PositionInParent + 1); + assertTrue(task1.mOnDisplayChangedCalled); + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java new file mode 100644 index 000000000000..aab75ee1699b --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.Color; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link TaskSnapshotSurface}. + * + * runtest frameworks-services -c com.android.server.wm.TaskSnapshotSurfaceTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class TaskSnapshotSurfaceTest extends WindowTestsBase { + + private TaskSnapshotSurface mSurface; + + @Before + public void setUp() { + mSurface = new TaskSnapshotSurface(null, null, null, Color.WHITE); + } + + @Test + public void fillEmptyBackground_fillHorizontally() throws Exception { + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(200); + when(mockCanvas.getHeight()).thenReturn(100); + final Bitmap b = Bitmap.createBitmap(100, 200, Config.ARGB_8888); + mSurface.fillEmptyBackground(mockCanvas, b); + verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any()); + } + + @Test + public void fillEmptyBackground_fillVertically() throws Exception { + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(200); + final Bitmap b = Bitmap.createBitmap(200, 100, Config.ARGB_8888); + mSurface.fillEmptyBackground(mockCanvas, b); + verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any()); + } + + @Test + public void fillEmptyBackground_fillBoth() throws Exception { + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(200); + when(mockCanvas.getHeight()).thenReturn(200); + final Bitmap b = Bitmap.createBitmap(100, 100, Config.ARGB_8888); + mSurface.fillEmptyBackground(mockCanvas, b); + verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any()); + verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any()); + } + + @Test + public void fillEmptyBackground_dontFill_sameSize() throws Exception { + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + final Bitmap b = Bitmap.createBitmap(100, 100, Config.ARGB_8888); + mSurface.fillEmptyBackground(mockCanvas, b); + verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); + } + + @Test + public void fillEmptyBackground_dontFill_bitmapLarger() throws Exception { + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + final Bitmap b = Bitmap.createBitmap(200, 200, Config.ARGB_8888); + mSurface.fillEmptyBackground(mockCanvas, b); + verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java index bb9bc9eef97a..462bd68dc420 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java @@ -17,17 +17,16 @@ package com.android.server.wm; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; -import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.Before; +import org.junit.After; -import android.hardware.display.DisplayManagerGlobal; +import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.Display; -import android.view.DisplayInfo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -46,95 +45,69 @@ import static org.junit.Assert.assertTrue; @RunWith(AndroidJUnit4.class) public class TaskStackContainersTests extends WindowTestsBase { + private TaskStack mPinnedStack; + + @Before + public void setUp() throws Exception { + super.setUp(); + mPinnedStack = new StackWindowController(PINNED_STACK_ID, null, + sDisplayContent.getDisplayId(), true /* onTop */, new Rect(), sWm).mContainer; + + // Stack should contain visible app window to be considered visible. + final Task pinnedTask = createTaskInStack(mPinnedStack, 0 /* userId */); + assertFalse(mPinnedStack.isVisible()); + final TestAppWindowToken pinnedApp = new TestAppWindowToken(sDisplayContent); + pinnedTask.addChild(pinnedApp, 0 /* addPos */); + assertTrue(mPinnedStack.isVisible()); + } + + @After + public void tearDown() throws Exception { + mPinnedStack.removeImmediately(); + } + @Test public void testStackPositionChildAt() throws Exception { // Test that always-on-top stack can't be moved to position other than top. final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent); final TaskStack stack2 = createTaskStackOnDisplay(sDisplayContent); - final TaskStack pinnedStack = addPinnedStack(); final WindowContainer taskStackContainer = stack1.getParent(); final int stack1Pos = taskStackContainer.mChildren.indexOf(stack1); final int stack2Pos = taskStackContainer.mChildren.indexOf(stack2); - final int pinnedStackPos = taskStackContainer.mChildren.indexOf(pinnedStack); + final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack); assertGreaterThan(pinnedStackPos, stack2Pos); assertGreaterThan(stack2Pos, stack1Pos); - taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, pinnedStack, false); + taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, mPinnedStack, false); assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1); assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2); - assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack); + assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack); - taskStackContainer.positionChildAt(1, pinnedStack, false); + taskStackContainer.positionChildAt(1, mPinnedStack, false); assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1); assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2); - assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack); + assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack); } + @Test public void testStackPositionBelowPinnedStack() throws Exception { // Test that no stack can be above pinned stack. - final TaskStack pinnedStack = addPinnedStack(); final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent); final WindowContainer taskStackContainer = stack1.getParent(); final int stackPos = taskStackContainer.mChildren.indexOf(stack1); - final int pinnedStackPos = taskStackContainer.mChildren.indexOf(pinnedStack); + final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack); assertGreaterThan(pinnedStackPos, stackPos); taskStackContainer.positionChildAt(WindowContainer.POSITION_TOP, stack1, false); assertEquals(taskStackContainer.mChildren.get(stackPos), stack1); - assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack); + assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack); taskStackContainer.positionChildAt(taskStackContainer.mChildren.size() - 1, stack1, false); assertEquals(taskStackContainer.mChildren.get(stackPos), stack1); - assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack); - } - - @Test - public void testReparentBetweenDisplays() throws Exception { - // Create first stack on primary display. - final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent); - final TestTaskWindowContainerController taskController = - new TestTaskWindowContainerController(stack1.mStackId); - final TestTask task1 = (TestTask) taskController.mContainer; - task1.mOnDisplayChangedCalled = false; - - // Create second display and put second stack on it. - final Display display = new Display(DisplayManagerGlobal.getInstance(), - sDisplayContent.getDisplayId() + 1, new DisplayInfo(), - DEFAULT_DISPLAY_ADJUSTMENTS); - final DisplayContent dc = new DisplayContent(display, sWm, sLayersController, - new WallpaperController(sWm)); - sWm.mRoot.addChild(dc, 1); - final TaskStack stack2 = createTaskStackOnDisplay(dc); - - // Reparent and check state.DisplayContent.java:2572 - sWm.moveStackToDisplay(stack1.mStackId, dc.getDisplayId()); - assertEquals(dc, stack1.getDisplayContent()); - final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1); - final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2); - assertEquals(stack1PositionInParent, stack2PositionInParent + 1); - assertTrue(task1.mOnDisplayChangedCalled); - } - - private TaskStack addPinnedStack() { - TaskStack pinnedStack = sWm.mStackIdToStack.get(PINNED_STACK_ID); - if (pinnedStack == null) { - sDisplayContent.addStackToDisplay(PINNED_STACK_ID, true); - pinnedStack = sWm.mStackIdToStack.get(PINNED_STACK_ID); - } - - if (!pinnedStack.isVisible()) { - // Stack should contain visible app window to be considered visible. - final Task pinnedTask = createTaskInStack(pinnedStack, 0 /* userId */); - assertFalse(pinnedStack.isVisible()); - final TestAppWindowToken pinnedApp = new TestAppWindowToken(sDisplayContent); - pinnedTask.addChild(pinnedApp, 0 /* addPos */); - assertTrue(pinnedStack.isVisible()); - } - - return pinnedStack; + assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack); } } diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java index 7cd3f648d8b1..3ee1da434edf 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java @@ -18,14 +18,14 @@ package com.android.server.wm; import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; -import org.junit.Test; - import android.hardware.display.DisplayManagerGlobal; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.view.Display; import android.view.DisplayInfo; +import org.junit.Test; +import org.junit.runner.RunWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -40,7 +40,7 @@ import static org.junit.Assert.assertTrue; */ @SmallTest @Presubmit -@org.junit.runner.RunWith(AndroidJUnit4.class) +@RunWith(AndroidJUnit4.class) public class TaskWindowContainerControllerTests extends WindowTestsBase { @Test @@ -57,7 +57,7 @@ public class TaskWindowContainerControllerTests extends WindowTestsBase { } @Test - public void testRemoveContainer_DeferRemoval() throws Exception { + public void testRemoveContainer_deferRemoval() throws Exception { final TestTaskWindowContainerController taskController = new TestTaskWindowContainerController(); final TestAppWindowContainerController appController = @@ -83,58 +83,66 @@ public class TaskWindowContainerControllerTests extends WindowTestsBase { @Test public void testReparent() throws Exception { - final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent); + final StackWindowController stackController1 = + createStackControllerOnDisplay(sDisplayContent); final TestTaskWindowContainerController taskController = - new TestTaskWindowContainerController(stack1.mStackId); - final TaskStack stack2 = createTaskStackOnDisplay(sDisplayContent); + new TestTaskWindowContainerController(stackController1); + final StackWindowController stackController2 = + createStackControllerOnDisplay(sDisplayContent); final TestTaskWindowContainerController taskController2 = - new TestTaskWindowContainerController(stack2.mStackId); + new TestTaskWindowContainerController(stackController2); boolean gotException = false; try { - taskController.reparent(stack1.mStackId, 0); + taskController.reparent(stackController1, 0); } catch (IllegalArgumentException e) { gotException = true; } assertTrue("Should not be able to reparent to the same parent", gotException); + final StackWindowController stackController3 = + createStackControllerOnDisplay(sDisplayContent); + stackController3.setContainer(null); gotException = false; try { - taskController.reparent(sNextStackId + 1, 0); + taskController.reparent(stackController3, 0); } catch (IllegalArgumentException e) { gotException = true; } - assertTrue("Should not be able to reparent to a stackId that doesn't exist", gotException); + assertTrue("Should not be able to reparent to a stack that doesn't have a container", + gotException); - taskController.reparent(stack2.mStackId, 0); - assertEquals(stack2, taskController.mContainer.getParent()); + taskController.reparent(stackController2, 0); + assertEquals(stackController2.mContainer, taskController.mContainer.getParent()); assertEquals(0, ((TestTask) taskController.mContainer).positionInParent()); assertEquals(1, ((TestTask) taskController2.mContainer).positionInParent()); } @Test - public void testReparentBetweenDisplays() throws Exception { + public void testReparent_BetweenDisplays() throws Exception { // Create first stack on primary display. - final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent); + final StackWindowController stack1Controller = + createStackControllerOnDisplay(sDisplayContent); + final TaskStack stack1 = stack1Controller.mContainer; final TestTaskWindowContainerController taskController = - new TestTaskWindowContainerController(stack1.mStackId); + new TestTaskWindowContainerController(stack1Controller); final TestTask task1 = (TestTask) taskController.mContainer; task1.mOnDisplayChangedCalled = false; // Create second display and put second stack on it. final Display display = new Display(DisplayManagerGlobal.getInstance(), - sDisplayContent.getDisplayId() + 1, new DisplayInfo(), - DEFAULT_DISPLAY_ADJUSTMENTS); + sDisplayContent.getDisplayId() + 1, new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS); final DisplayContent dc = new DisplayContent(display, sWm, sLayersController, new WallpaperController(sWm)); sWm.mRoot.addChild(dc, 1); - final TaskStack stack2 = createTaskStackOnDisplay(dc); + final StackWindowController stack2Controller = createStackControllerOnDisplay(dc); + final TaskStack stack2 = stack2Controller.mContainer; final TestTaskWindowContainerController taskController2 = - new TestTaskWindowContainerController(stack2.mStackId); + new TestTaskWindowContainerController(stack2Controller); final TestTask task2 = (TestTask) taskController2.mContainer; // Reparent and check state - taskController.reparent(stack2.mStackId, 0); + taskController.reparent(stack2Controller, 0); assertEquals(stack2, task1.getParent()); assertEquals(0, task1.positionInParent()); assertEquals(1, task2.positionInParent()); diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java index 269b71986e72..ec429a05e3ca 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -24,6 +24,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS; import static android.view.WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY; @@ -74,6 +75,7 @@ import android.view.Display; import android.view.IWindowManager; import android.view.KeyEvent; import android.view.WindowManager; +import android.view.WindowManagerGlobal; import android.view.WindowManagerPolicy; import android.view.animation.Animation; import android.os.PowerManagerInternal; @@ -92,6 +94,8 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { int rotationToReport = 0; + private Runnable mRunnableWhenAddingSplashScreen; + static synchronized WindowManagerService getWindowManagerService(Context context) { if (sWm == null) { // We only want to do this once for the test process as we don't want WM to try to @@ -318,11 +322,36 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { return false; } + /** + * Sets a runnable to run when adding a splash screen which gets executed after the window has + * been added but before returning the surface. + */ + void setRunnableWhenAddingSplashScreen(Runnable r) { + mRunnableWhenAddingSplashScreen = r; + } + @Override public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId) { - return null; + final com.android.server.wm.WindowState window; + final AppWindowToken atoken; + synchronized (sWm.mWindowMap) { + atoken = WindowTestsBase.sDisplayContent.getAppWindowToken(appToken); + window = WindowTestsBase.createWindow(null, TYPE_APPLICATION_STARTING, atoken, + "Starting window"); + atoken.startingWindow = window; + } + if (mRunnableWhenAddingSplashScreen != null) { + mRunnableWhenAddingSplashScreen.run(); + mRunnableWhenAddingSplashScreen = null; + } + return () -> { + synchronized (sWm.mWindowMap) { + atoken.removeChild(window); + atoken.startingWindow = null; + } + }; } @Override @@ -482,7 +511,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { @Override public boolean isScreenOn() { - return false; + return true; } @Override diff --git a/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java index 612919845f92..772bfb49ed4f 100644 --- a/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java @@ -45,20 +45,18 @@ import org.mockito.invocation.InvocationOnMock; @SmallTest @Presubmit @RunWith(AndroidJUnit4.class) -public class UnknownAppVisibilityControllerTest { +public class UnknownAppVisibilityControllerTest extends WindowTestsBase { private WindowManagerService mWm; - private @Mock ActivityManagerInternal mAm; @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); + super.setUp(); final Context context = InstrumentationRegistry.getTargetContext(); - LocalServices.addService(ActivityManagerInternal.class, mAm); doAnswer((InvocationOnMock invocationOnMock) -> { invocationOnMock.getArgumentAt(0, Runnable.class).run(); return null; - }).when(mAm).notifyKeyguardFlagsChanged(any()); + }).when(sMockAm).notifyKeyguardFlagsChanged(any()); mWm = TestWindowManagerPolicy.getWindowManagerService(context); } diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java index 466da942edbc..186884b01d21 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java @@ -20,6 +20,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import android.app.ActivityManager.TaskDescription; import android.content.Context; import android.graphics.Rect; import android.os.Binder; @@ -76,7 +77,8 @@ public class WindowFrameTests { final Rect mInsetBounds = new Rect(); boolean mFullscreenForTest = true; TaskWithBounds(Rect bounds) { - super(0, mStubStack, 0, sWm, null, null, false, 0, false, null); + super(0, mStubStack, 0, sWm, null, null, false, 0, false, false, new TaskDescription(), + null); mBounds = bounds; } @Override diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java index 813d2638ac94..72157b6d1c29 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java @@ -16,14 +16,18 @@ package com.android.server.wm; +import android.app.ActivityManager.TaskDescription; +import android.app.ActivityManagerInternal; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; import android.view.IApplicationToken; import org.junit.Assert; import org.junit.Before; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; -import android.app.ActivityManager; import android.app.ActivityManager.TaskSnapshot; import android.content.Context; import android.os.IBinder; @@ -50,13 +54,17 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static org.mockito.Mockito.mock; +import com.android.server.AttributeCache; +import com.android.server.LocalServices; + /** * Common base class for window manager unit test classes. */ class WindowTestsBase { static WindowManagerService sWm = null; - private final IWindow mIWindow = new TestIWindow(); - private final Session mMockSession = mock(Session.class); + static TestWindowManagerPolicy sPolicy = null; + private final static IWindow sIWindow = new TestIWindow(); + private final static Session sMockSession = mock(Session.class); static int sNextStackId = FIRST_DYNAMIC_STACK_ID; private static int sNextTaskId = 0; @@ -72,19 +80,31 @@ class WindowTestsBase { static WindowState sAppWindow; static WindowState sChildAppWindowAbove; static WindowState sChildAppWindowBelow; + static @Mock ActivityManagerInternal sMockAm; @Before public void setUp() throws Exception { if (sOneTimeSetupDone) { + Mockito.reset(sMockAm); return; } sOneTimeSetupDone = true; + MockitoAnnotations.initMocks(this); final Context context = InstrumentationRegistry.getTargetContext(); + LocalServices.addService(ActivityManagerInternal.class, sMockAm); + AttributeCache.init(context); sWm = TestWindowManagerPolicy.getWindowManagerService(context); + sPolicy = (TestWindowManagerPolicy) sWm.mPolicy; sLayersController = new WindowLayersController(sWm); + sDisplayContent = sWm.mRoot.getDisplayContent(context.getDisplay().getDisplayId()); + if (sDisplayContent != null) { + sDisplayContent.removeImmediately(); + } sDisplayContent = new DisplayContent(context.getDisplay(), sWm, sLayersController, new WallpaperController(sWm)); sWm.mRoot.addChild(sDisplayContent, 0); + sWm.mDisplayEnabled = true; + sWm.mDisplayReady = true; // Set-up some common windows. sWallpaperWindow = createWindow(null, TYPE_WALLPAPER, sDisplayContent, "wallpaperWindow"); @@ -108,7 +128,14 @@ class WindowTestsBase { Assert.assertTrue("Excepted " + first + " to be greater than " + second, first > second); } - private WindowToken createWindowToken(DisplayContent dc, int type) { + /** + * Waits until the main handler for WM has processed all messages. + */ + void waitUntilHandlerIdle() { + sWm.mH.runWithScissors(() -> { }, 0); + } + + private static WindowToken createWindowToken(DisplayContent dc, int type) { if (type < FIRST_APPLICATION_WINDOW || type > LAST_APPLICATION_WINDOW) { return new TestWindowToken(type, dc); } @@ -120,7 +147,7 @@ class WindowTestsBase { return token; } - WindowState createWindow(WindowState parent, int type, String name) { + static WindowState createWindow(WindowState parent, int type, String name) { return (parent == null) ? createWindow(parent, type, sDisplayContent, name) : createWindow(parent, type, parent.mToken, name); @@ -132,16 +159,16 @@ class WindowTestsBase { return createWindow(null, type, token, name); } - WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name) { + static WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name) { final WindowToken token = createWindowToken(dc, type); return createWindow(parent, type, token, name); } - WindowState createWindow(WindowState parent, int type, WindowToken token, String name) { + static WindowState createWindow(WindowState parent, int type, WindowToken token, String name) { final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type); attrs.setTitle(name); - final WindowState w = new WindowState(sWm, mMockSession, mIWindow, token, parent, OP_NONE, + final WindowState w = new WindowState(sWm, sMockSession, sIWindow, token, parent, OP_NONE, 0, attrs, 0, 0); // TODO: Probably better to make this call in the WindowState ctor to avoid errors with // adding it to the token... @@ -150,22 +177,26 @@ class WindowTestsBase { } /** Creates a {@link TaskStack} and adds it to the specified {@link DisplayContent}. */ - TaskStack createTaskStackOnDisplay(DisplayContent dc) { - final int stackId = sNextStackId++; - dc.addStackToDisplay(stackId, true); - return sWm.mStackIdToStack.get(stackId); + static TaskStack createTaskStackOnDisplay(DisplayContent dc) { + return createStackControllerOnDisplay(dc).mContainer; + } + + static StackWindowController createStackControllerOnDisplay(DisplayContent dc) { + final int stackId = ++sNextStackId; + return new StackWindowController(stackId, null, dc.getDisplayId(), + true /* onTop */, new Rect(), sWm); } /**Creates a {@link Task} and adds it to the specified {@link TaskStack}. */ - Task createTaskInStack(TaskStack stack, int userId) { + static Task createTaskInStack(TaskStack stack, int userId) { final Task newTask = new Task(sNextTaskId++, stack, userId, sWm, null, EMPTY, false, 0, - false, null); + false, false, new TaskDescription(), null); stack.addTask(newTask, POSITION_TOP); return newTask; } /* Used so we can gain access to some protected members of the {@link WindowToken} class */ - class TestWindowToken extends WindowToken { + static class TestWindowToken extends WindowToken { TestWindowToken(int type, DisplayContent dc) { this(type, dc, false /* persistOnEmpty */); @@ -185,7 +216,7 @@ class WindowTestsBase { } /** Used so we can gain access to some protected members of the {@link AppWindowToken} class. */ - class TestAppWindowToken extends AppWindowToken { + static class TestAppWindowToken extends AppWindowToken { TestAppWindowToken(DisplayContent dc) { super(sWm, null, false, dc); @@ -213,12 +244,16 @@ class WindowTestsBase { boolean mShouldDeferRemoval = false; boolean mOnDisplayChangedCalled = false; + private boolean mUseLocalIsAnimating = false; + private boolean mIsAnimating = false; TestTask(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds, Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode, - boolean homeTask, TaskWindowContainerController controller) { + boolean supportsPictureInPicture, boolean homeTask, + TaskWindowContainerController controller) { super(taskId, stack, userId, service, bounds, overrideConfig, isOnTopLauncher, - resizeMode, homeTask, controller); + resizeMode, supportsPictureInPicture, homeTask, new TaskDescription(), + controller); } boolean shouldDeferRemoval() { @@ -234,6 +269,16 @@ class WindowTestsBase { super.onDisplayChanged(dc); mOnDisplayChangedCalled = true; } + + @Override + boolean isAnimating() { + return mUseLocalIsAnimating ? mIsAnimating : super.isAnimating(); + } + + void setLocalIsAnimating(boolean isAnimating) { + mUseLocalIsAnimating = true; + mIsAnimating = isAnimating; + } } /** @@ -243,21 +288,33 @@ class WindowTestsBase { class TestTaskWindowContainerController extends TaskWindowContainerController { TestTaskWindowContainerController() { - this(createTaskStackOnDisplay(sDisplayContent).mStackId); + this(createStackControllerOnDisplay(sDisplayContent)); } - TestTaskWindowContainerController(int stackId) { - super(sNextTaskId++, snapshot -> {}, stackId, 0 /* userId */, null /* bounds */, - EMPTY /* overrideConfig*/, RESIZE_MODE_UNRESIZEABLE, false /* homeTask*/, - false /* isOnTopLauncher */, true /* toTop*/, true /* showForAllUsers */); + TestTaskWindowContainerController(StackWindowController stackController) { + super(sNextTaskId++, new TaskWindowContainerListener() { + @Override + public void onSnapshotChanged(TaskSnapshot snapshot) { + + } + + @Override + public void requestResize(Rect bounds, int resizeMode) { + + } + }, stackController, 0 /* userId */, null /* bounds */, + EMPTY /* overrideConfig*/, RESIZE_MODE_UNRESIZEABLE, + false /* supportsPictureInPicture */, false /* homeTask*/, + false /* isOnTopLauncher */, true /* toTop*/, true /* showForAllUsers */, + new TaskDescription(), sWm); } @Override TestTask createTask(int taskId, TaskStack stack, int userId, Rect bounds, - Configuration overrideConfig, int resizeMode, boolean homeTask, - boolean isOnTopLauncher) { + Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture, + boolean homeTask, boolean isOnTopLauncher, TaskDescription taskDescription) { return new TestTask(taskId, stack, userId, mService, bounds, overrideConfig, - isOnTopLauncher, resizeMode, homeTask, this); + isOnTopLauncher, resizeMode, supportsPictureInPicture, homeTask, this); } } @@ -279,6 +336,10 @@ class WindowTestsBase { 0 /* inputDispatchingTimeoutNanos */, sWm); mToken = token; } + + AppWindowToken getAppWindowToken() { + return (AppWindowToken) sDisplayContent.getWindowToken(mToken.asBinder()); + } } class TestIApplicationToken implements IApplicationToken { @@ -295,7 +356,7 @@ class WindowTestsBase { boolean resizeReported; TestWindowState(WindowManager.LayoutParams attrs, WindowToken token) { - super(sWm, mMockSession, mIWindow, token, null, OP_NONE, 0, attrs, 0, 0); + super(sWm, sMockSession, sIWindow, token, null, OP_NONE, 0, attrs, 0, 0); } @Override diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 6dfb48bfbcf9..7a69803c8463 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -106,7 +106,7 @@ public class UsageStatsService extends SystemService implements private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES; private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds. - private static final boolean ENABLE_KERNEL_UPDATES = false; + private static final boolean ENABLE_KERNEL_UPDATES = true; private static final File KERNEL_COUNTER_FILE = new File("/proc/uid_procstat/set"); long mAppIdleScreenThresholdMillis; diff --git a/services/usb/Android.mk b/services/usb/Android.mk index feabf0a970c2..f560e71ebe6a 100644 --- a/services/usb/Android.mk +++ b/services/usb/Android.mk @@ -8,5 +8,7 @@ LOCAL_SRC_FILES += \ $(call all-java-files-under,java) LOCAL_JAVA_LIBRARIES := services.core +LOCAL_STATIC_JAVA_LIBRARIES := android.hardware.usb@1.0-java-static \ +android.hidl.manager@1.0-java-static include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java index 4aff3d54834a..4b8e4c8ab13a 100644 --- a/services/usb/java/com/android/server/usb/UsbPortManager.java +++ b/services/usb/java/com/android/server/usb/UsbPortManager.java @@ -16,36 +16,40 @@ package com.android.server.usb; -import com.android.internal.util.IndentingPrintWriter; -import com.android.server.FgThread; - import android.content.Context; import android.content.Intent; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; import android.hardware.usb.UsbPortStatus; +import android.hardware.usb.V1_0.IUsb; +import android.hardware.usb.V1_0.IUsbCallback; +import android.hardware.usb.V1_0.PortRole; +import android.hardware.usb.V1_0.PortRoleType; +import android.hardware.usb.V1_0.PortStatus; +import android.hardware.usb.V1_0.Status; +import android.hidl.manager.V1_0.IServiceManager; +import android.hidl.manager.V1_0.IServiceNotification; +import android.os.Bundle; import android.os.Handler; +import android.os.HwBinder; import android.os.Message; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.os.UEventObserver; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; import android.os.UserHandle; -import android.system.ErrnoException; -import android.system.Os; -import android.system.OsConstants; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.FgThread; -import libcore.io.IoUtils; +import java.util.ArrayList; +import java.util.NoSuchElementException; /** * Allows trusted components to control the properties of physical USB ports - * via the "/sys/class/dual_role_usb" kernel interface. + * via the IUsb.hal. * <p> * Note: This interface may not be supported on all chipsets since the USB drivers * must be changed to publish this information through the module. At the moment @@ -60,43 +64,6 @@ public class UsbPortManager { private static final int MSG_UPDATE_PORTS = 1; - // UEvent path to watch. - private static final String UEVENT_FILTER = "SUBSYSTEM=dual_role_usb"; - - // SysFS directory that contains USB ports as subdirectories. - private static final String SYSFS_CLASS = "/sys/class/dual_role_usb"; - - // SysFS file that contains a USB port's supported modes. (read-only) - // Contents: "", "ufp", "dfp", or "ufp dfp". - private static final String SYSFS_PORT_SUPPORTED_MODES = "supported_modes"; - - // SysFS file that contains a USB port's current mode. (read-write if configurable) - // Contents: "", "ufp", or "dfp". - private static final String SYSFS_PORT_MODE = "mode"; - - // SysFS file that contains a USB port's current power role. (read-write if configurable) - // Contents: "", "source", or "sink". - private static final String SYSFS_PORT_POWER_ROLE = "power_role"; - - // SysFS file that contains a USB port's current data role. (read-write if configurable) - // Contents: "", "host", or "device". - private static final String SYSFS_PORT_DATA_ROLE = "data_role"; - - // Port modes: upstream facing port or downstream facing port. - private static final String PORT_MODE_DFP = "dfp"; - private static final String PORT_MODE_UFP = "ufp"; - - // Port power roles: source or sink. - private static final String PORT_POWER_ROLE_SOURCE = "source"; - private static final String PORT_POWER_ROLE_SINK = "sink"; - - // Port data roles: host or device. - private static final String PORT_DATA_ROLE_HOST = "host"; - private static final String PORT_DATA_ROLE_DEVICE = "device"; - - private static final String USB_TYPEC_PROP_PREFIX = "sys.usb.typec."; - private static final String USB_TYPEC_STATE = "sys.usb.typec.state"; - // All non-trivial role combinations. private static final int COMBO_SOURCE_HOST = UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_HOST); @@ -110,8 +77,29 @@ public class UsbPortManager { // The system context. private final Context mContext; - // True if we have kernel support. - private final boolean mHaveKernelSupport; + // Proxy object for the usb hal daemon. + // @GuardedBy("mLock") + private IUsb mProxy = null; + + // Callback when the UsbPort status is changed by the kernel. + // Mostly due a command sent by the remote Usb device. + private HALCallback mHALCallback = new HALCallback(null, this); + + // Notification object used to listen to the start of the usb daemon. + private final ServiceNotification mServiceNotification = new ServiceNotification(); + + // Cookie sent for usb hal death notification. + private static final int USB_HAL_DEATH_COOKIE = 1000; + + // Usb hal service name. + private static String sSERVICENAME = "usb_hal"; + + // Used as the key while sending the bundle to Main thread. + private static final String PORT_INFO = "port_info"; + + // This is monitored to prevent updating the protInfo before the system + // is ready. + private boolean mSystemReady; // Mutex for all mutable shared state. private final Object mLock = new Object(); @@ -123,17 +111,37 @@ public class UsbPortManager { private final ArrayMap<String, PortInfo> mPorts = new ArrayMap<String, PortInfo>(); // List of all simulated ports, indexed by id. - private final ArrayMap<String, SimulatedPortInfo> mSimulatedPorts = - new ArrayMap<String, SimulatedPortInfo>(); + private final ArrayMap<String, RawPortInfo> mSimulatedPorts = + new ArrayMap<String, RawPortInfo>(); public UsbPortManager(Context context) { mContext = context; - mHaveKernelSupport = new File(SYSFS_CLASS).exists(); + try { + boolean ret = IServiceManager.getService("manager") + .registerForNotifications("android.hardware.usb@1.0::IUsb", + "", mServiceNotification); + if (!ret) { + logAndPrint(Log.ERROR, null, "Failed to register service start notification"); + } + } catch (RemoteException e) { + logAndPrint(Log.ERROR, null, "Failed to register service start notification"); + Thread.dumpStack(); + return; + } + connectToProxy(null); } public void systemReady() { - mUEventObserver.startObserving(UEVENT_FILTER); - scheduleUpdatePorts(); + if (mProxy != null) { + try { + mProxy.queryPortStatus(); + } catch (RemoteException e) { + logAndPrint(Log.ERROR, null, + "ServiceStart: Failed to query port status"); + Thread.dumpStack(); + } + } + mSystemReady = true; } public UsbPort[] getPorts() { @@ -223,20 +231,14 @@ public class UsbPortManager { + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole) + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole)); - SimulatedPortInfo sim = mSimulatedPorts.get(portId); + RawPortInfo sim = mSimulatedPorts.get(portId); if (sim != null) { // Change simulated state. sim.mCurrentMode = newMode; sim.mCurrentPowerRole = newPowerRole; sim.mCurrentDataRole = newDataRole; - } else if (mHaveKernelSupport) { - // Change actual state. - final File portDir = new File(SYSFS_CLASS, portId); - if (!portDir.exists()) { - logAndPrint(Log.ERROR, pw, "USB port not found: portId=" + portId); - return; - } - + updatePortsLocked(pw, null); + } else if (mProxy != null) { if (currentMode != newMode) { // Changing the mode will have the side-effect of also changing // the power and data roles but it might take some time to apply @@ -244,38 +246,54 @@ public class UsbPortManager { // hardware, we have no way of knowing whether it will work apriori // which is why we would prefer to set the power and data roles // directly instead. - if (!writeFile(portDir, SYSFS_PORT_MODE, - newMode == UsbPort.MODE_DFP ? PORT_MODE_DFP : PORT_MODE_UFP)) { - logAndPrint(Log.ERROR, pw, "Failed to set the USB port mode: " + + logAndPrint(Log.ERROR, pw, "Trying to set the USB port mode: " + "portId=" + portId + ", newMode=" + UsbPort.modeToString(newMode)); + PortRole newRole = new PortRole(); + newRole.type = PortRoleType.MODE; + newRole.role = newMode; + try { + mProxy.switchRole(portId, newRole); + } catch (RemoteException e) { + logAndPrint(Log.ERROR, pw, "Failed to set the USB port mode: " + + "portId=" + portId + + ", newMode=" + UsbPort.modeToString(newRole.role)); + Thread.dumpStack(); return; } } else { // Change power and data role independently as needed. if (currentPowerRole != newPowerRole) { - if (!writeFile(portDir, SYSFS_PORT_POWER_ROLE, - newPowerRole == UsbPort.POWER_ROLE_SOURCE - ? PORT_POWER_ROLE_SOURCE : PORT_POWER_ROLE_SINK)) { + PortRole newRole = new PortRole(); + newRole.type = PortRoleType.POWER_ROLE; + newRole.role = newPowerRole; + try { + mProxy.switchRole(portId, newRole); + } catch (RemoteException e) { logAndPrint(Log.ERROR, pw, "Failed to set the USB port power role: " + "portId=" + portId - + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)); + + ", newPowerRole=" + UsbPort.powerRoleToString(newRole.role)); + Thread.dumpStack(); return; } } if (currentDataRole != newDataRole) { - if (!writeFile(portDir, SYSFS_PORT_DATA_ROLE, - newDataRole == UsbPort.DATA_ROLE_HOST - ? PORT_DATA_ROLE_HOST : PORT_DATA_ROLE_DEVICE)) { + PortRole newRole = new PortRole(); + newRole.type = PortRoleType.DATA_ROLE; + newRole.role = newDataRole; + try { + mProxy.switchRole(portId, newRole); + } catch (RemoteException e) { logAndPrint(Log.ERROR, pw, "Failed to set the USB port data role: " + "portId=" + portId - + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole)); + + ", newDataRole=" + UsbPort.dataRoleToString(newRole.role)); + Thread.dumpStack(); return; } } } } - updatePortsLocked(pw); } } @@ -289,8 +307,8 @@ public class UsbPortManager { pw.println("Adding simulated port: portId=" + portId + ", supportedModes=" + UsbPort.modeToString(supportedModes)); mSimulatedPorts.put(portId, - new SimulatedPortInfo(portId, supportedModes)); - updatePortsLocked(pw); + new RawPortInfo(portId, supportedModes)); + updatePortsLocked(pw, null); } } @@ -298,7 +316,7 @@ public class UsbPortManager { int powerRole, boolean canChangePowerRole, int dataRole, boolean canChangeDataRole, IndentingPrintWriter pw) { synchronized (mLock) { - final SimulatedPortInfo portInfo = mSimulatedPorts.get(portId); + final RawPortInfo portInfo = mSimulatedPorts.get(portId); if (portInfo == null) { pw.println("Cannot connect simulated port which does not exist."); return; @@ -328,13 +346,13 @@ public class UsbPortManager { portInfo.mCanChangePowerRole = canChangePowerRole; portInfo.mCurrentDataRole = dataRole; portInfo.mCanChangeDataRole = canChangeDataRole; - updatePortsLocked(pw); + updatePortsLocked(pw, null); } } public void disconnectSimulatedPort(String portId, IndentingPrintWriter pw) { synchronized (mLock) { - final SimulatedPortInfo portInfo = mSimulatedPorts.get(portId); + final RawPortInfo portInfo = mSimulatedPorts.get(portId); if (portInfo == null) { pw.println("Cannot disconnect simulated port which does not exist."); return; @@ -347,7 +365,7 @@ public class UsbPortManager { portInfo.mCanChangePowerRole = false; portInfo.mCurrentDataRole = 0; portInfo.mCanChangeDataRole = false; - updatePortsLocked(pw); + updatePortsLocked(pw, null); } } @@ -361,7 +379,7 @@ public class UsbPortManager { pw.println("Disconnecting simulated port: portId=" + portId); mSimulatedPorts.removeAt(index); - updatePortsLocked(pw); + updatePortsLocked(pw, null); } } @@ -370,7 +388,7 @@ public class UsbPortManager { pw.println("Removing all simulated ports and ending simulation."); if (!mSimulatedPorts.isEmpty()) { mSimulatedPorts.clear(); - updatePortsLocked(pw); + updatePortsLocked(pw, null); } } } @@ -393,9 +411,108 @@ public class UsbPortManager { } } - private void updatePortsLocked(IndentingPrintWriter pw) { - // Assume all ports are gone unless informed otherwise. - // Kind of pessimistic but simple. + private static class HALCallback extends IUsbCallback.Stub { + public IndentingPrintWriter pw; + public UsbPortManager portManager; + + HALCallback() { + super(); + } + + HALCallback(IndentingPrintWriter pw, UsbPortManager portManager) { + this.pw = pw; + this.portManager = portManager; + } + + public void notifyPortStatusChange(ArrayList<PortStatus> currentPortStatus, int retval) { + if (!portManager.mSystemReady) return; + + if (retval != Status.SUCCESS) { + logAndPrint(Log.ERROR, pw, "port status enquiry failed"); + return; + } + + ArrayList<RawPortInfo> newPortInfo = new ArrayList<RawPortInfo>(); + + for (PortStatus current : currentPortStatus) { + RawPortInfo temp = new RawPortInfo(current.portName, + current.supportedModes, current.currentMode, + current.canChangeMode, current.currentPowerRole, + current.canChangePowerRole, + current.currentDataRole, current.canChangeDataRole); + newPortInfo.add(temp); + logAndPrint(Log.INFO, pw, "ClientCallback: " + current.portName); + } + + Message message = portManager.mHandler.obtainMessage(); + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList(PORT_INFO, newPortInfo); + message.what = MSG_UPDATE_PORTS; + message.setData(bundle); + portManager.mHandler.sendMessage(message); + return; + } + + public void notifyRoleSwitchStatus(String portName, PortRole role, int retval) { + if (retval == Status.SUCCESS) { + logAndPrint(Log.INFO, pw, portName + "role switch successful"); + } else { + logAndPrint(Log.ERROR, pw, portName + "role switch failed"); + } + } + }; + + final class DeathRecipient implements HwBinder.DeathRecipient { + public IndentingPrintWriter pw; + + DeathRecipient(IndentingPrintWriter pw) { + this.pw = pw; + } + + @Override + public void serviceDied(long cookie) { + if (cookie == USB_HAL_DEATH_COOKIE) { + logAndPrint(Log.ERROR, pw, "Usb hal service died cookie: " + cookie); + synchronized (mLock) { + mProxy = null; + } + } + } + } + + final class ServiceNotification extends IServiceNotification.Stub { + @Override + public void onRegistration(String fqName, String name, boolean preexisting) { + logAndPrint(Log.INFO, null, "Usb hal service started " + fqName + " " + name); + connectToProxy(null); + } + } + + private void connectToProxy(IndentingPrintWriter pw) { + synchronized (mLock) { + if (mProxy != null) return; + + try { + mProxy = IUsb.getService(sSERVICENAME); + mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE); + mProxy.setCallback(mHALCallback); + mProxy.queryPortStatus(); + } catch (NoSuchElementException e) { + logAndPrint(Log.ERROR, pw, sSERVICENAME + "not found." + + " Did the service failed to start ?"); + Thread.dumpStack(); + } catch (RemoteException e) { + logAndPrint(Log.ERROR, pw, sSERVICENAME + "connectToProxy: Service not responding"); + Thread.dumpStack(); + } + } + } + + /** + * Simulated ports directly add the new roles to mSimulatedPorts before calling. + * USB hal callback populates and sends the newPortInfo. + */ + private void updatePortsLocked(IndentingPrintWriter pw, ArrayList<RawPortInfo> newPortInfo) { for (int i = mPorts.size(); i-- > 0; ) { mPorts.valueAt(i).mDisposition = PortInfo.DISPOSITION_REMOVED; } @@ -404,34 +521,18 @@ public class UsbPortManager { if (!mSimulatedPorts.isEmpty()) { final int count = mSimulatedPorts.size(); for (int i = 0; i < count; i++) { - final SimulatedPortInfo portInfo = mSimulatedPorts.valueAt(i); + final RawPortInfo portInfo = mSimulatedPorts.valueAt(i); addOrUpdatePortLocked(portInfo.mPortId, portInfo.mSupportedModes, portInfo.mCurrentMode, portInfo.mCanChangeMode, portInfo.mCurrentPowerRole, portInfo.mCanChangePowerRole, portInfo.mCurrentDataRole, portInfo.mCanChangeDataRole, pw); } - } else if (mHaveKernelSupport) { - final File[] portDirs = new File(SYSFS_CLASS).listFiles(); - if (portDirs != null) { - for (File portDir : portDirs) { - if (!portDir.isDirectory()) { - continue; - } - - // Parse the sysfs file contents. - final String portId = portDir.getName(); - final int supportedModes = readSupportedModes(portDir); - final int currentMode = readCurrentMode(portDir); - final boolean canChangeMode = canChangeMode(portDir); - final int currentPowerRole = readCurrentPowerRole(portDir); - final boolean canChangePowerRole = canChangePowerRole(portDir); - final int currentDataRole = readCurrentDataRole(portDir); - final boolean canChangeDataRole = canChangeDataRole(portDir); - addOrUpdatePortLocked(portId, supportedModes, - currentMode, canChangeMode, - currentPowerRole, canChangePowerRole, - currentDataRole, canChangeDataRole, pw); - } + } else { + for (RawPortInfo currentPortInfo : newPortInfo) { + addOrUpdatePortLocked(currentPortInfo.mPortId, currentPortInfo.mSupportedModes, + currentPortInfo.mCurrentMode, currentPortInfo.mCanChangeMode, + currentPortInfo.mCurrentPowerRole, currentPortInfo.mCanChangePowerRole, + currentPortInfo.mCurrentDataRole, currentPortInfo.mCanChangeDataRole, pw); } } @@ -457,20 +558,21 @@ public class UsbPortManager { } } + // Must only be called by updatePortsLocked. private void addOrUpdatePortLocked(String portId, int supportedModes, - int currentMode, boolean canChangeMode, - int currentPowerRole, boolean canChangePowerRole, - int currentDataRole, boolean canChangeDataRole, - IndentingPrintWriter pw) { + int currentMode, boolean canChangeMode, + int currentPowerRole, boolean canChangePowerRole, + int currentDataRole, boolean canChangeDataRole, + IndentingPrintWriter pw) { // Only allow mode switch capability for dual role ports. // Validate that the current mode matches the supported modes we expect. if (supportedModes != UsbPort.MODE_DUAL) { canChangeMode = false; if (currentMode != 0 && currentMode != supportedModes) { logAndPrint(Log.WARN, pw, "Ignoring inconsistent current mode from USB " - + "port driver: supportedModes=" + UsbPort.modeToString(supportedModes) - + ", currentMode=" + UsbPort.modeToString(currentMode)); + + "port driver: supportedModes=" + UsbPort.modeToString(supportedModes) + + ", currentMode=" + UsbPort.modeToString(currentMode)); currentMode = 0; } } @@ -485,8 +587,8 @@ public class UsbPortManager { // Can change both power and data role independently. // Assume all combinations are possible. supportedRoleCombinations |= - COMBO_SOURCE_HOST | COMBO_SOURCE_DEVICE - | COMBO_SINK_HOST | COMBO_SINK_DEVICE; + COMBO_SOURCE_HOST | COMBO_SOURCE_DEVICE + | COMBO_SINK_HOST | COMBO_SINK_DEVICE; } else if (canChangePowerRole) { // Can only change power role. // Assume data role must remain at its current value. @@ -514,24 +616,24 @@ public class UsbPortManager { if (portInfo == null) { portInfo = new PortInfo(portId, supportedModes); portInfo.setStatus(currentMode, canChangeMode, - currentPowerRole, canChangePowerRole, - currentDataRole, canChangeDataRole, - supportedRoleCombinations); + currentPowerRole, canChangePowerRole, + currentDataRole, canChangeDataRole, + supportedRoleCombinations); mPorts.put(portId, portInfo); } else { // Sanity check that ports aren't changing definition out from under us. if (supportedModes != portInfo.mUsbPort.getSupportedModes()) { logAndPrint(Log.WARN, pw, "Ignoring inconsistent list of supported modes from " - + "USB port driver (should be immutable): " - + "previous=" + UsbPort.modeToString( - portInfo.mUsbPort.getSupportedModes()) - + ", current=" + UsbPort.modeToString(supportedModes)); + + "USB port driver (should be immutable): " + + "previous=" + UsbPort.modeToString( + portInfo.mUsbPort.getSupportedModes()) + + ", current=" + UsbPort.modeToString(supportedModes)); } if (portInfo.setStatus(currentMode, canChangeMode, - currentPowerRole, canChangePowerRole, - currentDataRole, canChangeDataRole, - supportedRoleCombinations)) { + currentPowerRole, canChangePowerRole, + currentDataRole, canChangeDataRole, + supportedRoleCombinations)) { portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED; } else { portInfo.mDisposition = PortInfo.DISPOSITION_READY; @@ -572,120 +674,6 @@ public class UsbPortManager { }); } - private void scheduleUpdatePorts() { - if (!mHandler.hasMessages(MSG_UPDATE_PORTS)) { - mHandler.sendEmptyMessage(MSG_UPDATE_PORTS); - } - } - - private static int readSupportedModes(File portDir) { - int modes = 0; - final String contents = readFile(portDir, SYSFS_PORT_SUPPORTED_MODES); - if (contents != null) { - if (contents.contains(PORT_MODE_DFP)) { - modes |= UsbPort.MODE_DFP; - } - if (contents.contains(PORT_MODE_UFP)) { - modes |= UsbPort.MODE_UFP; - } - } - return modes; - } - - private static int readCurrentMode(File portDir) { - final String contents = readFile(portDir, SYSFS_PORT_MODE); - if (contents != null) { - if (contents.equals(PORT_MODE_DFP)) { - return UsbPort.MODE_DFP; - } - if (contents.equals(PORT_MODE_UFP)) { - return UsbPort.MODE_UFP; - } - } - return 0; - } - - private static int readCurrentPowerRole(File portDir) { - final String contents = readFile(portDir, SYSFS_PORT_POWER_ROLE); - if (contents != null) { - if (contents.equals(PORT_POWER_ROLE_SOURCE)) { - return UsbPort.POWER_ROLE_SOURCE; - } - if (contents.equals(PORT_POWER_ROLE_SINK)) { - return UsbPort.POWER_ROLE_SINK; - } - } - return 0; - } - - private static int readCurrentDataRole(File portDir) { - final String contents = readFile(portDir, SYSFS_PORT_DATA_ROLE); - if (contents != null) { - if (contents.equals(PORT_DATA_ROLE_HOST)) { - return UsbPort.DATA_ROLE_HOST; - } - if (contents.equals(PORT_DATA_ROLE_DEVICE)) { - return UsbPort.DATA_ROLE_DEVICE; - } - } - return 0; - } - - private static boolean fileIsRootWritable(String path) { - try { - // If the file is user writable, then it is root writable. - return (Os.stat(path).st_mode & OsConstants.S_IWUSR) != 0; - } catch (ErrnoException e) { - return false; - } - } - - private static boolean canChangeMode(File portDir) { - return fileIsRootWritable(new File(portDir, SYSFS_PORT_MODE).getPath()); - } - - private static boolean canChangePowerRole(File portDir) { - return fileIsRootWritable(new File(portDir, SYSFS_PORT_POWER_ROLE).getPath()); - } - - private static boolean canChangeDataRole(File portDir) { - return fileIsRootWritable(new File(portDir, SYSFS_PORT_DATA_ROLE).getPath()); - } - - private static String readFile(File dir, String filename) { - final File file = new File(dir, filename); - try { - return IoUtils.readFileAsString(file.getAbsolutePath()).trim(); - } catch (IOException ex) { - return null; - } - } - - private static boolean waitForState(String property, String state) { - // wait for the transition to complete. - // give up after 5 seconds. - // 5 seconds is probably too long, but we have seen hardware that takes - // over 3 seconds to change states. - String value = null; - for (int i = 0; i < 100; i++) { - // State transition is done when property is set to the new configuration - value = SystemProperties.get(property); - if (state.equals(value)) return true; - SystemClock.sleep(50); - } - Slog.e(TAG, "waitForState(" + state + ") for " + property + " FAILED: got " + value); - return false; - } - - private static String propertyFromFilename(String filename) { - return USB_TYPEC_PROP_PREFIX + filename; - } - - private static boolean writeFile(File dir, String filename, String contents) { - SystemProperties.set(propertyFromFilename(filename), contents); - return waitForState(USB_TYPEC_STATE, contents); - } - private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) { Slog.println(priority, TAG, msg); if (pw != null) { @@ -698,8 +686,10 @@ public class UsbPortManager { public void handleMessage(Message msg) { switch (msg.what) { case MSG_UPDATE_PORTS: { + Bundle b = msg.getData(); + ArrayList<RawPortInfo> PortInfo = b.getParcelableArrayList(PORT_INFO); synchronized (mLock) { - updatePortsLocked(null); + updatePortsLocked(null, PortInfo); } break; } @@ -707,13 +697,6 @@ public class UsbPortManager { } }; - private final UEventObserver mUEventObserver = new UEventObserver() { - @Override - public void onUEvent(UEvent event) { - scheduleUpdatePorts(); - } - }; - /** * Describes a USB port. */ @@ -764,10 +747,10 @@ public class UsbPortManager { } /** - * Describes a simulated USB port. - * Roughly mirrors the information we would ordinarily get from the kernel. + * Used for storing the raw data from the kernel + * Values of the member variables mocked directly incase of emulation. */ - private static final class SimulatedPortInfo { + private static final class RawPortInfo implements Parcelable { public final String mPortId; public final int mSupportedModes; public int mCurrentMode; @@ -777,9 +760,63 @@ public class UsbPortManager { public int mCurrentDataRole; public boolean mCanChangeDataRole; - public SimulatedPortInfo(String portId, int supportedModes) { + RawPortInfo(String portId, int supportedModes) { mPortId = portId; mSupportedModes = supportedModes; } + + RawPortInfo(String portId, int supportedModes, + int currentMode, boolean canChangeMode, + int currentPowerRole, boolean canChangePowerRole, + int currentDataRole, boolean canChangeDataRole) { + mPortId = portId; + mSupportedModes = supportedModes; + mCurrentMode = currentMode; + mCanChangeMode = canChangeMode; + mCurrentPowerRole = currentPowerRole; + mCanChangePowerRole = canChangePowerRole; + mCurrentDataRole = currentDataRole; + mCanChangeDataRole = canChangeDataRole; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mPortId); + dest.writeInt(mSupportedModes); + dest.writeInt(mCurrentMode); + dest.writeByte((byte) (mCanChangeMode ? 1 : 0)); + dest.writeInt(mCurrentPowerRole); + dest.writeByte((byte) (mCanChangePowerRole ? 1 : 0)); + dest.writeInt(mCurrentDataRole); + dest.writeByte((byte) (mCanChangeDataRole ? 1 : 0)); + } + + public static final Parcelable.Creator<RawPortInfo> CREATOR = + new Parcelable.Creator<RawPortInfo>() { + @Override + public RawPortInfo createFromParcel(Parcel in) { + String id = in.readString(); + int supportedModes = in.readInt(); + int currentMode = in.readInt(); + boolean canChangeMode = in.readByte() != 0; + int currentPowerRole = in.readInt(); + boolean canChangePowerRole = in.readByte() != 0; + int currentDataRole = in.readInt(); + boolean canChangeDataRole = in.readByte() != 0; + return new RawPortInfo(id, supportedModes, currentMode, canChangeMode, + currentPowerRole, canChangePowerRole, + currentDataRole, canChangeDataRole); + } + + @Override + public RawPortInfo[] newArray(int size) { + return new RawPortInfo[size]; + } + }; } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index a3f7c18f54bd..b28627bf3f74 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -4791,6 +4791,20 @@ public class TelephonyManager { } } + /* + * @return true, if the device is currently on a technology (e.g. UMTS or LTE) which can support + * voice and data simultaneously. This can change based on location or network condition. + */ + public boolean isConcurrentVoiceAndDataAllowed() { + try { + ITelephony telephony = getITelephony(); + return (telephony == null ? false : telephony.isConcurrentVoiceAndDataAllowed(mSubId)); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isConcurrentVoiceAndDataAllowed", e); + } + return false; + } + /** @hide */ @SystemApi public boolean handlePinMmi(String dialString) { diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index c0d6768aece0..9a9a0923bc68 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -466,6 +466,12 @@ interface ITelephony { */ int getVoiceMessageCountForSubscriber(int subId); + /** + * Returns true if current state supports both voice and data + * simultaneously. This can change based on location or network condition. + */ + boolean isConcurrentVoiceAndDataAllowed(int subId); + oneway void setVisualVoicemailEnabled(String callingPackage, in PhoneAccountHandle accountHandle, boolean enabled); diff --git a/tools/aapt/AaptConfig.cpp b/tools/aapt/AaptConfig.cpp index d0026a28ba16..0aca45ea8d60 100644 --- a/tools/aapt/AaptConfig.cpp +++ b/tools/aapt/AaptConfig.cpp @@ -267,8 +267,8 @@ void applyVersionForCompatibility(ConfigDescription* config) { uint16_t minSdk = 0; if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE) == ResTable_config::UI_MODE_TYPE_VR_HEADSET - || config->colorimetry & ResTable_config::MASK_WIDE_COLOR_GAMUT - || config->colorimetry & ResTable_config::MASK_HDR) { + || config->colorMode & ResTable_config::MASK_WIDE_COLOR_GAMUT + || config->colorMode & ResTable_config::MASK_HDR) { minSdk = SDK_O; } else if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) { minSdk = SDK_MNC; @@ -451,18 +451,18 @@ bool parseScreenRound(const char* name, ResTable_config* out) { bool parseWideColorGamut(const char* name, ResTable_config* out) { if (strcmp(name, kWildcardName) == 0) { - if (out) out->colorimetry = - (out->colorimetry&~ResTable_config::MASK_WIDE_COLOR_GAMUT) + if (out) out->colorMode = + (out->colorMode&~ResTable_config::MASK_WIDE_COLOR_GAMUT) | ResTable_config::WIDE_COLOR_GAMUT_ANY; return true; } else if (strcmp(name, "widecg") == 0) { - if (out) out->colorimetry = - (out->colorimetry&~ResTable_config::MASK_WIDE_COLOR_GAMUT) + if (out) out->colorMode = + (out->colorMode&~ResTable_config::MASK_WIDE_COLOR_GAMUT) | ResTable_config::WIDE_COLOR_GAMUT_YES; return true; } else if (strcmp(name, "nowidecg") == 0) { - if (out) out->colorimetry = - (out->colorimetry&~ResTable_config::MASK_WIDE_COLOR_GAMUT) + if (out) out->colorMode = + (out->colorMode&~ResTable_config::MASK_WIDE_COLOR_GAMUT) | ResTable_config::WIDE_COLOR_GAMUT_NO; return true; } @@ -471,18 +471,18 @@ bool parseWideColorGamut(const char* name, ResTable_config* out) { bool parseHdr(const char* name, ResTable_config* out) { if (strcmp(name, kWildcardName) == 0) { - if (out) out->colorimetry = - (out->colorimetry&~ResTable_config::MASK_HDR) + if (out) out->colorMode = + (out->colorMode&~ResTable_config::MASK_HDR) | ResTable_config::HDR_ANY; return true; } else if (strcmp(name, "highdr") == 0) { - if (out) out->colorimetry = - (out->colorimetry&~ResTable_config::MASK_HDR) + if (out) out->colorMode = + (out->colorMode&~ResTable_config::MASK_HDR) | ResTable_config::HDR_YES; return true; } else if (strcmp(name, "lowdr") == 0) { - if (out) out->colorimetry = - (out->colorimetry&~ResTable_config::MASK_HDR) + if (out) out->colorMode = + (out->colorMode&~ResTable_config::MASK_HDR) | ResTable_config::HDR_NO; return true; } diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index 653c1b4d6f97..a93ee2e2b71d 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -55,6 +55,7 @@ public: mCompressionMethod(0), mJunkPath(false), mOutputAPKFile(NULL), mManifestPackageNameOverride(NULL), mInstrumentationPackageNameOverride(NULL), mAutoAddOverlay(false), mGenDependencies(false), mNoVersionVectors(false), + mNoVersionTransitions(false), mCrunchedOutputDir(NULL), mProguardFile(NULL), mMainDexProguardFile(NULL), mAndroidManifestFile(NULL), mPublicOutputFile(NULL), mRClassDir(NULL), mResourceIntermediatesDir(NULL), mManifestMinSdkVersion(NULL), @@ -219,6 +220,8 @@ public: void setBuildAppAsSharedLibrary(bool val) { mBuildAppAsSharedLibrary = val; } void setNoVersionVectors(bool val) { mNoVersionVectors = val; } bool getNoVersionVectors() const { return mNoVersionVectors; } + void setNoVersionTransitions(bool val) { mNoVersionTransitions = val; } + bool getNoVersionTransitions() const { return mNoVersionTransitions; } /* * Set and get the file specification. @@ -299,6 +302,7 @@ private: bool mAutoAddOverlay; bool mGenDependencies; bool mNoVersionVectors; + bool mNoVersionTransitions; const char* mCrunchedOutputDir; const char* mProguardFile; const char* mMainDexProguardFile; diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index 984d98e30f29..417b7ae087e1 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -223,6 +223,8 @@ void usage(void) " localization\n" " --no-version-vectors\n" " Do not automatically generate versioned copies of vector XML resources.\n" + " --no-version-transitions\n" + " Do not automatically generate versioned copies of transition XML resources.\n" " --private-symbols\n" " Java package name to use when generating R.java for private resources.\n", gDefaultIgnoreAssets); @@ -704,6 +706,8 @@ int main(int argc, char* const argv[]) bundle.setPseudolocalize(PSEUDO_ACCENTED | PSEUDO_BIDI); } else if (strcmp(cp, "-no-version-vectors") == 0) { bundle.setNoVersionVectors(true); + } else if (strcmp(cp, "-no-version-transitions") == 0) { + bundle.setNoVersionTransitions(true); } else if (strcmp(cp, "-private-symbols") == 0) { argc--; argv++; diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index 045d68c3bbae..cf5badc82117 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -1223,6 +1223,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil sp<ResourceTypeSet> colors; sp<ResourceTypeSet> menus; sp<ResourceTypeSet> mipmaps; + sp<ResourceTypeSet> fonts; ASSIGN_IT(drawable); ASSIGN_IT(layout); @@ -1235,6 +1236,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil ASSIGN_IT(color); ASSIGN_IT(menu); ASSIGN_IT(mipmap); + ASSIGN_IT(font); assets->setResources(resources); // now go through any resource overlays and collect their files @@ -1257,6 +1259,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil !applyFileOverlay(bundle, assets, &raws, "raw") || !applyFileOverlay(bundle, assets, &colors, "color") || !applyFileOverlay(bundle, assets, &menus, "menu") || + !applyFileOverlay(bundle, assets, &fonts, "font") || !applyFileOverlay(bundle, assets, &mipmaps, "mipmap")) { return UNKNOWN_ERROR; } @@ -1291,6 +1294,13 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil } } + if (fonts != NULL) { + err = makeFileResources(bundle, assets, &table, fonts, "font"); + if (err != NO_ERROR) { + hasErrors = true; + } + } + if (layouts != NULL) { err = makeFileResources(bundle, assets, &table, layouts, "layout"); if (err != NO_ERROR) { @@ -1549,6 +1559,26 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil err = NO_ERROR; } + if (fonts != NULL) { + ResourceDirIterator it(fonts, String8("font")); + while ((err=it.next()) == NO_ERROR) { + // fonts can be resources other than xml. + if (it.getFile()->getPath().getPathExtension() == ".xml") { + String8 src = it.getFile()->getPrintableSource(); + err = compileXmlFile(bundle, assets, String16(it.getBaseName()), + it.getFile(), &table, xmlFlags); + if (err != NO_ERROR) { + hasErrors = true; + } + } + } + + if (err < NO_ERROR) { + hasErrors = true; + } + err = NO_ERROR; + } + // Now compile any generated resources. std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue(); while (!workQueue.empty()) { diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index 661409e3d4bc..63498f7cef08 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -4730,6 +4730,32 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle) { return NO_ERROR; } +const String16 kTransitionElements[] = { + String16("fade"), + String16("changeBounds"), + String16("slide"), + String16("explode"), + String16("changeImageTransform"), + String16("changeTransform"), + String16("changeClipBounds"), + String16("autoTransition"), + String16("recolor"), + String16("changeScroll"), + String16("transitionSet"), + String16("transition"), + String16("transitionManager"), +}; + +static bool IsTransitionElement(const String16& name) { + for (int i = 0, size = sizeof(kTransitionElements) / sizeof(kTransitionElements[0]); + i < size; ++i) { + if (name == kTransitionElements[i]) { + return true; + } + } + return false; +} + status_t ResourceTable::modifyForCompat(const Bundle* bundle, const String16& resourceName, const sp<AaptFile>& target, @@ -4766,6 +4792,11 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, continue; } + if (bundle->getNoVersionTransitions() && (IsTransitionElement(node->getElementName()))) { + // We were told not to version transition tags, so skip the children here. + continue; + } + const Vector<XMLNode::attribute_entry>& attrs = node->getAttributes(); for (size_t i = 0; i < attrs.size(); i++) { const XMLNode::attribute_entry& attr = attrs[i]; diff --git a/tools/aapt/tests/AaptConfig_test.cpp b/tools/aapt/tests/AaptConfig_test.cpp index 23f61e9e4b70..4f22fa581a88 100644 --- a/tools/aapt/tests/AaptConfig_test.cpp +++ b/tools/aapt/tests/AaptConfig_test.cpp @@ -103,13 +103,13 @@ TEST(AaptConfigTest, WideColorGamutQualifier) { ConfigDescription config; EXPECT_TRUE(TestParse("widecg", &config)); EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_YES, - config.colorimetry & android::ResTable_config::MASK_WIDE_COLOR_GAMUT); + config.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT); EXPECT_EQ(SDK_O, config.sdkVersion); EXPECT_EQ(String8("widecg-v26"), config.toString()); EXPECT_TRUE(TestParse("nowidecg", &config)); EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_NO, - config.colorimetry & android::ResTable_config::MASK_WIDE_COLOR_GAMUT); + config.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT); EXPECT_EQ(SDK_O, config.sdkVersion); EXPECT_EQ(String8("nowidecg-v26"), config.toString()); } @@ -118,13 +118,13 @@ TEST(AaptConfigTest, HdrQualifier) { ConfigDescription config; EXPECT_TRUE(TestParse("highdr", &config)); EXPECT_EQ(android::ResTable_config::HDR_YES, - config.colorimetry & android::ResTable_config::MASK_HDR); + config.colorMode & android::ResTable_config::MASK_HDR); EXPECT_EQ(SDK_O, config.sdkVersion); EXPECT_EQ(String8("highdr-v26"), config.toString()); EXPECT_TRUE(TestParse("lowdr", &config)); EXPECT_EQ(android::ResTable_config::HDR_NO, - config.colorimetry & android::ResTable_config::MASK_HDR); + config.colorMode & android::ResTable_config::MASK_HDR); EXPECT_EQ(SDK_O, config.sdkVersion); EXPECT_EQ(String8("lowdr-v26"), config.toString()); }
\ No newline at end of file diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp index 5bea3ad1bbad..46098cbc3aa4 100644 --- a/tools/aapt2/ConfigDescription.cpp +++ b/tools/aapt2/ConfigDescription.cpp @@ -209,20 +209,20 @@ static bool parseScreenRound(const char* name, ResTable_config* out) { static bool parseWideColorGamut(const char* name, ResTable_config* out) { if (strcmp(name, kWildcardName) == 0) { if (out) - out->colorimetry = - (out->colorimetry & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) | + out->colorMode = + (out->colorMode & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) | ResTable_config::WIDE_COLOR_GAMUT_ANY; return true; } else if (strcmp(name, "widecg") == 0) { if (out) - out->colorimetry = - (out->colorimetry & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) | + out->colorMode = + (out->colorMode & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) | ResTable_config::WIDE_COLOR_GAMUT_YES; return true; } else if (strcmp(name, "nowidecg") == 0) { if (out) - out->colorimetry = - (out->colorimetry & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) | + out->colorMode = + (out->colorMode & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) | ResTable_config::WIDE_COLOR_GAMUT_NO; return true; } @@ -232,20 +232,20 @@ static bool parseWideColorGamut(const char* name, ResTable_config* out) { static bool parseHdr(const char* name, ResTable_config* out) { if (strcmp(name, kWildcardName) == 0) { if (out) - out->colorimetry = - (out->colorimetry & ~ResTable_config::MASK_HDR) | + out->colorMode = + (out->colorMode & ~ResTable_config::MASK_HDR) | ResTable_config::HDR_ANY; return true; } else if (strcmp(name, "highdr") == 0) { if (out) - out->colorimetry = - (out->colorimetry & ~ResTable_config::MASK_HDR) | + out->colorMode = + (out->colorMode & ~ResTable_config::MASK_HDR) | ResTable_config::HDR_YES; return true; } else if (strcmp(name, "lowdr") == 0) { if (out) - out->colorimetry = - (out->colorimetry & ~ResTable_config::MASK_HDR) | + out->colorMode = + (out->colorMode & ~ResTable_config::MASK_HDR) | ResTable_config::HDR_NO; return true; } @@ -840,8 +840,8 @@ void ConfigDescription::ApplyVersionForCompatibility( uint16_t min_sdk = 0; if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE) == ResTable_config::UI_MODE_TYPE_VR_HEADSET || - config->colorimetry & ResTable_config::MASK_WIDE_COLOR_GAMUT || - config->colorimetry & ResTable_config::MASK_HDR) { + config->colorMode & ResTable_config::MASK_WIDE_COLOR_GAMUT || + config->colorMode & ResTable_config::MASK_HDR) { min_sdk = SDK_O; } else if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) { min_sdk = SDK_MARSHMALLOW; @@ -912,11 +912,11 @@ bool ConfigDescription::HasHigherPrecedenceThan( if ((screenLayout2 | o.screenLayout2) & MASK_SCREENROUND) { return !(o.screenLayout2 & MASK_SCREENROUND); } - if ((colorimetry | o.colorimetry) & MASK_HDR) { - return !(o.colorimetry & MASK_HDR); + if ((colorMode | o.colorMode) & MASK_HDR) { + return !(o.colorMode & MASK_HDR); } - if ((colorimetry | o.colorimetry) & MASK_WIDE_COLOR_GAMUT) { - return !(o.colorimetry & MASK_WIDE_COLOR_GAMUT); + if ((colorMode | o.colorMode) & MASK_WIDE_COLOR_GAMUT) { + return !(o.colorMode & MASK_WIDE_COLOR_GAMUT); } if (orientation || o.orientation) return (!o.orientation); if ((uiMode | o.uiMode) & MASK_UI_MODE_TYPE) { @@ -964,9 +964,9 @@ bool ConfigDescription::ConflictsWith(const ConfigDescription& o) const { !pred(uiMode & MASK_UI_MODE_NIGHT, o.uiMode & MASK_UI_MODE_NIGHT) || !pred(screenLayout2 & MASK_SCREENROUND, o.screenLayout2 & MASK_SCREENROUND) || - !pred(colorimetry & MASK_HDR, o.colorimetry & MASK_HDR) || - !pred(colorimetry & MASK_WIDE_COLOR_GAMUT, - o.colorimetry & MASK_WIDE_COLOR_GAMUT) || + !pred(colorMode & MASK_HDR, o.colorMode & MASK_HDR) || + !pred(colorMode & MASK_WIDE_COLOR_GAMUT, + o.colorMode & MASK_WIDE_COLOR_GAMUT) || !pred(orientation, o.orientation) || !pred(touchscreen, o.touchscreen) || !pred(inputFlags & MASK_KEYSHIDDEN, o.inputFlags & MASK_KEYSHIDDEN) || diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp index b88838ae26f5..14a565624e01 100644 --- a/tools/aapt2/ConfigDescription_test.cpp +++ b/tools/aapt2/ConfigDescription_test.cpp @@ -106,13 +106,13 @@ TEST(ConfigDescriptionTest, TestWideColorGamutQualifier) { ConfigDescription config; EXPECT_TRUE(TestParse("widecg", &config)); EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_YES, - config.colorimetry & android::ResTable_config::MASK_WIDE_COLOR_GAMUT); + config.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT); EXPECT_EQ(SDK_O, config.sdkVersion); EXPECT_EQ(std::string("widecg-v26"), config.toString().string()); EXPECT_TRUE(TestParse("nowidecg", &config)); EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_NO, - config.colorimetry & android::ResTable_config::MASK_WIDE_COLOR_GAMUT); + config.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT); EXPECT_EQ(SDK_O, config.sdkVersion); EXPECT_EQ(std::string("nowidecg-v26"), config.toString().string()); } @@ -121,13 +121,13 @@ TEST(ConfigDescriptionTest, TestHdrQualifier) { ConfigDescription config; EXPECT_TRUE(TestParse("highdr", &config)); EXPECT_EQ(android::ResTable_config::HDR_YES, - config.colorimetry & android::ResTable_config::MASK_HDR); + config.colorMode & android::ResTable_config::MASK_HDR); EXPECT_EQ(SDK_O, config.sdkVersion); EXPECT_EQ(std::string("highdr-v26"), config.toString().string()); EXPECT_TRUE(TestParse("lowdr", &config)); EXPECT_EQ(android::ResTable_config::HDR_NO, - config.colorimetry & android::ResTable_config::MASK_HDR); + config.colorMode & android::ResTable_config::MASK_HDR); EXPECT_EQ(SDK_O, config.sdkVersion); EXPECT_EQ(std::string("lowdr-v26"), config.toString().string()); } diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index 74d40194e955..15d7e2e2241b 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -25,7 +25,7 @@ namespace aapt { static const char* sMajorVersion = "2"; // Update minor version whenever a feature or flag is added. -static const char* sMinorVersion = "4"; +static const char* sMinorVersion = "5"; int PrintVersion() { std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "." diff --git a/tools/aapt2/integration-tests/AppOne/Android.mk b/tools/aapt2/integration-tests/AppOne/Android.mk index bc40a6269382..a6f32d4b9fdc 100644 --- a/tools/aapt2/integration-tests/AppOne/Android.mk +++ b/tools/aapt2/integration-tests/AppOne/Android.mk @@ -24,5 +24,5 @@ LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_STATIC_ANDROID_LIBRARIES := \ AaptTestStaticLibOne \ AaptTestStaticLibTwo -LOCAL_AAPT_FLAGS := --no-version-vectors +LOCAL_AAPT_FLAGS := --no-version-vectors --no-version-transitions include $(BUILD_PACKAGE) diff --git a/tools/aapt2/integration-tests/AppOne/res/transition/transition_set.xml b/tools/aapt2/integration-tests/AppOne/res/transition/transition_set.xml new file mode 100644 index 000000000000..e10e6c2f53da --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/transition/transition_set.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" + android:transitionOrdering="sequential"> + <fade android:fadingMode="fade_out" /> + <changeBounds /> + <fade android:fadingMode="fade_in" /> +</transitionSet> diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index c3ce0760f554..f7e0f8f55fd4 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -80,6 +80,7 @@ struct LinkOptions { // Optimizations/features. bool no_auto_version = false; bool no_version_vectors = false; + bool no_version_transitions = false; bool no_resource_deduping = false; bool no_xml_namespaces = false; bool do_not_compress_anything = false; @@ -250,6 +251,7 @@ static std::unique_ptr<xml::XmlResource> LoadXml(const std::string& path, struct ResourceFileFlattenerOptions { bool no_auto_version = false; bool no_version_vectors = false; + bool no_version_transitions = false; bool no_xml_namespaces = false; bool keep_raw_values = false; bool do_not_compress_anything = false; @@ -306,6 +308,23 @@ uint32_t ResourceFileFlattener::GetCompressionFlags(const StringPiece& str) { return ArchiveEntry::kCompress; } +static bool IsTransitionElement(const std::string& name) { + return + name == "fade" || + name == "changeBounds" || + name == "slide" || + name == "explode" || + name == "changeImageTransform" || + name == "changeTransform" || + name == "changeClipBounds" || + name == "autoTransition" || + name == "recolor" || + name == "changeScroll" || + name == "transitionSet" || + name == "transition" || + name == "transitionManager"; +} + bool ResourceFileFlattener::LinkAndVersionXmlFile( ResourceTable* table, FileOperation* file_op, std::queue<FileOperation>* out_file_op_queue) { @@ -345,6 +364,17 @@ bool ResourceFileFlattener::LinkAndVersionXmlFile( } } } + if (options_.no_version_transitions) { + // Skip this if it is a transition resource. + xml::Element* el = xml::FindRootElement(doc); + if (el && el->namespace_uri.empty()) { + if (IsTransitionElement(el->name)) { + // We are NOT going to version this file. + file_op->skip_version = true; + return true; + } + } + } const ConfigDescription& config = file_op->config; @@ -1384,6 +1414,7 @@ class LinkCommand { options_.extensions_to_not_compress; file_flattener_options.no_auto_version = options_.no_auto_version; file_flattener_options.no_version_vectors = options_.no_version_vectors; + file_flattener_options.no_version_transitions = options_.no_version_transitions; file_flattener_options.no_xml_namespaces = options_.no_xml_namespaces; file_flattener_options.update_proguard_spec = static_cast<bool>(options_.generate_proguard_rules_path); @@ -1863,6 +1894,11 @@ int Link(const std::vector<StringPiece>& args) { "Use this only\n" "when building with vector drawable support library", &options.no_version_vectors) + .OptionalSwitch("--no-version-transitions", + "Disables automatic versioning of transition resources. " + "Use this only\n" + "when building with transition support library", + &options.no_version_transitions) .OptionalSwitch("--no-resource-deduping", "Disables automatic deduping of resources with\n" "identical values across compatible configurations.", @@ -2104,6 +2140,7 @@ int Link(const std::vector<StringPiece>& args) { if (options.static_lib) { options.no_auto_version = true; options.no_version_vectors = true; + options.no_version_transitions = true; } LinkCommand cmd(&context, options); diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md index 800103307e2b..e2a752e488ea 100644 --- a/tools/aapt2/readme.md +++ b/tools/aapt2/readme.md @@ -1,5 +1,10 @@ # Android Asset Packaging Tool 2.0 (AAPT2) release notes +## Version 2.5 +### `aapt2 link ...` +- Transition XML versioning: Adds a new flag `--no-version-transitions` to disable automatic + versioning of Transition XML resources. + ## Version 2.4 ### `aapt2 link ...` - Supports `<meta-data>` tags in `<manifest>`. diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java index d0c959972b13..147ed99ee047 100644 --- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java @@ -249,14 +249,17 @@ public class FontFamily_Delegate { // ---- delegate methods ---- @LayoutlibDelegate /*package*/ static boolean addFont(FontFamily thisFontFamily, String path, int ttcIndex) { - final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mNativePtr); + if (thisFontFamily.mBuilderPtr == 0) { + throw new IllegalStateException("Unable to call addFont after freezing."); + } + final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mBuilderPtr); return delegate != null && delegate.addFont(path, ttcIndex); } // ---- native methods ---- @LayoutlibDelegate - /*package*/ static long nCreateFamily(String lang, int variant) { + /*package*/ static long nInitBuilder(String lang, int variant) { // TODO: support lang. This is required for japanese locale. FontFamily_Delegate delegate = new FontFamily_Delegate(); // variant can be 0, 1 or 2. @@ -271,6 +274,11 @@ public class FontFamily_Delegate { } @LayoutlibDelegate + /*package*/ static long nCreateFamily(long builderPtr) { + return builderPtr; + } + + @LayoutlibDelegate /*package*/ static void nUnrefFamily(long nativePtr) { // Removing the java reference for the object doesn't mean that it's freed for garbage // collection. Typeface_Delegate may still hold a reference for it. @@ -278,22 +286,22 @@ public class FontFamily_Delegate { } @LayoutlibDelegate - /*package*/ static boolean nAddFont(long nativeFamily, ByteBuffer font, int ttcIndex) { + /*package*/ static boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex) { assert false : "The only client of this method has been overriden."; return false; } @LayoutlibDelegate - /*package*/ static boolean nAddFontWeightStyle(long nativeFamily, ByteBuffer font, + /*package*/ static boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font, int ttcIndex, List<FontConfig.Axis> listOfAxis, int weight, boolean isItalic) { assert false : "The only client of this method has been overriden."; return false; } - static boolean addFont(long nativeFamily, final String path, final int weight, + static boolean addFont(long builderPtr, final String path, final int weight, final boolean isItalic) { - final FontFamily_Delegate delegate = getDelegate(nativeFamily); + final FontFamily_Delegate delegate = getDelegate(builderPtr); if (delegate != null) { if (sFontLocation == null) { delegate.mPostInitRunnables.add(() -> delegate.addFont(path, weight, isItalic)); @@ -305,8 +313,8 @@ public class FontFamily_Delegate { } @LayoutlibDelegate - /*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) { - FontFamily_Delegate ffd = sManager.getDelegate(nativeFamily); + /*package*/ static boolean nAddFontFromAsset(long builderPtr, AssetManager mgr, String path) { + FontFamily_Delegate ffd = sManager.getDelegate(builderPtr); if (ffd == null) { return false; } diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java index 6e337d50a31f..f6c463f1b578 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java @@ -213,9 +213,10 @@ public final class Typeface_Delegate { Map<String, ByteBuffer> bufferForPath) { FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant()); for (FontConfig.Font font : family.getFonts()) { - FontFamily_Delegate.addFont(fontFamily.mNativePtr, font.getFontName(), + FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, font.getFontName(), font.getWeight(), font.isItalic()); } + fontFamily.freeze(); return fontFamily; } diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index 5ed5460f8c4f..56898f185d15 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -340,12 +340,6 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public Rect getBoundsForNewConfiguration(int stackId) throws RemoteException { - // TODO Auto-generated method stub - return null; - } - - @Override public void setScreenCaptureDisabled(int userId, boolean disabled) { // TODO Auto-generated method stub } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index 7ba86fdc5c30..741eb27558ed 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -339,7 +339,8 @@ public final class CreateInfo implements ICreateInfo { */ private final static String[] PROMOTED_FIELDS = new String[] { "android.graphics.drawable.VectorDrawable#mVectorState", - "android.view.Choreographer#mLastFrameTimeNanos" + "android.view.Choreographer#mLastFrameTimeNanos", + "android.graphics.FontFamily#mBuilderPtr" }; /** diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java index 65a49ea6cecc..98fd0f3bde15 100644 --- a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java +++ b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java @@ -21,11 +21,17 @@ import android.net.wifi.hotspot2.pps.Credential; import android.net.wifi.hotspot2.pps.HomeSP; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import java.io.IOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.xml.sax.SAXException; @@ -131,16 +137,34 @@ public final class PPSMOParser { private static final String NODE_FQDN = "FQDN"; private static final String NODE_FRIENDLY_NAME = "FriendlyName"; private static final String NODE_ROAMING_CONSORTIUM_OI = "RoamingConsortiumOI"; + private static final String NODE_NETWORK_ID = "NetworkID"; + private static final String NODE_SSID = "SSID"; + private static final String NODE_HESSID = "HESSID"; + private static final String NODE_ICON_URL = "IconURL"; + private static final String NODE_HOME_OI_LIST = "HomeOIList"; + private static final String NODE_HOME_OI = "HomeOI"; + private static final String NODE_HOME_OI_REQUIRED = "HomeOIRequired"; + private static final String NODE_OTHER_HOME_PARTNERS = "OtherHomePartners"; /** * Fields under Credential subtree. */ private static final String NODE_CREDENTIAL = "Credential"; + private static final String NODE_CREATION_DATE = "CreationDate"; + private static final String NODE_EXPIRATION_DATE = "ExpirationDate"; private static final String NODE_USERNAME_PASSWORD = "UsernamePassword"; private static final String NODE_USERNAME = "Username"; private static final String NODE_PASSWORD = "Password"; + private static final String NODE_MACHINE_MANAGED = "MachineManaged"; + private static final String NODE_SOFT_TOKEN_APP = "SoftTokenApp"; + private static final String NODE_ABLE_TO_SHARE = "AbleToShare"; private static final String NODE_EAP_METHOD = "EAPMethod"; private static final String NODE_EAP_TYPE = "EAPType"; + private static final String NODE_VENDOR_ID = "VendorId"; + private static final String NODE_VENDOR_TYPE = "VendorType"; + private static final String NODE_INNER_EAP_TYPE = "InnerEAPType"; + private static final String NODE_INNER_VENDOR_ID = "InnerVendorID"; + private static final String NODE_INNER_VENDOR_TYPE = "InnerVendorType"; private static final String NODE_INNER_METHOD = "InnerMethod"; private static final String NODE_DIGITAL_CERTIFICATE = "DigitalCertificate"; private static final String NODE_CERTIFICATE_TYPE = "CertificateType"; @@ -148,6 +172,7 @@ public final class PPSMOParser { private static final String NODE_REALM = "Realm"; private static final String NODE_SIM = "SIM"; private static final String NODE_SIM_IMSI = "IMSI"; + private static final String NODE_CHECK_AAA_SERVER_CERT_STATUS = "CheckAAAServerCertStatus"; /** * URN (Unique Resource Name) for PerProviderSubscription Management Object Tree. @@ -558,6 +583,20 @@ public final class PPSMOParser { homeSp.roamingConsortiumOIs = parseRoamingConsortiumOI(getPpsNodeValue(child)); break; + case NODE_ICON_URL: + homeSp.iconUrl = getPpsNodeValue(child); + break; + case NODE_NETWORK_ID: + homeSp.homeNetworkIds = parseNetworkIds(child); + break; + case NODE_HOME_OI_LIST: + Pair<List<Long>, List<Long>> homeOIs = parseHomeOIList(child); + homeSp.matchAllOIs = convertFromLongList(homeOIs.first); + homeSp.matchAnyOIs = convertFromLongList(homeOIs.second); + break; + case NODE_OTHER_HOME_PARTNERS: + homeSp.otherHomePartners = parseOtherHomePartners(child); + break; default: throw new ParsingException("Unknown node under HomeSP: " + child.getName()); } @@ -587,6 +626,192 @@ public final class PPSMOParser { } /** + * Parse configurations under PerProviderSubscription/HomeSP/NetworkID subtree. + * + * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP/NetworkID + * subtree + * @return HashMap<String, Long> representing list of <SSID, HESSID> pair. + * @throws ParsingException + */ + static private Map<String, Long> parseNetworkIds(PPSNode node) + throws ParsingException { + if (node.isLeaf()) { + throw new ParsingException("Leaf node not expected for NetworkID"); + } + + Map<String, Long> networkIds = new HashMap<>(); + for (PPSNode child : node.getChildren()) { + Pair<String, Long> networkId = parseNetworkIdInstance(child); + networkIds.put(networkId.first, networkId.second); + } + return networkIds; + } + + /** + * Parse configurations under PerProviderSubscription/HomeSP/NetworkID/<X+> subtree. + * The instance name (<X+>) is irrelevant and must be unique for each instance, which + * is verified when the PPS tree is constructed {@link #buildPpsNode}. + * + * @param node PPSNode representing the root of the + * PerProviderSubscription/HomeSP/NetworkID/<X+> subtree + * @return Pair<String, Long> representing <SSID, HESSID> pair. + * @throws ParsingException + */ + static private Pair<String, Long> parseNetworkIdInstance(PPSNode node) + throws ParsingException { + if (node.isLeaf()) { + throw new ParsingException("Leaf node not expected for NetworkID instance"); + } + + String ssid = null; + Long hessid = null; + for (PPSNode child : node.getChildren()) { + switch (child.getName()) { + case NODE_SSID: + ssid = getPpsNodeValue(child); + break; + case NODE_HESSID: + try { + hessid = Long.parseLong(getPpsNodeValue(child), 16); + } catch (NumberFormatException e) { + throw new ParsingException("Invalid HESSID: " + getPpsNodeValue(child)); + } + break; + default: + throw new ParsingException("Unknown node under NetworkID instance: " + + child.getName()); + } + } + if (ssid == null) + throw new ParsingException("NetworkID instance missing SSID"); + + return new Pair<String, Long>(ssid, hessid); + } + + /** + * Parse configurations under PerProviderSubscription/HomeSP/HomeOIList subtree. + * + * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP/HomeOIList + * subtree + * @return Pair<List<Long>, List<Long>> containing both MatchAllOIs and MatchAnyOIs list. + * @throws ParsingException + */ + private static Pair<List<Long>, List<Long>> parseHomeOIList(PPSNode node) + throws ParsingException { + if (node.isLeaf()) { + throw new ParsingException("Leaf node not expected for HomeOIList"); + } + + List<Long> matchAllOIs = new ArrayList<Long>(); + List<Long> matchAnyOIs = new ArrayList<Long>(); + for (PPSNode child : node.getChildren()) { + Pair<Long, Boolean> homeOI = parseHomeOIInstance(child); + if (homeOI.second.booleanValue()) { + matchAllOIs.add(homeOI.first); + } else { + matchAnyOIs.add(homeOI.first); + } + } + return new Pair<List<Long>, List<Long>>(matchAllOIs, matchAnyOIs); + } + + /** + * Parse configurations under PerProviderSubscription/HomeSP/HomeOIList/<X+> subtree. + * The instance name (<X+>) is irrelevant and must be unique for each instance, which + * is verified when the PPS tree is constructed {@link #buildPpsNode}. + * + * @param node PPSNode representing the root of the + * PerProviderSubscription/HomeSP/HomeOIList/<X+> subtree + * @return Pair<Long, Boolean> containing a HomeOI and a HomeOIRequired flag + * @throws ParsingException + */ + private static Pair<Long, Boolean> parseHomeOIInstance(PPSNode node) throws ParsingException { + if (node.isLeaf()) { + throw new ParsingException("Leaf node not expected for HomeOI instance"); + } + + Long oi = null; + Boolean required = null; + for (PPSNode child : node.getChildren()) { + switch (child.getName()) { + case NODE_HOME_OI: + try { + oi = Long.valueOf(getPpsNodeValue(child), 16); + } catch (NumberFormatException e) { + throw new ParsingException("Invalid HomeOI: " + getPpsNodeValue(child)); + } + break; + case NODE_HOME_OI_REQUIRED: + required = Boolean.valueOf(getPpsNodeValue(child)); + break; + default: + throw new ParsingException("Unknown node under NetworkID instance: " + + child.getName()); + } + } + if (oi == null) { + throw new ParsingException("HomeOI instance missing OI field"); + } + if (required == null) { + throw new ParsingException("HomeOI instance missing required field"); + } + return new Pair<Long, Boolean>(oi, required); + } + + /** + * Parse configurations under PerProviderSubscription/HomeSP/OtherHomePartners subtree. + * This contains a list of FQDN (Fully Qualified Domain Name) that are considered + * home partners. + * + * @param node PPSNode representing the root of the + * PerProviderSubscription/HomeSP/OtherHomePartners subtree + * @return String[] list of partner's FQDN + * @throws ParsingException + */ + private static String[] parseOtherHomePartners(PPSNode node) throws ParsingException { + if (node.isLeaf()) { + throw new ParsingException("Leaf node not expected for OtherHomePartners"); + } + List<String> otherHomePartners = new ArrayList<String>(); + for (PPSNode child : node.getChildren()) { + String fqdn = parseOtherHomePartnerInstance(child); + otherHomePartners.add(fqdn); + } + return otherHomePartners.toArray(new String[otherHomePartners.size()]); + } + + /** + * Parse configurations under PerProviderSubscription/HomeSP/OtherHomePartners/<X+> subtree. + * The instance name (<X+>) is irrelevant and must be unique for each instance, which + * is verified when the PPS tree is constructed {@link #buildPpsNode}. + * + * @param node PPSNode representing the root of the + * PerProviderSubscription/HomeSP/OtherHomePartners/<X+> subtree + * @return String FQDN of the partner + * @throws ParsingException + */ + private static String parseOtherHomePartnerInstance(PPSNode node) throws ParsingException { + if (node.isLeaf()) { + throw new ParsingException("Leaf node not expected for OtherHomePartner instance"); + } + String fqdn = null; + for (PPSNode child : node.getChildren()) { + switch (child.getName()) { + case NODE_FQDN: + fqdn = getPpsNodeValue(child); + break; + default: + throw new ParsingException( + "Unknown node under OtherHomePartner instance: " + child.getName()); + } + } + if (fqdn == null) { + throw new ParsingException("OtherHomePartner instance missing FQDN field"); + } + return fqdn; + } + + /** * Parse configurations under PerProviderSubscription/Credential subtree. * * @param node PPSNode representing the root of the PerProviderSubscription/Credential subtree @@ -601,6 +826,12 @@ public final class PPSMOParser { Credential credential = new Credential(); for (PPSNode child: node.getChildren()) { switch (child.getName()) { + case NODE_CREATION_DATE: + credential.creationTimeInMs = parseDate(getPpsNodeValue(child)); + break; + case NODE_EXPIRATION_DATE: + credential.expirationTimeInMs = parseDate(getPpsNodeValue(child)); + break; case NODE_USERNAME_PASSWORD: credential.userCredential = parseUserCredential(child); break; @@ -610,6 +841,10 @@ public final class PPSMOParser { case NODE_REALM: credential.realm = getPpsNodeValue(child); break; + case NODE_CHECK_AAA_SERVER_CERT_STATUS: + credential.checkAAAServerCertStatus = + Boolean.parseBoolean(getPpsNodeValue(child)); + break; case NODE_SIM: credential.simCredential = parseSimCredential(child); break; @@ -644,6 +879,15 @@ public final class PPSMOParser { case NODE_PASSWORD: userCred.password = getPpsNodeValue(child); break; + case NODE_MACHINE_MANAGED: + userCred.machineManaged = Boolean.parseBoolean(getPpsNodeValue(child)); + break; + case NODE_SOFT_TOKEN_APP: + userCred.softTokenApp = getPpsNodeValue(child); + break; + case NODE_ABLE_TO_SHARE: + userCred.ableToShare = Boolean.parseBoolean(getPpsNodeValue(child)); + break; case NODE_EAP_METHOD: parseEAPMethod(child, userCred); break; @@ -678,6 +922,15 @@ public final class PPSMOParser { case NODE_INNER_METHOD: userCred.nonEapInnerMethod = getPpsNodeValue(child); break; + case NODE_VENDOR_ID: + case NODE_VENDOR_TYPE: + case NODE_INNER_EAP_TYPE: + case NODE_INNER_VENDOR_ID: + case NODE_INNER_VENDOR_TYPE: + // Only EAP-TTLS is currently supported for user credential, which doesn't + // use any of these parameters. + Log.d(TAG, "Ignore unsupported EAP method parameter: " + child.getName()); + break; default: throw new ParsingException("Unknown node under EAPMethod: " + child.getName()); } @@ -770,6 +1023,22 @@ public final class PPSMOParser { } /** + * Convert a date string to the number of milliseconds since January 1, 1970, 00:00:00 GMT. + * + * @param dateStr String in the format of yyyy-MM-dd'T'HH:mm:ss'Z' + * @return number of milliseconds + * @throws ParsingException + */ + private static long parseDate(String dateStr) throws ParsingException { + try { + DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + return format.parse(dateStr).getTime(); + } catch (ParseException pe) { + throw new ParsingException("Badly formatted time: " + dateStr); + } + } + + /** * Parse an integer string. * * @param value String of integer value @@ -783,4 +1052,19 @@ public final class PPSMOParser { throw new ParsingException("Invalid integer value: " + value); } } + + /** + * Convert a List<Long> to a primitive long array long[]. + * + * @param list List to be converted + * @return long[] + */ + private static long[] convertFromLongList(List<Long> list) { + Long[] objectArray = list.toArray(new Long[list.size()]); + long[] primitiveArray = new long[objectArray.length]; + for (int i = 0; i < objectArray.length; i++) { + primitiveArray[i] = objectArray[i].longValue(); + } + return primitiveArray; + } } diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java index 790dfaf643ca..3374f42dc118 100644 --- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java +++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java @@ -23,6 +23,7 @@ import android.os.Parcel; import android.text.TextUtils; import android.util.Log; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; @@ -41,8 +42,6 @@ import java.util.Set; * In addition to the fields in the Credential subtree, this will also maintain necessary * information for the private key and certificates associated with this credential. * - * Currently we only support the nodes that are used by Hotspot 2.0 Release 1. - * * @hide */ public final class Credential implements Parcelable { @@ -52,7 +51,21 @@ public final class Credential implements Parcelable { * Max string length for realm. Refer to Credential/Realm node in Hotspot 2.0 Release 2 * Technical Specification Section 9.1 for more info. */ - private static final int MAX_REALM_LENGTH = 253; + private static final int MAX_REALM_BYTES = 253; + + /** + * The time this credential is created. It is in the format of number + * of milliseconds since January 1, 1970, 00:00:00 GMT. + * Using Long.MIN_VALUE to indicate unset value. + */ + public long creationTimeInMs = Long.MIN_VALUE; + + /** + * The time this credential will expire. It is in the format of number + * of milliseconds since January 1, 1970, 00:00:00 GMT. + * Using Long.MIN_VALUE to indicate unset value. + */ + public long expirationTimeInMs = Long.MIN_VALUE; /** * The realm associated with this credential. It will be used to determine @@ -62,6 +75,13 @@ public final class Credential implements Parcelable { public String realm = null; /** + * When set to true, the device should check AAA (Authentication, Authorization, + * and Accounting) server's certificate during EAP (Extensible Authentication + * Protocol) authentication. + */ + public boolean checkAAAServerCertStatus = false; + + /** * Username-password based credential. * Contains the fields under PerProviderSubscription/Credential/UsernamePassword subtree. */ @@ -70,13 +90,13 @@ public final class Credential implements Parcelable { * Maximum string length for username. Refer to Credential/UsernamePassword/Username * node in Hotspot 2.0 Release 2 Technical Specification Section 9.1 for more info. */ - private static final int MAX_USERNAME_LENGTH = 63; + private static final int MAX_USERNAME_BYTES = 63; /** * Maximum string length for password. Refer to Credential/UsernamePassword/Password * in Hotspot 2.0 Release 2 Technical Specification Section 9.1 for more info. */ - private static final int MAX_PASSWORD_LENGTH = 255; + private static final int MAX_PASSWORD_BYTES = 255; /** * Supported Non-EAP inner methods. Refer to @@ -97,6 +117,21 @@ public final class Credential implements Parcelable { public String password = null; /** + * Flag indicating if the password is machine managed. + */ + public boolean machineManaged = false; + + /** + * The name of the application used to generate the password. + */ + public String softTokenApp = null; + + /** + * Flag indicating if this credential is usable on other mobile devices as well. + */ + public boolean ableToShare = false; + + /** * EAP (Extensible Authentication Protocol) method type. * Refer to http://www.iana.org/assignments/eap-numbers/eap-numbers.xml#eap-numbers-4 * for valid values. @@ -123,6 +158,9 @@ public final class Credential implements Parcelable { if (source != null) { username = source.username; password = source.password; + machineManaged = source.machineManaged; + softTokenApp = source.softTokenApp; + ableToShare = source.ableToShare; eapType = source.eapType; nonEapInnerMethod = source.nonEapInnerMethod; } @@ -137,6 +175,9 @@ public final class Credential implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeString(username); dest.writeString(password); + dest.writeInt(machineManaged ? 1 : 0); + dest.writeString(softTokenApp); + dest.writeInt(ableToShare ? 1 : 0); dest.writeInt(eapType); dest.writeString(nonEapInnerMethod); } @@ -151,10 +192,13 @@ public final class Credential implements Parcelable { } UserCredential that = (UserCredential) thatObject; - return TextUtils.equals(username, that.username) && - TextUtils.equals(password, that.password) && - eapType == that.eapType && - TextUtils.equals(nonEapInnerMethod, that.nonEapInnerMethod); + return TextUtils.equals(username, that.username) + && TextUtils.equals(password, that.password) + && machineManaged == that.machineManaged + && TextUtils.equals(softTokenApp, that.softTokenApp) + && ableToShare == that.ableToShare + && eapType == that.eapType + && TextUtils.equals(nonEapInnerMethod, that.nonEapInnerMethod); } /** @@ -167,8 +211,9 @@ public final class Credential implements Parcelable { Log.d(TAG, "Missing username"); return false; } - if (username.length() > MAX_USERNAME_LENGTH) { - Log.d(TAG, "username exceeding maximum length: " + username.length()); + if (username.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) { + Log.d(TAG, "username exceeding maximum length: " + + username.getBytes(StandardCharsets.UTF_8).length); return false; } @@ -176,8 +221,9 @@ public final class Credential implements Parcelable { Log.d(TAG, "Missing password"); return false; } - if (password.length() > MAX_PASSWORD_LENGTH) { - Log.d(TAG, "password exceeding maximum length: " + password.length()); + if (password.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) { + Log.d(TAG, "password exceeding maximum length: " + + password.getBytes(StandardCharsets.UTF_8).length); return false; } @@ -202,6 +248,9 @@ public final class Credential implements Parcelable { UserCredential userCredential = new UserCredential(); userCredential.username = in.readString(); userCredential.password = in.readString(); + userCredential.machineManaged = in.readInt() != 0; + userCredential.softTokenApp = in.readString(); + userCredential.ableToShare = in.readInt() != 0; userCredential.eapType = in.readInt(); userCredential.nonEapInnerMethod = in.readString(); return userCredential; @@ -281,8 +330,8 @@ public final class Credential implements Parcelable { } CertificateCredential that = (CertificateCredential) thatObject; - return TextUtils.equals(certType, that.certType) && - Arrays.equals(certSha256FingerPrint, that.certSha256FingerPrint); + return TextUtils.equals(certType, that.certType) + && Arrays.equals(certSha256FingerPrint, that.certSha256FingerPrint); } /** @@ -295,8 +344,8 @@ public final class Credential implements Parcelable { Log.d(TAG, "Unsupported certificate type: " + certType); return false; } - if (certSha256FingerPrint == null || - certSha256FingerPrint.length != CERT_SHA256_FINGER_PRINT_LENGTH) { + if (certSha256FingerPrint == null + || certSha256FingerPrint.length != CERT_SHA256_FINGER_PRINT_LENGTH) { Log.d(TAG, "Invalid SHA-256 fingerprint"); return false; } @@ -378,8 +427,8 @@ public final class Credential implements Parcelable { } SimCredential that = (SimCredential) thatObject; - return TextUtils.equals(imsi, that.imsi) && - eapType == that.eapType; + return TextUtils.equals(imsi, that.imsi) + && eapType == that.eapType; } @Override @@ -400,8 +449,8 @@ public final class Credential implements Parcelable { if (!verifyImsi()) { return false; } - if (eapType != EAPConstants.EAP_SIM && eapType != EAPConstants.EAP_AKA && - eapType != EAPConstants.EAP_AKA_PRIME) { + if (eapType != EAPConstants.EAP_SIM && eapType != EAPConstants.EAP_AKA + && eapType != EAPConstants.EAP_AKA_PRIME) { Log.d(TAG, "Invalid EAP Type for SIM credential: " + eapType); return false; } @@ -490,7 +539,10 @@ public final class Credential implements Parcelable { */ public Credential(Credential source) { if (source != null) { + creationTimeInMs = source.creationTimeInMs; + expirationTimeInMs = source.expirationTimeInMs; realm = source.realm; + checkAAAServerCertStatus = source.checkAAAServerCertStatus; if (source.userCredential != null) { userCredential = new UserCredential(source.userCredential); } @@ -516,7 +568,10 @@ public final class Credential implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(creationTimeInMs); + dest.writeLong(expirationTimeInMs); dest.writeString(realm); + dest.writeInt(checkAAAServerCertStatus ? 1 : 0); dest.writeParcelable(userCredential, flags); dest.writeParcelable(certCredential, flags); dest.writeParcelable(simCredential, flags); @@ -535,16 +590,19 @@ public final class Credential implements Parcelable { } Credential that = (Credential) thatObject; - return TextUtils.equals(realm, that.realm) && - (userCredential == null ? that.userCredential == null : - userCredential.equals(that.userCredential)) && - (certCredential == null ? that.certCredential == null : - certCredential.equals(that.certCredential)) && - (simCredential == null ? that.simCredential == null : - simCredential.equals(that.simCredential)) && - isX509CertificateEquals(caCertificate, that.caCertificate) && - isX509CertificatesEquals(clientCertificateChain, that.clientCertificateChain) && - isPrivateKeyEquals(clientPrivateKey, that.clientPrivateKey); + return TextUtils.equals(realm, that.realm) + && creationTimeInMs == that.creationTimeInMs + && expirationTimeInMs == that.expirationTimeInMs + && checkAAAServerCertStatus == that.checkAAAServerCertStatus + && (userCredential == null ? that.userCredential == null + : userCredential.equals(that.userCredential)) + && (certCredential == null ? that.certCredential == null + : certCredential.equals(that.certCredential)) + && (simCredential == null ? that.simCredential == null + : simCredential.equals(that.simCredential)) + && isX509CertificateEquals(caCertificate, that.caCertificate) + && isX509CertificatesEquals(clientCertificateChain, that.clientCertificateChain) + && isPrivateKeyEquals(clientPrivateKey, that.clientPrivateKey); } /** @@ -557,8 +615,9 @@ public final class Credential implements Parcelable { Log.d(TAG, "Missing realm"); return false; } - if (realm.length() > MAX_REALM_LENGTH) { - Log.d(TAG, "realm exceeding maximum length: " + realm.length()); + if (realm.getBytes(StandardCharsets.UTF_8).length > MAX_REALM_BYTES) { + Log.d(TAG, "realm exceeding maximum length: " + + realm.getBytes(StandardCharsets.UTF_8).length); return false; } @@ -588,7 +647,10 @@ public final class Credential implements Parcelable { @Override public Credential createFromParcel(Parcel in) { Credential credential = new Credential(); + credential.creationTimeInMs = in.readLong(); + credential.expirationTimeInMs = in.readLong(); credential.realm = in.readString(); + credential.checkAAAServerCertStatus = in.readInt() != 0; credential.userCredential = in.readParcelable(null); credential.certCredential = in.readParcelable(null); credential.simCredential = in.readParcelable(null); diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java index d4a5792d93fc..4ddf21099751 100644 --- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java +++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java @@ -21,7 +21,11 @@ import android.os.Parcel; import android.text.TextUtils; import android.util.Log; +import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; /** * Class representing HomeSP subtree in PerProviderSubscription (PPS) @@ -30,14 +34,22 @@ import java.util.Arrays; * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0 * Release 2 Technical Specification. * - * Currently we only support the nodes that are used by Hotspot 2.0 Release 1. - * * @hide */ public final class HomeSP implements Parcelable { private static final String TAG = "HomeSP"; /** + * Maximum number of bytes allowed for a SSID. + */ + private static final int MAX_SSID_BYTES = 32; + + /** + * Integer value used for indicating null value in the Parcel. + */ + private static final int NULL_VALUE = -1; + + /** * FQDN (Fully Qualified Domain Name) of this home service provider. */ public String fqdn = null; @@ -48,6 +60,55 @@ public final class HomeSP implements Parcelable { public String friendlyName = null; /** + * Icon URL of this home service provider. + */ + public String iconUrl = null; + + /** + * <SSID, HESSID> duple of the networks that are consider home networks. + * + * According to the Section 9.1.2 of the Hotspot 2.0 Release 2 Technical Specification, + * all nodes in the PSS MO are encoded using UTF-8 unless stated otherwise. Thus, the SSID + * string is assumed to be encoded using UTF-8. + */ + public Map<String, Long> homeNetworkIds = null; + + /** + * Used for determining if this provider is a member of a given Hotspot provider. + * Every Organization Identifiers (OIs) in this list are required to match an OI in the + * the Roaming Consortium advertised by a Hotspot, in order to consider this provider + * as a member of that Hotspot provider (e.g. successful authentication with such Hotspot + * is possible). + * + * Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object + * (MO) tree for more detail. + */ + public long[] matchAllOIs = null; + + /** + * Used for determining if this provider is a member of a given Hotspot provider. + * Matching of any Organization Identifiers (OIs) in this list with an OI in the + * Roaming Consortium advertised by a Hotspot, will consider this provider as a member + * of that Hotspot provider (e.g. successful authentication with such Hotspot + * is possible). + * + * {@link #matchAllOIs} will have precedence over this one, meaning this list will + * only be used for matching if {@link #matchAllOIs} is null or empty. + * + * Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object + * (MO) tree for more detail. + */ + public long[] matchAnyOIs = null; + + /** + * List of FQDN (Fully Qualified Domain Name) of partner providers. + * These providers should also be regarded as home Hotspot operators. + * This relationship is most likely achieved via a commercial agreement or + * operator merges between the providers. + */ + public String[] otherHomePartners = null; + + /** * List of Organization Identifiers (OIs) identifying a roaming consortium of * which this provider is a member. */ @@ -64,13 +125,28 @@ public final class HomeSP implements Parcelable { * @param source The source to copy from */ public HomeSP(HomeSP source) { - if (source != null) { - fqdn = source.fqdn; - friendlyName = source.friendlyName; - if (source.roamingConsortiumOIs != null) { - roamingConsortiumOIs = Arrays.copyOf(source.roamingConsortiumOIs, - source.roamingConsortiumOIs.length); - } + if (source == null) { + return; + } + fqdn = source.fqdn; + friendlyName = source.friendlyName; + iconUrl = source.iconUrl; + if (source.homeNetworkIds != null) { + homeNetworkIds = Collections.unmodifiableMap(source.homeNetworkIds); + } + if (source.matchAllOIs != null) { + matchAllOIs = Arrays.copyOf(source.matchAllOIs, source.matchAllOIs.length); + } + if (source.matchAnyOIs != null) { + matchAnyOIs = Arrays.copyOf(source.matchAnyOIs, source.matchAnyOIs.length); + } + if (source.otherHomePartners != null) { + otherHomePartners = Arrays.copyOf(source.otherHomePartners, + source.otherHomePartners.length); + } + if (source.roamingConsortiumOIs != null) { + roamingConsortiumOIs = Arrays.copyOf(source.roamingConsortiumOIs, + source.roamingConsortiumOIs.length); } } @@ -83,6 +159,11 @@ public final class HomeSP implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeString(fqdn); dest.writeString(friendlyName); + dest.writeString(iconUrl); + writeHomeNetworkIds(dest, homeNetworkIds); + dest.writeLongArray(matchAllOIs); + dest.writeLongArray(matchAnyOIs); + dest.writeStringArray(otherHomePartners); dest.writeLongArray(roamingConsortiumOIs); } @@ -96,9 +177,15 @@ public final class HomeSP implements Parcelable { } HomeSP that = (HomeSP) thatObject; - return TextUtils.equals(fqdn, that.fqdn) && - TextUtils.equals(friendlyName, that.friendlyName) && - Arrays.equals(roamingConsortiumOIs, that.roamingConsortiumOIs); + return TextUtils.equals(fqdn, that.fqdn) + && TextUtils.equals(friendlyName, that.friendlyName) + && TextUtils.equals(iconUrl, that.iconUrl) + && (homeNetworkIds == null ? that.homeNetworkIds == null + : homeNetworkIds.equals(that.homeNetworkIds)) + && Arrays.equals(matchAllOIs, that.matchAllOIs) + && Arrays.equals(matchAnyOIs, that.matchAnyOIs) + && Arrays.equals(otherHomePartners, that.otherHomePartners) + && Arrays.equals(roamingConsortiumOIs, that.roamingConsortiumOIs); } /** @@ -115,6 +202,16 @@ public final class HomeSP implements Parcelable { Log.d(TAG, "Missing friendly name"); return false; } + // Verify SSIDs specified in the NetworkID + if (homeNetworkIds != null) { + for (Map.Entry<String, Long> entry : homeNetworkIds.entrySet()) { + if (entry.getKey() == null || + entry.getKey().getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) { + Log.d(TAG, "Invalid SSID in HomeNetworkIDs"); + return false; + } + } + } return true; } @@ -125,6 +222,11 @@ public final class HomeSP implements Parcelable { HomeSP homeSp = new HomeSP(); homeSp.fqdn = in.readString(); homeSp.friendlyName = in.readString(); + homeSp.iconUrl = in.readString(); + homeSp.homeNetworkIds = readHomeNetworkIds(in); + homeSp.matchAllOIs = in.createLongArray(); + homeSp.matchAnyOIs = in.createLongArray(); + homeSp.otherHomePartners = in.createStringArray(); homeSp.roamingConsortiumOIs = in.createLongArray(); return homeSp; } @@ -133,5 +235,51 @@ public final class HomeSP implements Parcelable { public HomeSP[] newArray(int size) { return new HomeSP[size]; } + + /** + * Helper function for reading a Home Network IDs map from a Parcel. + * + * @param in The Parcel to read from + * @return Map of home network IDs + */ + private Map<String, Long> readHomeNetworkIds(Parcel in) { + int size = in.readInt(); + if (size == NULL_VALUE) { + return null; + } + Map<String, Long> networkIds = new HashMap<>(size); + for (int i = 0; i < size; i++) { + String key = in.readString(); + Long value = null; + long readValue = in.readLong(); + if (readValue != NULL_VALUE) { + value = Long.valueOf(readValue); + } + networkIds.put(key, value); + } + return networkIds; + } }; + + /** + * Helper function for writing Home Network IDs map to a Parcel. + * + * @param dest The Parcel to write to + * @param networkIds The map of home network IDs + */ + private static void writeHomeNetworkIds(Parcel dest, Map<String, Long> networkIds) { + if (networkIds == null) { + dest.writeInt(NULL_VALUE); + return; + } + dest.writeInt(networkIds.size()); + for (Map.Entry<String, Long> entry : networkIds.entrySet()) { + dest.writeString(entry.getKey()); + if (entry.getValue() == null) { + dest.writeLong(NULL_VALUE); + } else { + dest.writeLong(entry.getValue()); + } + } + } } diff --git a/wifi/tests/assets/pps/PerProviderSubscription.xml b/wifi/tests/assets/pps/PerProviderSubscription.xml index 53d38ad23b9b..3969f697a325 100644 --- a/wifi/tests/assets/pps/PerProviderSubscription.xml +++ b/wifi/tests/assets/pps/PerProviderSubscription.xml @@ -23,14 +23,86 @@ <NodeName>RoamingConsortiumOI</NodeName> <Value>112233,445566</Value> </Node> + <Node> + <NodeName>IconURL</NodeName> + <Value>icon.test.com</Value> + </Node> + <Node> + <NodeName>NetworkID</NodeName> + <Node> + <NodeName>n001</NodeName> + <Node> + <NodeName>SSID</NodeName> + <Value>TestSSID</Value> + </Node> + <Node> + <NodeName>HESSID</NodeName> + <Value>12345678</Value> + </Node> + </Node> + <Node> + <NodeName>n002</NodeName> + <Node> + <NodeName>SSID</NodeName> + <Value>NullHESSID</Value> + </Node> + </Node> + </Node> + <Node> + <NodeName>HomeOIList</NodeName> + <Node> + <NodeName>h001</NodeName> + <Node> + <NodeName>HomeOI</NodeName> + <Value>11223344</Value> + </Node> + <Node> + <NodeName>HomeOIRequired</NodeName> + <Value>true</Value> + </Node> + </Node> + <Node> + <NodeName>h002</NodeName> + <Node> + <NodeName>HomeOI</NodeName> + <Value>55667788</Value> + </Node> + <Node> + <NodeName>HomeOIRequired</NodeName> + <Value>false</Value> + </Node> + </Node> + </Node> + <Node> + <NodeName>OtherHomePartners</NodeName> + <Node> + <NodeName>o001</NodeName> + <Node> + <NodeName>FQDN</NodeName> + <Value>other.fqdn.com</Value> + </Node> + </Node> + </Node> </Node> <Node> <NodeName>Credential</NodeName> <Node> + <NodeName>CreationDate</NodeName> + <Value>2016-01-01T10:00:00Z</Value> + </Node> + <Node> + <NodeName>ExpirationDate</NodeName> + <Value>2016-02-01T10:00:00Z</Value> + </Node> + <Node> <NodeName>Realm</NodeName> <Value>shaken.stirred.com</Value> </Node> <Node> + <NodeName>CheckAAAServerCertStatus</NodeName> + <Value>true</Value> + </Node> + <Node> <NodeName>UsernamePassword</NodeName> <Node> <NodeName>Username</NodeName> @@ -41,6 +113,18 @@ <Value>Ym9uZDAwNw==</Value> </Node> <Node> + <NodeName>MachineManaged</NodeName> + <Value>true</Value> + </Node> + <Node> + <NodeName>SoftTokenApp</NodeName> + <Value>TestApp</Value> + </Node> + <Node> + <NodeName>AbleToShare</NodeName> + <Value>true</Value> + </Node> + <Node> <NodeName>EAPMethod</NodeName> <Node> <NodeName>EAPType</NodeName> diff --git a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java index 10b02677a15b..1c7508e7d0f5 100644 --- a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java +++ b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java @@ -31,7 +31,10 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.HashMap; /** * Unit tests for {@link android.net.wifi.hotspot2.omadm.PPSMOParser}. @@ -77,7 +80,7 @@ public class PPSMOParserTest { * * @return {@link PasspointConfiguration} */ - private PasspointConfiguration generateConfigurationFromPPSMOTree() { + private PasspointConfiguration generateConfigurationFromPPSMOTree() throws Exception { PasspointConfiguration config = new PasspointConfiguration(); // HomeSP configuration. @@ -85,13 +88,27 @@ public class PPSMOParserTest { config.homeSp.friendlyName = "Century House"; config.homeSp.fqdn = "mi6.co.uk"; config.homeSp.roamingConsortiumOIs = new long[] {0x112233L, 0x445566L}; + config.homeSp.iconUrl = "icon.test.com"; + config.homeSp.homeNetworkIds = new HashMap<>(); + config.homeSp.homeNetworkIds.put("TestSSID", 0x12345678L); + config.homeSp.homeNetworkIds.put("NullHESSID", null); + config.homeSp.matchAllOIs = new long[] {0x11223344}; + config.homeSp.matchAnyOIs = new long[] {0x55667788}; + config.homeSp.otherHomePartners = new String[] {"other.fqdn.com"}; // Credential configuration. + DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); config.credential = new Credential(); + config.credential.creationTimeInMs = format.parse("2016-01-01T10:00:00Z").getTime(); + config.credential.expirationTimeInMs = format.parse("2016-02-01T10:00:00Z").getTime(); config.credential.realm = "shaken.stirred.com"; + config.credential.checkAAAServerCertStatus = true; config.credential.userCredential = new Credential.UserCredential(); config.credential.userCredential.username = "james"; config.credential.userCredential.password = "Ym9uZDAwNw=="; + config.credential.userCredential.machineManaged = true; + config.credential.userCredential.softTokenApp = "TestApp"; + config.credential.userCredential.ableToShare = true; config.credential.userCredential.eapType = 21; config.credential.userCredential.nonEapInnerMethod = "MS-CHAP-V2"; config.credential.certCredential = new Credential.CertificateCredential(); diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java index 9c8b749e1c93..f571c7fc5966 100644 --- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java +++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java @@ -37,6 +37,17 @@ import org.junit.Test; */ @SmallTest public class CredentialTest { + /** + * Helper function for generating Credential for testing. + * + * @param userCred Instance of UserCredential + * @param certCred Instance of CertificateCredential + * @param simCred Instance of SimCredential + * @param caCert CA certificate + * @param clientCertificateChain Chain of client certificates + * @param clientPrivateKey Client private key + * @return {@link Credential} + */ private static Credential createCredential(Credential.UserCredential userCred, Credential.CertificateCredential certCred, Credential.SimCredential simCred, @@ -44,7 +55,10 @@ public class CredentialTest { X509Certificate[] clientCertificateChain, PrivateKey clientPrivateKey) { Credential cred = new Credential(); + cred.creationTimeInMs = 123455L; + cred.expirationTimeInMs = 2310093L; cred.realm = "realm"; + cred.checkAAAServerCertStatus = true; cred.userCredential = userCred; cred.certCredential = certCred; cred.simCredential = simCred; @@ -54,6 +68,11 @@ public class CredentialTest { return cred; } + /** + * Helper function for generating certificate credential for testing. + * + * @return {@link Credential} + */ private static Credential createCredentialWithCertificateCredential() { Credential.CertificateCredential certCred = new Credential.CertificateCredential(); certCred.certType = "x509v3"; @@ -62,6 +81,11 @@ public class CredentialTest { new X509Certificate[] {FakeKeys.CLIENT_CERT}, FakeKeys.RSA_KEY1); } + /** + * Helper function for generating SIM credential for testing. + * + * @return {@link Credential} + */ private static Credential createCredentialWithSimCredential() { Credential.SimCredential simCred = new Credential.SimCredential(); simCred.imsi = "1234*"; @@ -69,10 +93,18 @@ public class CredentialTest { return createCredential(null, null, simCred, null, null, null); } + /** + * Helper function for generating user credential for testing. + * + * @return {@link Credential} + */ private static Credential createCredentialWithUserCredential() { Credential.UserCredential userCred = new Credential.UserCredential(); userCred.username = "username"; userCred.password = "password"; + userCred.machineManaged = true; + userCred.ableToShare = true; + userCred.softTokenApp = "TestApp"; userCred.eapType = EAPConstants.EAP_TTLS; userCred.nonEapInnerMethod = "MS-CHAP"; return createCredential(userCred, null, null, FakeKeys.CA_CERT0, diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java index c70799332b02..45fdbea90769 100644 --- a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java +++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java @@ -24,19 +24,71 @@ import android.test.suitebuilder.annotation.SmallTest; import org.junit.Test; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + /** * Unit tests for {@link android.net.wifi.hotspot2.pps.HomeSP}. */ @SmallTest public class HomeSPTest { - private static HomeSP createHomeSp() { + + /** + * Helper function for creating a map of home network IDs for testing. + * + * @return Map of home network IDs + */ + private static Map<String, Long> createHomeNetworkIds() { + Map<String, Long> homeNetworkIds = new HashMap<>(); + homeNetworkIds.put("ssid", 0x1234L); + homeNetworkIds.put("nullhessid", null); + return homeNetworkIds; + } + + /** + * Helper function for creating a HomeSP for testing. + * + * @param homeNetworkIds The map of home network IDs associated with HomeSP + * @return {@link HomeSP} + */ + private static HomeSP createHomeSp(Map<String, Long> homeNetworkIds) { HomeSP homeSp = new HomeSP(); homeSp.fqdn = "fqdn"; homeSp.friendlyName = "friendly name"; + homeSp.iconUrl = "icon.url"; + homeSp.homeNetworkIds = homeNetworkIds; + homeSp.matchAllOIs = new long[] {0x11L, 0x22L}; + homeSp.matchAnyOIs = new long[] {0x33L, 0x44L}; + homeSp.otherHomePartners = new String[] {"partner1", "partner2"}; homeSp.roamingConsortiumOIs = new long[] {0x55, 0x66}; return homeSp; } + /** + * Helper function for creating a HomeSP with home network IDs for testing. + * + * @return {@link HomeSP} + */ + private static HomeSP createHomeSpWithHomeNetworkIds() { + return createHomeSp(createHomeNetworkIds()); + } + + /** + * Helper function for creating a HomeSP without home network IDs for testing. + * + * @return {@link HomeSP} + */ + private static HomeSP createHomeSpWithoutHomeNetworkIds() { + return createHomeSp(null); + } + + /** + * Helper function for verifying HomeSP after parcel write then read. + * @param writeHomeSp + * @throws Exception + */ private static void verifyParcel(HomeSP writeHomeSp) throws Exception { Parcel parcel = Parcel.obtain(); writeHomeSp.writeToParcel(parcel, 0); @@ -57,13 +109,23 @@ public class HomeSPTest { } /** - * Verify parcel read/write for a valid HomeSP. + * Verify parcel read/write for a HomeSP containing Home Network IDs. + * + * @throws Exception + */ + @Test + public void verifyParcelWithHomeNetworkIds() throws Exception { + verifyParcel(createHomeSpWithHomeNetworkIds()); + } + + /** + * Verify parcel read/write for a HomeSP without Home Network IDs. * * @throws Exception */ @Test - public void verifyParcelWithValidHomeSP() throws Exception { - verifyParcel(createHomeSp()); + public void verifyParcelWithoutHomeNetworkIds() throws Exception { + verifyParcel(createHomeSpWithoutHomeNetworkIds()); } /** @@ -120,6 +182,49 @@ public class HomeSPTest { } /** + * Verify that a HomeSP is valid when the optional Home Network IDs are + * provided. + * + * @throws Exception + */ + @Test + public void validateHomeSpWithHomeNetworkIds() throws Exception { + HomeSP homeSp = createHomeSpWithHomeNetworkIds(); + assertTrue(homeSp.validate()); + } + + /** + * Verify that a HomeSP is valid when the optional Home Network IDs are + * not provided. + * + * @throws Exception + */ + @Test + public void validateHomeSpWithoutHomeNetworkIds() throws Exception { + HomeSP homeSp = createHomeSpWithoutHomeNetworkIds(); + assertTrue(homeSp.validate()); + } + + /** + * Verify that a HomeSP is invalid when the optional Home Network IDs + * contained an invalid SSID (exceeding maximum number of bytes). + * + * @throws Exception + */ + @Test + public void validateHomeSpWithInvalidHomeNetworkIds() throws Exception { + HomeSP homeSp = new HomeSP(); + homeSp.fqdn = "fqdn"; + homeSp.friendlyName = "friendly name"; + homeSp.homeNetworkIds = new HashMap<>(); + byte[] rawSsidBytes = new byte[33]; + Arrays.fill(rawSsidBytes, (byte) 'a'); + homeSp.homeNetworkIds.put( + StringFactory.newStringFromBytes(rawSsidBytes, StandardCharsets.UTF_8), 0x1234L); + assertFalse(homeSp.validate()); + } + + /** * Verify that copy constructor works when pass in a null source. * * @throws Exception @@ -138,10 +243,7 @@ public class HomeSPTest { */ @Test public void validateCopyConstructorFromValidSource() throws Exception { - HomeSP sourceSp = new HomeSP(); - sourceSp.fqdn = "fqdn"; - sourceSp.friendlyName = "friendlyName"; - sourceSp.roamingConsortiumOIs = new long[] {0x55, 0x66}; + HomeSP sourceSp = createHomeSpWithHomeNetworkIds(); HomeSP copySp = new HomeSP(sourceSp); assertTrue(copySp.equals(sourceSp)); } |