diff options
213 files changed, 6910 insertions, 2209 deletions
diff --git a/Android.mk b/Android.mk index 2539c3dfbda6..71b77d5840e5 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 \ diff --git a/api/current.txt b/api/current.txt index e56eb36b91ed..d8110203c8cd 100644 --- a/api/current.txt +++ b/api/current.txt @@ -203,6 +203,8 @@ package android { public static final class R.attr { ctor public R.attr(); + field public static final int __removed0 = 16844097; // 0x1010541 + field public static final int __removed1 = 16844099; // 0x1010543 field public static final int absListViewStyle = 16842858; // 0x101006a field public static final int accessibilityEventTypes = 16843648; // 0x1010380 field public static final int accessibilityFeedbackType = 16843650; // 0x1010382 @@ -758,7 +760,6 @@ package android { field public static final int keyboardLayout = 16843691; // 0x10103ab field public static final int keyboardMode = 16843341; // 0x101024d field public static final int keyboardNavigationCluster = 16844096; // 0x1010540 - field public static final int keyboardNavigationSection = 16844097; // 0x1010541 field public static final int keycode = 16842949; // 0x10100c5 field public static final int killAfterRestore = 16843420; // 0x101029c field public static final int label = 16842753; // 0x1010001 @@ -908,7 +909,6 @@ package android { field public static final int nextFocusLeft = 16842977; // 0x10100e1 field public static final int nextFocusRight = 16842978; // 0x10100e2 field public static final int nextFocusUp = 16842979; // 0x10100e3 - field public static final int nextSectionForward = 16844099; // 0x1010543 field public static final int noHistory = 16843309; // 0x101022d field public static final int normalScreens = 16843397; // 0x1010285 field public static final int notificationTimeout = 16843651; // 0x1010383 @@ -1169,6 +1169,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 +1818,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 +3538,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 +3687,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); @@ -5583,6 +5583,18 @@ package android.app { field public static final int STYLE_SPINNER = 0; // 0x0 } + public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable { + ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent); + method public int describeContents(); + method public android.app.PendingIntent getUserAction(); + method public java.lang.CharSequence getUserActionTitle(); + method public java.lang.CharSequence getUserMessage(); + method public void showAsDialog(android.app.Activity); + method public void showAsNotification(android.content.Context); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR; + } + public final class RemoteAction implements android.os.Parcelable { ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener); method public android.app.RemoteAction clone(); @@ -9748,6 +9760,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 { @@ -13214,6 +13227,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); @@ -13234,6 +13248,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(); } @@ -13788,6 +13810,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 { @@ -17593,6 +17628,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(); @@ -17602,6 +17646,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); @@ -17621,9 +17667,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); @@ -18092,6 +18148,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(); @@ -18867,6 +18931,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(); @@ -19395,6 +19487,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(); @@ -23568,6 +23689,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"; @@ -23608,8 +23730,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"; @@ -23738,6 +23866,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 @@ -30454,14 +30583,22 @@ package android.os.storage { } public class StorageManager { + method public long getCacheQuotaBytes(); + method public long getCacheSizeBytes(); + method public long getExternalCacheQuotaBytes(); + method public long getExternalCacheSizeBytes(); method public java.lang.String getMountedObbPath(java.lang.String); method public android.os.storage.StorageVolume getPrimaryStorageVolume(); method public android.os.storage.StorageVolume getStorageVolume(java.io.File); method public java.util.List<android.os.storage.StorageVolume> getStorageVolumes(); + method public boolean isCacheBehaviorAtomic(java.io.File) throws java.io.IOException; + method public boolean isCacheBehaviorTombstone(java.io.File) throws java.io.IOException; method public boolean isEncrypted(java.io.File); method public boolean isObbMounted(java.lang.String); method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener); method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException; + method public void setCacheBehaviorAtomic(java.io.File, boolean) throws java.io.IOException; + method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException; method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener); field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE"; } @@ -32931,6 +33068,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"; @@ -35768,6 +35915,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(); @@ -35809,7 +35957,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(); @@ -36225,6 +36372,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); } @@ -36377,6 +36525,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 { @@ -38468,6 +38617,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(); @@ -39905,22 +40055,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); @@ -39936,13 +40070,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); @@ -39955,13 +40082,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...); @@ -42215,7 +42335,9 @@ package android.view { method public android.view.Display.Mode[] getSupportedModes(); method public deprecated float[] getSupportedRefreshRates(); method public deprecated int getWidth(); + method public boolean isHdr(); method public boolean isValid(); + method public boolean isWideColorGamut(); field public static final int DEFAULT_DISPLAY = 0; // 0x0 field public static final int FLAG_PRESENTATION = 8; // 0x8 field public static final int FLAG_PRIVATE = 4; // 0x4 @@ -42284,7 +42406,7 @@ package android.view { method public android.view.View findNearestTouchable(android.view.ViewGroup, int, int, int, int[]); method public final android.view.View findNextFocus(android.view.ViewGroup, android.view.View, int); method public android.view.View findNextFocusFromRect(android.view.ViewGroup, android.graphics.Rect, int); - method public android.view.View findNextKeyboardNavigationGroup(int, android.view.View, android.view.View, int); + method public android.view.View findNextKeyboardNavigationCluster(android.view.View, android.view.View, int); method public static android.view.FocusFinder getInstance(); } @@ -43582,7 +43704,7 @@ package android.view { method public void addChildrenForAccessibility(java.util.ArrayList<android.view.View>); method public void addFocusables(java.util.ArrayList<android.view.View>, int); method public void addFocusables(java.util.ArrayList<android.view.View>, int, int); - method public void addKeyboardNavigationGroups(int, java.util.Collection<android.view.View>, int); + method public void addKeyboardNavigationClusters(java.util.Collection<android.view.View>, int); method public void addOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener); method public void addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener); method public void addTouchables(java.util.ArrayList<android.view.View>); @@ -43704,6 +43826,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(); @@ -43746,7 +43869,6 @@ package android.view { method public int getNextFocusLeftId(); method public int getNextFocusRightId(); method public int getNextFocusUpId(); - method public int getNextSectionForwardId(); method public android.view.View.OnFocusChangeListener getOnFocusChangeListener(); method public android.view.ViewOutlineProvider getOutlineProvider(); method public int getOverScrollMode(); @@ -43790,7 +43912,6 @@ package android.view { method public java.lang.Object getTag(int); method public int getTextAlignment(); method public int getTextDirection(); - method public final deprecated java.lang.CharSequence getTooltip(); method public final java.lang.CharSequence getTooltipText(); method public final int getTop(); method protected float getTopFadingEdgeStrength(); @@ -43852,7 +43973,6 @@ package android.view { method public boolean isInLayout(); method public boolean isInTouchMode(); method public final boolean isKeyboardNavigationCluster(); - method public final boolean isKeyboardNavigationSection(); method public boolean isLaidOut(); method public boolean isLayoutDirectionResolved(); method public boolean isLayoutRequested(); @@ -43875,7 +43995,7 @@ package android.view { method public boolean isVerticalFadingEdgeEnabled(); method public boolean isVerticalScrollBarEnabled(); method public void jumpDrawablesToCurrentState(); - method public android.view.View keyboardNavigationGroupSearch(int, android.view.View, int); + method public android.view.View keyboardNavigationClusterSearch(android.view.View, int); method public void layout(int, int, int, int); method public final void measure(int, int); method protected static int[] mergeDrawableStates(int[], int[]); @@ -44010,6 +44130,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); @@ -44025,7 +44146,6 @@ package android.view { method public void setImportantForAccessibility(int); method public void setKeepScreenOn(boolean); method public void setKeyboardNavigationCluster(boolean); - method public void setKeyboardNavigationSection(boolean); method public void setLabelFor(int); method public void setLayerPaint(android.graphics.Paint); method public void setLayerType(int, android.graphics.Paint); @@ -44043,7 +44163,6 @@ package android.view { method public void setNextFocusLeftId(int); method public void setNextFocusRightId(int); method public void setNextFocusUpId(int); - method public void setNextSectionForwardId(int); method public void setOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener); method public void setOnClickListener(android.view.View.OnClickListener); method public void setOnContextClickListener(android.view.View.OnContextClickListener); @@ -44092,7 +44211,6 @@ package android.view { method public void setTag(int, java.lang.Object); method public void setTextAlignment(int); method public void setTextDirection(int); - method public final deprecated void setTooltip(java.lang.CharSequence); method public final void setTooltipText(java.lang.CharSequence); method public final void setTop(int); method public void setTouchDelegate(android.view.TouchDelegate); @@ -44150,8 +44268,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; @@ -44170,8 +44290,6 @@ package android.view { field public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1 field public static final int INVISIBLE = 4; // 0x4 field public static final int KEEP_SCREEN_ON = 67108864; // 0x4000000 - field public static final int KEYBOARD_NAVIGATION_GROUP_CLUSTER = 1; // 0x1 - field public static final int KEYBOARD_NAVIGATION_GROUP_SECTION = 2; // 0x2 field public static final int LAYER_TYPE_HARDWARE = 2; // 0x2 field public static final int LAYER_TYPE_NONE = 0; // 0x0 field public static final int LAYER_TYPE_SOFTWARE = 1; // 0x1 @@ -44183,6 +44301,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 @@ -44690,7 +44809,7 @@ package android.view { method public abstract boolean isLayoutRequested(); method public abstract boolean isTextAlignmentResolved(); method public abstract boolean isTextDirectionResolved(); - method public abstract android.view.View keyboardNavigationGroupSearch(int, android.view.View, int); + method public abstract android.view.View keyboardNavigationClusterSearch(android.view.View, int); method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int); method public abstract boolean onNestedFling(android.view.View, float, float, boolean); method public abstract boolean onNestedPreFling(android.view.View, float, float); @@ -46477,6 +46596,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 { @@ -49566,7 +49762,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(); @@ -49682,7 +49878,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); diff --git a/api/system-current.txt b/api/system-current.txt index 820c2aae5f52..5cd33ba3d7d5 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -312,6 +312,8 @@ package android { public static final class R.attr { ctor public R.attr(); + field public static final int __removed0 = 16844097; // 0x1010541 + field public static final int __removed1 = 16844099; // 0x1010543 field public static final int absListViewStyle = 16842858; // 0x101006a field public static final int accessibilityEventTypes = 16843648; // 0x1010380 field public static final int accessibilityFeedbackType = 16843650; // 0x1010382 @@ -867,7 +869,6 @@ package android { field public static final int keyboardLayout = 16843691; // 0x10103ab field public static final int keyboardMode = 16843341; // 0x101024d field public static final int keyboardNavigationCluster = 16844096; // 0x1010540 - field public static final int keyboardNavigationSection = 16844097; // 0x1010541 field public static final int keycode = 16842949; // 0x10100c5 field public static final int killAfterRestore = 16843420; // 0x101029c field public static final int label = 16842753; // 0x1010001 @@ -1017,7 +1018,6 @@ package android { field public static final int nextFocusLeft = 16842977; // 0x10100e1 field public static final int nextFocusRight = 16842978; // 0x10100e2 field public static final int nextFocusUp = 16842979; // 0x10100e3 - field public static final int nextSectionForward = 16844099; // 0x1010543 field public static final int noHistory = 16843309; // 0x101022d field public static final int normalScreens = 16843397; // 0x1010285 field public static final int notificationTimeout = 16843651; // 0x1010383 @@ -1282,6 +1282,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 +1931,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 +3657,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 +3808,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); @@ -5772,6 +5772,18 @@ package android.app { field public static final int STYLE_SPINNER = 0; // 0x0 } + public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable { + ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent); + method public int describeContents(); + method public android.app.PendingIntent getUserAction(); + method public java.lang.CharSequence getUserActionTitle(); + method public java.lang.CharSequence getUserMessage(); + method public void showAsDialog(android.app.Activity); + method public void showAsNotification(android.content.Context); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR; + } + public final class RemoteAction implements android.os.Parcelable { ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener); method public android.app.RemoteAction clone(); @@ -6775,15 +6787,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 @@ -6898,6 +6913,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); @@ -10152,6 +10173,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 { @@ -10182,7 +10204,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(); @@ -10190,6 +10213,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"; @@ -13759,6 +13783,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); @@ -13779,6 +13804,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(); } @@ -14333,6 +14366,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 { @@ -18860,6 +18906,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(); @@ -18869,6 +18924,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); @@ -18888,9 +18945,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); @@ -19359,6 +19426,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(); @@ -20134,6 +20209,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(); @@ -20662,6 +20765,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(); @@ -25256,6 +25388,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"; @@ -25287,6 +25420,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"; @@ -25327,8 +25461,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"; @@ -25338,6 +25478,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"; @@ -25537,6 +25678,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 @@ -25792,6 +25934,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 { @@ -33208,14 +33391,22 @@ package android.os.storage { } public class StorageManager { + method public long getCacheQuotaBytes(); + method public long getCacheSizeBytes(); + method public long getExternalCacheQuotaBytes(); + method public long getExternalCacheSizeBytes(); method public java.lang.String getMountedObbPath(java.lang.String); method public android.os.storage.StorageVolume getPrimaryStorageVolume(); method public android.os.storage.StorageVolume getStorageVolume(java.io.File); method public java.util.List<android.os.storage.StorageVolume> getStorageVolumes(); + method public boolean isCacheBehaviorAtomic(java.io.File) throws java.io.IOException; + method public boolean isCacheBehaviorTombstone(java.io.File) throws java.io.IOException; method public boolean isEncrypted(java.io.File); method public boolean isObbMounted(java.lang.String); method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener); method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException; + method public void setCacheBehaviorAtomic(java.io.File, boolean) throws java.io.IOException; + method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException; method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener); field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE"; } @@ -35754,6 +35945,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"; @@ -38713,6 +38914,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(); @@ -38754,7 +38956,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(); @@ -39215,6 +39416,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); } @@ -39367,6 +39569,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 { @@ -41724,6 +41927,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(); @@ -43208,22 +43412,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); @@ -43239,13 +43427,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); @@ -43258,13 +43439,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...); @@ -45519,7 +45693,9 @@ package android.view { method public android.view.Display.Mode[] getSupportedModes(); method public deprecated float[] getSupportedRefreshRates(); method public deprecated int getWidth(); + method public boolean isHdr(); method public boolean isValid(); + method public boolean isWideColorGamut(); field public static final int DEFAULT_DISPLAY = 0; // 0x0 field public static final int FLAG_PRESENTATION = 8; // 0x8 field public static final int FLAG_PRIVATE = 4; // 0x4 @@ -45588,7 +45764,7 @@ package android.view { method public android.view.View findNearestTouchable(android.view.ViewGroup, int, int, int, int[]); method public final android.view.View findNextFocus(android.view.ViewGroup, android.view.View, int); method public android.view.View findNextFocusFromRect(android.view.ViewGroup, android.graphics.Rect, int); - method public android.view.View findNextKeyboardNavigationGroup(int, android.view.View, android.view.View, int); + method public android.view.View findNextKeyboardNavigationCluster(android.view.View, android.view.View, int); method public static android.view.FocusFinder getInstance(); } @@ -46886,7 +47062,7 @@ package android.view { method public void addChildrenForAccessibility(java.util.ArrayList<android.view.View>); method public void addFocusables(java.util.ArrayList<android.view.View>, int); method public void addFocusables(java.util.ArrayList<android.view.View>, int, int); - method public void addKeyboardNavigationGroups(int, java.util.Collection<android.view.View>, int); + method public void addKeyboardNavigationClusters(java.util.Collection<android.view.View>, int); method public void addOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener); method public void addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener); method public void addTouchables(java.util.ArrayList<android.view.View>); @@ -47008,6 +47184,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(); @@ -47050,7 +47227,6 @@ package android.view { method public int getNextFocusLeftId(); method public int getNextFocusRightId(); method public int getNextFocusUpId(); - method public int getNextSectionForwardId(); method public android.view.View.OnFocusChangeListener getOnFocusChangeListener(); method public android.view.ViewOutlineProvider getOutlineProvider(); method public int getOverScrollMode(); @@ -47094,7 +47270,6 @@ package android.view { method public java.lang.Object getTag(int); method public int getTextAlignment(); method public int getTextDirection(); - method public final deprecated java.lang.CharSequence getTooltip(); method public final java.lang.CharSequence getTooltipText(); method public final int getTop(); method protected float getTopFadingEdgeStrength(); @@ -47156,7 +47331,6 @@ package android.view { method public boolean isInLayout(); method public boolean isInTouchMode(); method public final boolean isKeyboardNavigationCluster(); - method public final boolean isKeyboardNavigationSection(); method public boolean isLaidOut(); method public boolean isLayoutDirectionResolved(); method public boolean isLayoutRequested(); @@ -47179,7 +47353,7 @@ package android.view { method public boolean isVerticalFadingEdgeEnabled(); method public boolean isVerticalScrollBarEnabled(); method public void jumpDrawablesToCurrentState(); - method public android.view.View keyboardNavigationGroupSearch(int, android.view.View, int); + method public android.view.View keyboardNavigationClusterSearch(android.view.View, int); method public void layout(int, int, int, int); method public final void measure(int, int); method protected static int[] mergeDrawableStates(int[], int[]); @@ -47314,6 +47488,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); @@ -47329,7 +47504,6 @@ package android.view { method public void setImportantForAccessibility(int); method public void setKeepScreenOn(boolean); method public void setKeyboardNavigationCluster(boolean); - method public void setKeyboardNavigationSection(boolean); method public void setLabelFor(int); method public void setLayerPaint(android.graphics.Paint); method public void setLayerType(int, android.graphics.Paint); @@ -47347,7 +47521,6 @@ package android.view { method public void setNextFocusLeftId(int); method public void setNextFocusRightId(int); method public void setNextFocusUpId(int); - method public void setNextSectionForwardId(int); method public void setOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener); method public void setOnClickListener(android.view.View.OnClickListener); method public void setOnContextClickListener(android.view.View.OnContextClickListener); @@ -47396,7 +47569,6 @@ package android.view { method public void setTag(int, java.lang.Object); method public void setTextAlignment(int); method public void setTextDirection(int); - method public final deprecated void setTooltip(java.lang.CharSequence); method public final void setTooltipText(java.lang.CharSequence); method public final void setTop(int); method public void setTouchDelegate(android.view.TouchDelegate); @@ -47454,8 +47626,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; @@ -47474,8 +47648,6 @@ package android.view { field public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1 field public static final int INVISIBLE = 4; // 0x4 field public static final int KEEP_SCREEN_ON = 67108864; // 0x4000000 - field public static final int KEYBOARD_NAVIGATION_GROUP_CLUSTER = 1; // 0x1 - field public static final int KEYBOARD_NAVIGATION_GROUP_SECTION = 2; // 0x2 field public static final int LAYER_TYPE_HARDWARE = 2; // 0x2 field public static final int LAYER_TYPE_NONE = 0; // 0x0 field public static final int LAYER_TYPE_SOFTWARE = 1; // 0x1 @@ -47487,6 +47659,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 @@ -47994,7 +48167,7 @@ package android.view { method public abstract boolean isLayoutRequested(); method public abstract boolean isTextAlignmentResolved(); method public abstract boolean isTextDirectionResolved(); - method public abstract android.view.View keyboardNavigationGroupSearch(int, android.view.View, int); + method public abstract android.view.View keyboardNavigationClusterSearch(android.view.View, int); method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int); method public abstract boolean onNestedFling(android.view.View, float, float, boolean); method public abstract boolean onNestedPreFling(android.view.View, float, float); @@ -49784,6 +49957,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 { @@ -53234,7 +53484,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(); @@ -53350,7 +53600,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); diff --git a/api/test-current.txt b/api/test-current.txt index 72b2a6244a3f..94b4a000ed98 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -203,6 +203,8 @@ package android { public static final class R.attr { ctor public R.attr(); + field public static final int __removed0 = 16844097; // 0x1010541 + field public static final int __removed1 = 16844099; // 0x1010543 field public static final int absListViewStyle = 16842858; // 0x101006a field public static final int accessibilityEventTypes = 16843648; // 0x1010380 field public static final int accessibilityFeedbackType = 16843650; // 0x1010382 @@ -758,7 +760,6 @@ package android { field public static final int keyboardLayout = 16843691; // 0x10103ab field public static final int keyboardMode = 16843341; // 0x101024d field public static final int keyboardNavigationCluster = 16844096; // 0x1010540 - field public static final int keyboardNavigationSection = 16844097; // 0x1010541 field public static final int keycode = 16842949; // 0x10100c5 field public static final int killAfterRestore = 16843420; // 0x101029c field public static final int label = 16842753; // 0x1010001 @@ -908,7 +909,6 @@ package android { field public static final int nextFocusLeft = 16842977; // 0x10100e1 field public static final int nextFocusRight = 16842978; // 0x10100e2 field public static final int nextFocusUp = 16842979; // 0x10100e3 - field public static final int nextSectionForward = 16844099; // 0x1010543 field public static final int noHistory = 16843309; // 0x101022d field public static final int normalScreens = 16843397; // 0x1010285 field public static final int notificationTimeout = 16843651; // 0x1010383 @@ -1169,6 +1169,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 +1818,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 +3540,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 +3689,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); @@ -5594,6 +5594,18 @@ package android.app { field public static final int STYLE_SPINNER = 0; // 0x0 } + public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable { + ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent); + method public int describeContents(); + method public android.app.PendingIntent getUserAction(); + method public java.lang.CharSequence getUserActionTitle(); + method public java.lang.CharSequence getUserMessage(); + method public void showAsDialog(android.app.Activity); + method public void showAsNotification(android.content.Context); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR; + } + public final class RemoteAction implements android.os.Parcelable { ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener); method public android.app.RemoteAction clone(); @@ -9775,6 +9787,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 { @@ -13246,6 +13259,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); @@ -13266,6 +13280,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(); } @@ -13820,6 +13842,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 { @@ -17625,6 +17660,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(); @@ -17634,6 +17678,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); @@ -17653,9 +17699,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); @@ -18124,6 +18180,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(); @@ -18899,6 +18963,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(); @@ -19427,6 +19519,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(); @@ -23658,6 +23779,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"; @@ -23698,8 +23820,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"; @@ -23828,6 +23956,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 @@ -30567,14 +30696,22 @@ package android.os.storage { } public class StorageManager { + method public long getCacheQuotaBytes(); + method public long getCacheSizeBytes(); + method public long getExternalCacheQuotaBytes(); + method public long getExternalCacheSizeBytes(); method public java.lang.String getMountedObbPath(java.lang.String); method public android.os.storage.StorageVolume getPrimaryStorageVolume(); method public android.os.storage.StorageVolume getStorageVolume(java.io.File); method public java.util.List<android.os.storage.StorageVolume> getStorageVolumes(); + method public boolean isCacheBehaviorAtomic(java.io.File) throws java.io.IOException; + method public boolean isCacheBehaviorTombstone(java.io.File) throws java.io.IOException; method public boolean isEncrypted(java.io.File); method public boolean isObbMounted(java.lang.String); method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener); method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException; + method public void setCacheBehaviorAtomic(java.io.File, boolean) throws java.io.IOException; + method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException; method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener); field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE"; } @@ -33047,6 +33184,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"; @@ -35889,6 +36036,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(); @@ -35930,7 +36078,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(); @@ -36346,6 +36493,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); } @@ -36498,6 +36646,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 { @@ -38589,6 +38738,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(); @@ -40029,22 +40179,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); @@ -40060,13 +40194,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); @@ -40079,13 +40206,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...); @@ -42504,7 +42624,9 @@ package android.view { method public android.view.Display.Mode[] getSupportedModes(); method public deprecated float[] getSupportedRefreshRates(); method public deprecated int getWidth(); + method public boolean isHdr(); method public boolean isValid(); + method public boolean isWideColorGamut(); field public static final int DEFAULT_DISPLAY = 0; // 0x0 field public static final int FLAG_PRESENTATION = 8; // 0x8 field public static final int FLAG_PRIVATE = 4; // 0x4 @@ -42573,7 +42695,7 @@ package android.view { method public android.view.View findNearestTouchable(android.view.ViewGroup, int, int, int, int[]); method public final android.view.View findNextFocus(android.view.ViewGroup, android.view.View, int); method public android.view.View findNextFocusFromRect(android.view.ViewGroup, android.graphics.Rect, int); - method public android.view.View findNextKeyboardNavigationGroup(int, android.view.View, android.view.View, int); + method public android.view.View findNextKeyboardNavigationCluster(android.view.View, android.view.View, int); method public static android.view.FocusFinder getInstance(); } @@ -43873,7 +43995,7 @@ package android.view { method public void addChildrenForAccessibility(java.util.ArrayList<android.view.View>); method public void addFocusables(java.util.ArrayList<android.view.View>, int); method public void addFocusables(java.util.ArrayList<android.view.View>, int, int); - method public void addKeyboardNavigationGroups(int, java.util.Collection<android.view.View>, int); + method public void addKeyboardNavigationClusters(java.util.Collection<android.view.View>, int); method public void addOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener); method public void addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener); method public void addTouchables(java.util.ArrayList<android.view.View>); @@ -43995,6 +44117,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(); @@ -44037,7 +44160,6 @@ package android.view { method public int getNextFocusLeftId(); method public int getNextFocusRightId(); method public int getNextFocusUpId(); - method public int getNextSectionForwardId(); method public android.view.View.OnFocusChangeListener getOnFocusChangeListener(); method public android.view.ViewOutlineProvider getOutlineProvider(); method public int getOverScrollMode(); @@ -44081,7 +44203,6 @@ package android.view { method public java.lang.Object getTag(int); method public int getTextAlignment(); method public int getTextDirection(); - method public final deprecated java.lang.CharSequence getTooltip(); method public final java.lang.CharSequence getTooltipText(); method public android.view.View getTooltipView(); method public final int getTop(); @@ -44144,7 +44265,6 @@ package android.view { method public boolean isInLayout(); method public boolean isInTouchMode(); method public final boolean isKeyboardNavigationCluster(); - method public final boolean isKeyboardNavigationSection(); method public boolean isLaidOut(); method public boolean isLayoutDirectionResolved(); method public boolean isLayoutRequested(); @@ -44167,7 +44287,7 @@ package android.view { method public boolean isVerticalFadingEdgeEnabled(); method public boolean isVerticalScrollBarEnabled(); method public void jumpDrawablesToCurrentState(); - method public android.view.View keyboardNavigationGroupSearch(int, android.view.View, int); + method public android.view.View keyboardNavigationClusterSearch(android.view.View, int); method public void layout(int, int, int, int); method public final void measure(int, int); method protected static int[] mergeDrawableStates(int[], int[]); @@ -44302,6 +44422,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); @@ -44317,7 +44438,6 @@ package android.view { method public void setImportantForAccessibility(int); method public void setKeepScreenOn(boolean); method public void setKeyboardNavigationCluster(boolean); - method public void setKeyboardNavigationSection(boolean); method public void setLabelFor(int); method public void setLayerPaint(android.graphics.Paint); method public void setLayerType(int, android.graphics.Paint); @@ -44335,7 +44455,6 @@ package android.view { method public void setNextFocusLeftId(int); method public void setNextFocusRightId(int); method public void setNextFocusUpId(int); - method public void setNextSectionForwardId(int); method public void setOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener); method public void setOnClickListener(android.view.View.OnClickListener); method public void setOnContextClickListener(android.view.View.OnContextClickListener); @@ -44384,7 +44503,6 @@ package android.view { method public void setTag(int, java.lang.Object); method public void setTextAlignment(int); method public void setTextDirection(int); - method public final deprecated void setTooltip(java.lang.CharSequence); method public final void setTooltipText(java.lang.CharSequence); method public final void setTop(int); method public void setTouchDelegate(android.view.TouchDelegate); @@ -44442,8 +44560,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; @@ -44462,8 +44582,6 @@ package android.view { field public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1 field public static final int INVISIBLE = 4; // 0x4 field public static final int KEEP_SCREEN_ON = 67108864; // 0x4000000 - field public static final int KEYBOARD_NAVIGATION_GROUP_CLUSTER = 1; // 0x1 - field public static final int KEYBOARD_NAVIGATION_GROUP_SECTION = 2; // 0x2 field public static final int LAYER_TYPE_HARDWARE = 2; // 0x2 field public static final int LAYER_TYPE_NONE = 0; // 0x0 field public static final int LAYER_TYPE_SOFTWARE = 1; // 0x1 @@ -44475,6 +44593,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 @@ -44986,7 +45105,7 @@ package android.view { method public abstract boolean isLayoutRequested(); method public abstract boolean isTextAlignmentResolved(); method public abstract boolean isTextDirectionResolved(); - method public abstract android.view.View keyboardNavigationGroupSearch(int, android.view.View, int); + method public abstract android.view.View keyboardNavigationClusterSearch(android.view.View, int); method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int); method public abstract boolean onNestedFling(android.view.View, float, float, boolean); method public abstract boolean onNestedPreFling(android.view.View, float, float); @@ -46775,6 +46894,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 { @@ -49871,7 +50067,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(); @@ -49987,7 +50183,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); 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/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 556d7add513f..8f169c85d676 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(); } } 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..d674bfe23231 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); 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/RecoverableSecurityException.java b/core/java/android/app/RecoverableSecurityException.java new file mode 100644 index 000000000000..1f015a607be8 --- /dev/null +++ b/core/java/android/app/RecoverableSecurityException.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.content.Context; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * Specialization of {@link SecurityException} that contains additional + * information about how to involve the end user to recover from the exception. + * <p> + * This exception is only appropriate where there is a concrete action the user + * can take to recover and make forward progress, such as confirming or entering + * authentication credentials. + * <p class="note"> + * Note: legacy code that receives this exception may treat it as a general + * {@link SecurityException}, and thus there is no guarantee that the messages + * contained will be shown to the end user. + * </p> + */ +public final class RecoverableSecurityException extends SecurityException implements Parcelable { + private static final String TAG = "RecoverableSecurityException"; + + private final CharSequence mUserMessage; + private final CharSequence mUserActionTitle; + private final PendingIntent mUserAction; + + /** {@hide} */ + public RecoverableSecurityException(Parcel in) { + this(new SecurityException(in.readString()), in.readCharSequence(), in.readCharSequence(), + PendingIntent.CREATOR.createFromParcel(in)); + } + + /** + * Create an instance ready to be thrown. + * + * @param cause original cause with details designed for engineering + * audiences. + * @param userMessage short message describing the issue for end user + * audiences, which may be shown in a notification or dialog. + * This should be less than 64 characters. For example: <em>PIN + * required to access Document.pdf</em> + * @param userActionTitle short title describing the primary action. This + * should be less than 24 characters. For example: <em>Enter + * PIN</em> + * @param userAction primary action that will initiate the recovery. This + * must launch an activity that is expected to set + * {@link Activity#setResult(int)} before finishing to + * communicate the final status of the recovery. For example, + * apps that observe {@link Activity#RESULT_OK} may choose to + * immediately retry their operation. + */ + public RecoverableSecurityException(Throwable cause, CharSequence userMessage, + CharSequence userActionTitle, PendingIntent userAction) { + super(cause.getMessage()); + mUserMessage = Preconditions.checkNotNull(userMessage); + mUserActionTitle = Preconditions.checkNotNull(userActionTitle); + mUserAction = Preconditions.checkNotNull(userAction); + } + + /** + * Return short message describing the issue for end user audiences, which + * may be shown in a notification or dialog. + */ + public CharSequence getUserMessage() { + return mUserMessage; + } + + /** + * Return short title describing the primary action. + */ + public CharSequence getUserActionTitle() { + return mUserActionTitle; + } + + /** + * Return primary action that will initiate the recovery. + */ + public PendingIntent getUserAction() { + return mUserAction; + } + + /** + * Convenience method that will show a very simple notification populated + * with the details from this exception. + * <p> + * If you want more flexibility over retrying your original operation once + * the user action has finished, consider presenting your own UI that uses + * {@link Activity#startIntentSenderForResult} to launch the + * {@link PendingIntent#getIntentSender()} from {@link #getUserAction()} + * when requested. If the result of that activity is + * {@link Activity#RESULT_OK}, you should consider retrying. + * <p> + * This method will only display the most recent exception from any single + * remote UID; notifications from older exceptions will always be replaced. + */ + public void showAsNotification(Context context) { + final Notification.Builder builder = new Notification.Builder(context) + .setSmallIcon(com.android.internal.R.drawable.ic_print_error) + .setContentTitle(mUserActionTitle) + .setContentText(mUserMessage) + .setContentIntent(mUserAction) + .setCategory(Notification.CATEGORY_ERROR); + + final NotificationManager nm = context.getSystemService(NotificationManager.class); + nm.notify(TAG, mUserAction.getCreatorUid(), builder.build()); + } + + /** + * Convenience method that will show a very simple dialog populated with the + * details from this exception. + * <p> + * If you want more flexibility over retrying your original operation once + * the user action has finished, consider presenting your own UI that uses + * {@link Activity#startIntentSenderForResult} to launch the + * {@link PendingIntent#getIntentSender()} from {@link #getUserAction()} + * when requested. If the result of that activity is + * {@link Activity#RESULT_OK}, you should consider retrying. + * <p> + * This method will only display the most recent exception from any single + * remote UID; dialogs from older exceptions will always be replaced. + */ + public void showAsDialog(Activity activity) { + final LocalDialog dialog = new LocalDialog(); + final Bundle args = new Bundle(); + args.putParcelable(TAG, this); + dialog.setArguments(args); + + final String tag = TAG + "_" + mUserAction.getCreatorUid(); + final FragmentManager fm = activity.getFragmentManager(); + final FragmentTransaction ft = fm.beginTransaction(); + final Fragment old = fm.findFragmentByTag(tag); + if (old != null) { + ft.remove(old); + } + ft.add(dialog, tag); + ft.commitAllowingStateLoss(); + } + + /** {@hide} */ + public static class LocalDialog extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final RecoverableSecurityException e = getArguments().getParcelable(TAG); + return new AlertDialog.Builder(getActivity()) + .setMessage(e.mUserMessage) + .setPositiveButton(e.mUserActionTitle, (dialog, which) -> { + try { + e.mUserAction.send(); + } catch (PendingIntent.CanceledException ignored) { + } + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(getMessage()); + dest.writeCharSequence(mUserMessage); + dest.writeCharSequence(mUserActionTitle); + mUserAction.writeToParcel(dest, flags); + } + + public static final Creator<RecoverableSecurityException> CREATOR = + new Creator<RecoverableSecurityException>() { + @Override + public RecoverableSecurityException createFromParcel(Parcel source) { + return new RecoverableSecurityException(source); + } + + @Override + public RecoverableSecurityException[] newArray(int size) { + return new RecoverableSecurityException[size]; + } + }; +} diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 0da89eb2bd98..aa56be6f36cd 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3735,13 +3735,13 @@ public class DevicePolicyManager { } /** - * Called by a device owner to set whether auto time is required. If auto time is required the - * user cannot set the date and time, but has to use network date and time. + * Called by a device or profile owner to set whether auto time is required. If auto time is + * required, no user will be able set the date and time and network date and time will be used. * <p> * Note: if auto time is required the user can still manually set the time zone. * <p> - * The calling device admin must be a device owner. If it is not, a security exception will be - * thrown. + * The calling device admin must be a device or profile owner. If it is not, a security + * exception will be thrown. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param required Whether auto time is set required or not. 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 8cc9a3aecb70..90f08cd8bc81 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3966,6 +3966,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 @@ -4842,6 +4848,10 @@ public class Intent implements Parcelable, Cloneable { * or not running) apps, regardless of whether that would be done by default. By * default they will only receive broadcasts if the broadcast has specified an * explicit component or package name. + * + * NOTE: dumpstate uses this flag numerically, so when its value is changed + * the broadcast code there must also be changed to match. + * * @hide */ public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000; diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 4bd091dae77e..4a94688315e8 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. @@ -369,6 +373,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. @@ -926,10 +937,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 +971,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: 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/PackageParser.java b/core/java/android/content/pm/PackageParser.java index d8d7abe6360a..43ebf46f4996 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; } @@ -4158,16 +4167,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 +4526,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 +4825,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/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/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/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index b03c9070dc44..35a266b7ab41 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -291,4 +291,6 @@ interface IStorageManager { void fstrim(int flags) = 72; AppFuseMount mountProxyFileDescriptorBridge() = 73; ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode) = 74; + long getCacheQuotaBytes(String volumeUuid, int uid) = 75; + long getCacheSizeBytes(String volumeUuid, int uid) = 76; } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index c6ff47694bab..626d6f4698ca 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -24,27 +24,32 @@ import android.annotation.SdkConstant; import android.app.ActivityThread; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.IPackageMoveObserver; import android.content.pm.PackageManager; import android.os.Binder; import android.os.Environment; import android.os.FileUtils; import android.os.Handler; -import android.os.ProxyFileDescriptorCallback; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; +import android.os.ProxyFileDescriptorCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; + import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.AppFuseMount; @@ -60,6 +65,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.lang.ref.WeakReference; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -1396,6 +1402,222 @@ public class StorageManager { } } + /** + * Return quota size in bytes for cached data belonging to the calling app. + * <p> + * If your app goes above this quota, your cached files will be some of the + * first to be deleted when additional disk space is needed. Conversely, if + * your app stays under this quota, your cached files will be some of the + * last to be deleted when additional disk space is needed. + * <p> + * This quota may change over time depending on how frequently the user + * interacts with your app, and depending on how much disk space is used. + * <p> + * Cached data tracked by this method always includes + * {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}, and + * it also includes {@link Context#getExternalCacheDir()} if the primary + * shared/external storage is hosted on the same storage device as your + * private data. + * <p class="note"> + * Note: if your app uses the {@code android:sharedUserId} manifest feature, + * then cached data for all packages in your shared UID is tracked together + * as a single unit. + * </p> + * + * @see #getCacheQuotaBytes() + * @see #getCacheSizeBytes() + * @see #getExternalCacheQuotaBytes() + * @see #getExternalCacheSizeBytes() + */ + public long getCacheQuotaBytes() { + try { + final ApplicationInfo app = mContext.getApplicationInfo(); + return mStorageManager.getCacheQuotaBytes(app.volumeUuid, app.uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return total size in bytes of cached data belonging to the calling app. + * <p> + * Cached data tracked by this method always includes + * {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}, and + * it also includes {@link Context#getExternalCacheDir()} if the primary + * shared/external storage is hosted on the same storage device as your + * private data. + * <p class="note"> + * Note: if your app uses the {@code android:sharedUserId} manifest feature, + * then cached data for all packages in your shared UID is tracked together + * as a single unit. + * </p> + * + * @see #getCacheQuotaBytes() + * @see #getCacheSizeBytes() + * @see #getExternalCacheQuotaBytes() + * @see #getExternalCacheSizeBytes() + */ + public long getCacheSizeBytes() { + try { + final ApplicationInfo app = mContext.getApplicationInfo(); + return mStorageManager.getCacheSizeBytes(app.volumeUuid, app.uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return quota size in bytes for cached data on primary shared/external + * storage belonging to the calling app. + * <p> + * If primary shared/external storage is hosted on the same storage device + * as your private data, this method will return -1, since all data stored + * under {@link Context#getExternalCacheDir()} will be counted under + * {@link #getCacheQuotaBytes()}. + * <p class="note"> + * Note: if your app uses the {@code android:sharedUserId} manifest feature, + * then cached data for all packages in your shared UID is tracked together + * as a single unit. + * </p> + */ + public long getExternalCacheQuotaBytes() { + final ApplicationInfo app = mContext.getApplicationInfo(); + final String primaryUuid = getPrimaryStorageUuid(); + if (Objects.equals(app.volumeUuid, primaryUuid)) { + return -1; + } + try { + return mStorageManager.getCacheQuotaBytes(primaryUuid, app.uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return total size in bytes of cached data on primary shared/external + * storage belonging to the calling app. + * <p> + * If primary shared/external storage is hosted on the same storage device + * as your private data, this method will return -1, since all data stored + * under {@link Context#getExternalCacheDir()} will be counted under + * {@link #getCacheQuotaBytes()}. + * <p class="note"> + * Note: if your app uses the {@code android:sharedUserId} manifest feature, + * then cached data for all packages in your shared UID is tracked together + * as a single unit. + * </p> + */ + public long getExternalCacheSizeBytes() { + final ApplicationInfo app = mContext.getApplicationInfo(); + final String primaryUuid = getPrimaryStorageUuid(); + if (Objects.equals(app.volumeUuid, primaryUuid)) { + return -1; + } + try { + return mStorageManager.getCacheSizeBytes(primaryUuid, app.uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static final String XATTR_ATOMIC = "user.atomic"; + private static final String XATTR_TOMBSTONE = "user.tombstone"; + + /** {@hide} */ + private static void setCacheBehavior(File path, String name, boolean enabled) + throws IOException { + if (!path.isDirectory()) { + throw new IOException("Cache behavior can only be set on directories"); + } + if (enabled) { + try { + Os.setxattr(path.getAbsolutePath(), name, + "1".getBytes(StandardCharsets.UTF_8), 0); + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } else { + try { + Os.removexattr(path.getAbsolutePath(), name); + } catch (ErrnoException e) { + if (e.errno != OsConstants.ENODATA) { + throw e.rethrowAsIOException(); + } + } + } + } + + /** {@hide} */ + private static boolean isCacheBehavior(File path, String name) throws IOException { + try { + Os.getxattr(path.getAbsolutePath(), name); + return true; + } catch (ErrnoException e) { + if (e.errno != OsConstants.ENODATA) { + throw e.rethrowAsIOException(); + } else { + return false; + } + } + } + + /** + * Enable or disable special cache behavior that treats this directory and + * its contents as an atomic unit. + * <p> + * When enabled and this directory is considered for automatic deletion by + * the OS, all contained files will either be deleted together, or not at + * all. This is useful when you have a directory that contains several + * related metadata files that depend on each other, such as movie file and + * a subtitle file. + * <p> + * When enabled, the <em>newest</em> {@link File#lastModified()} value of + * any contained files is considered the modified time of the entire + * directory. + * <p> + * This behavior can only be set on a directory, and it applies recursively + * to all contained files and directories. + */ + public void setCacheBehaviorAtomic(File path, boolean atomic) throws IOException { + setCacheBehavior(path, XATTR_ATOMIC, atomic); + } + + /** + * Read the current value set by + * {@link #setCacheBehaviorAtomic(File, boolean)}. + */ + public boolean isCacheBehaviorAtomic(File path) throws IOException { + return isCacheBehavior(path, XATTR_ATOMIC); + } + + /** + * Enable or disable special cache behavior that leaves deleted cache files + * intact as tombstones. + * <p> + * When enabled and a file contained in this directory is automatically + * deleted by the OS, the file will be truncated to have a length of 0 bytes + * instead of being fully deleted. This is useful if you need to distinguish + * between a file that was deleted versus one that never existed. + * <p> + * This behavior can only be set on a directory, and it applies recursively + * to all contained files and directories. + * <p class="note"> + * Note: this behavior is ignored completely if the user explicitly requests + * that all cached data be cleared. + * </p> + */ + public void setCacheBehaviorTombstone(File path, boolean tombstone) throws IOException { + setCacheBehavior(path, XATTR_TOMBSTONE, tombstone); + } + + /** + * Read the current value set by + * {@link #setCacheBehaviorTombstone(File, boolean)}. + */ + public boolean isCacheBehaviorTombstone(File path) throws IOException { + return isCacheBehavior(path, XATTR_TOMBSTONE); + } + private final Object mFuseAppLoopLock = new Object(); @GuardedBy("mFuseAppLoopLock") diff --git a/core/java/android/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 83aa3f7f1c1c..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. @@ -10016,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/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 694837e53c73..d9306897d876 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1166,11 +1166,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 +1201,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 +1234,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 +1259,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 +1283,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 +1306,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 +1353,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 +1384,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 +1465,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 +1492,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 +1566,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 +1592,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/Display.java b/core/java/android/view/Display.java index b37ea8ebeaa0..105cc47c88aa 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -292,7 +292,7 @@ public final class Display { public static final int STATE_VR = 5; /* The color mode constants defined below must be kept in sync with the ones in - * system/graphics.h */ + * system/core/include/system/graphics-base.h */ /** * Display color mode: The current color mode is unknown or invalid. @@ -306,11 +306,24 @@ public final class Display { */ public static final int COLOR_MODE_DEFAULT = 0; - /** - * Display color mode: SRGB - * @hide - */ + /** @hide */ + public static final int COLOR_MODE_BT601_625 = 1; + /** @hide */ + public static final int COLOR_MODE_BT601_625_UNADJUSTED = 2; + /** @hide */ + public static final int COLOR_MODE_BT601_525 = 3; + /** @hide */ + public static final int COLOR_MODE_BT601_525_UNADJUSTED = 4; + /** @hide */ + public static final int COLOR_MODE_BT709 = 5; + /** @hide */ + public static final int COLOR_MODE_DCI_P3 = 6; + /** @hide */ public static final int COLOR_MODE_SRGB = 7; + /** @hide */ + public static final int COLOR_MODE_ADOBE_RGB = 8; + /** @hide */ + public static final int COLOR_MODE_DISPLAY_P3 = 9; /** * Internal method to create a display. @@ -745,6 +758,8 @@ public final class Display { /** * Returns the display's HDR capabilities. + * + * @see #isHdr() */ public HdrCapabilities getHdrCapabilities() { synchronized (this) { @@ -754,6 +769,35 @@ public final class Display { } /** + * Returns whether this display supports any HDR type. + * + * @see #getHdrCapabilities() + * @see HdrCapabilities#getSupportedHdrTypes() + */ + public boolean isHdr() { + synchronized (this) { + updateDisplayInfoLocked(); + int[] types = mDisplayInfo.hdrCapabilities.getSupportedHdrTypes(); + return types != null && types.length > 0; + } + } + + /** + * Returns whether this display can be used to display wide color gamut content. + */ + public boolean isWideColorGamut() { + synchronized (this) { + updateDisplayInfoLocked(); + for (int colorMode : mDisplayInfo.supportedColorModes) { + if (colorMode == COLOR_MODE_DCI_P3 || colorMode > COLOR_MODE_SRGB) { + return true; + } + } + return false; + } + } + + /** * Gets the supported color modes of this device. * @hide */ diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java index a07a7ef600bf..41a13cf59bd8 100644 --- a/core/java/android/view/FocusFinder.java +++ b/core/java/android/view/FocusFinder.java @@ -16,16 +16,12 @@ package android.view; -import static android.view.View.KEYBOARD_NAVIGATION_GROUP_CLUSTER; -import static android.view.View.KEYBOARD_NAVIGATION_GROUP_SECTION; - import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; import android.util.ArrayMap; import android.util.SparseArray; import android.util.SparseBooleanArray; -import android.view.View.KeyboardNavigationGroupType; import java.util.ArrayList; import java.util.Collections; @@ -110,31 +106,28 @@ public class FocusFinder { } /** - * Find the root of the next keyboard navigation group after the current one. The group type can - * be either a cluster or a section. - * @param groupType Type of the keyboard navigation group + * Find the root of the next keyboard navigation cluster after the current one. * @param root The view tree to look inside. Cannot be null - * @param currentGroup The starting point of the search. Null means the default group + * @param currentCluster The starting point of the search. Null means the default cluster * @param direction Direction to look - * @return The next group, or null if none exists + * @return The next cluster, or null if none exists */ - public View findNextKeyboardNavigationGroup( - @KeyboardNavigationGroupType int groupType, + public View findNextKeyboardNavigationCluster( @NonNull View root, - @Nullable View currentGroup, + @Nullable View currentCluster, int direction) { View next = null; - final ArrayList<View> groups = mTempList; + final ArrayList<View> clusters = mTempList; try { - groups.clear(); - root.addKeyboardNavigationGroups(groupType, groups, direction); - if (!groups.isEmpty()) { - next = findNextKeyboardNavigationGroup( - groupType, root, currentGroup, groups, direction); + clusters.clear(); + root.addKeyboardNavigationClusters(clusters, direction); + if (!clusters.isEmpty()) { + next = findNextKeyboardNavigationCluster( + root, currentCluster, clusters, direction); } } finally { - groups.clear(); + clusters.clear(); } return next; } @@ -207,25 +200,22 @@ public class FocusFinder { } } - private View findNextKeyboardNavigationGroup( - @KeyboardNavigationGroupType int groupType, + private View findNextKeyboardNavigationCluster( View root, - View currentGroup, - List<View> groups, + View currentCluster, + List<View> clusters, int direction) { - final int count = groups.size(); + final int count = clusters.size(); switch (direction) { case View.FOCUS_FORWARD: case View.FOCUS_DOWN: case View.FOCUS_RIGHT: - return getNextKeyboardNavigationGroup( - groupType, root, currentGroup, groups, count); + return getNextKeyboardNavigationCluster(root, currentCluster, clusters, count); case View.FOCUS_BACKWARD: case View.FOCUS_UP: case View.FOCUS_LEFT: - return getPreviousKeyboardNavigationGroup( - groupType, root, currentGroup, groups, count); + return getPreviousKeyboardNavigationCluster(root, currentCluster, clusters, count); default: throw new IllegalArgumentException("Unknown direction: " + direction); } @@ -331,70 +321,50 @@ public class FocusFinder { return null; } - private static View getNextKeyboardNavigationGroup( - @KeyboardNavigationGroupType int groupType, + private static View getNextKeyboardNavigationCluster( View root, - View currentGroup, - List<View> groups, + View currentCluster, + List<View> clusters, int count) { - if (currentGroup == null) { - // The current group is the default one. - // The next group after the default one is the first one. - // Note that the caller guarantees that 'group' is not empty. - return groups.get(0); + if (currentCluster == null) { + // The current cluster is the default one. + // The next cluster after the default one is the first one. + // Note that the caller guarantees that 'clusters' is not empty. + return clusters.get(0); } - final int position = groups.lastIndexOf(currentGroup); + final int position = clusters.lastIndexOf(currentCluster); if (position >= 0 && position + 1 < count) { - // Return the next non-default group if we can find it. - return groups.get(position + 1); - } - - switch (groupType) { - case KEYBOARD_NAVIGATION_GROUP_CLUSTER: - // The current cluster is the last one. The next one is the default one, i.e. the - // root. - return root; - case KEYBOARD_NAVIGATION_GROUP_SECTION: - // There is no "default section", hence returning the first one. - return groups.get(0); - default: - throw new IllegalArgumentException( - "Unknown keyboard navigation group type: " + groupType); + // Return the next non-default cluster if we can find it. + return clusters.get(position + 1); } + + // The current cluster is the last one. The next one is the default one, i.e. the + // root. + return root; } - private static View getPreviousKeyboardNavigationGroup( - @KeyboardNavigationGroupType int groupType, + private static View getPreviousKeyboardNavigationCluster( View root, - View currentGroup, - List<View> groups, + View currentCluster, + List<View> clusters, int count) { - if (currentGroup == null) { - // The current group is the default one. - // The previous group before the default one is the last one. - // Note that the caller guarantees that 'groups' is not empty. - return groups.get(count - 1); + if (currentCluster == null) { + // The current cluster is the default one. + // The previous cluster before the default one is the last one. + // Note that the caller guarantees that 'clusters' is not empty. + return clusters.get(count - 1); } - final int position = groups.indexOf(currentGroup); + final int position = clusters.indexOf(currentCluster); if (position > 0) { - // Return the previous non-default group if we can find it. - return groups.get(position - 1); - } - - switch (groupType) { - case KEYBOARD_NAVIGATION_GROUP_CLUSTER: - // The current cluster is the first one. The previous one is the default one, i.e. - // the root. - return root; - case KEYBOARD_NAVIGATION_GROUP_SECTION: - // There is no "default section", hence returning the last one. - return groups.get(count - 1); - default: - throw new IllegalArgumentException( - "Unknown keyboard navigation group type: " + groupType); + // Return the previous non-default cluster if we can find it. + return clusters.get(position - 1); } + + // The current cluster is the first one. The previous one is the default one, i.e. + // the root. + return root; } /** diff --git a/core/java/android/view/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 26e311c6a921..3bd67c77401c 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) @@ -1252,14 +1269,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @Retention(RetentionPolicy.SOURCE) public @interface FocusRealDirection {} // Like @FocusDirection, but without forward/backward - /** @hide */ - @IntDef({ - KEYBOARD_NAVIGATION_GROUP_CLUSTER, - KEYBOARD_NAVIGATION_GROUP_SECTION - }) - @Retention(RetentionPolicy.SOURCE) - public @interface KeyboardNavigationGroupType {} - /** * Use with {@link #focusSearch(int)}. Move focus to the previous selectable * item. @@ -1293,18 +1302,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public static final int FOCUS_DOWN = 0x00000082; /** - * Use with {@link #keyboardNavigationGroupSearch(int, View, int)}. Search for a keyboard - * navigation cluster. - */ - public static final int KEYBOARD_NAVIGATION_GROUP_CLUSTER = 1; - - /** - * Use with {@link #keyboardNavigationGroupSearch(int, View, int)}. Search for a keyboard - * navigation section. - */ - public static final int KEYBOARD_NAVIGATION_GROUP_SECTION = 2; - - /** * Bits of {@link #getMeasuredWidthAndState()} and * {@link #getMeasuredWidthAndState()} that provide the actual measured size. */ @@ -2500,7 +2497,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 1 PFLAG3_SCROLL_INDICATOR_END * 1 PFLAG3_ASSIST_BLOCKED * 1 PFLAG3_CLUSTER - * 1 PFLAG3_SECTION + * x * NO LONGER NEEDED, SHOULD BE REUSED * * 1 PFLAG3_FINGER_DOWN * 1 PFLAG3_FOCUSED_BY_DEFAULT * xxxx * NO LONGER NEEDED, SHOULD BE REUSED * @@ -2710,14 +2707,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static final int PFLAG3_CLUSTER = 0x8000; /** - * Flag indicating that the view is a root of a keyboard navigation section. - * - * @see #isKeyboardNavigationSection() - * @see #setKeyboardNavigationSection(boolean) - */ - private static final int PFLAG3_SECTION = 0x10000; - - /** * Indicates that the user is currently touching the screen. * Currently used for the tooltip positioning only. */ @@ -3807,11 +3796,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ int mNextClusterForwardId = View.NO_ID; - /** - * User-specified next keyboard navigation section. - */ - int mNextSectionForwardId = View.NO_ID; - private CheckForLongPress mPendingCheckForLongPress; private CheckForTap mPendingCheckForTap = null; private PerformClick mPerformClick; @@ -4169,7 +4153,7 @@ public 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) | @@ -4355,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); @@ -4467,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; @@ -4622,9 +4610,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case R.styleable.View_nextClusterForward: mNextClusterForwardId = a.getResourceId(attr, View.NO_ID); break; - case R.styleable.View_nextSectionForward: - mNextSectionForwardId = a.getResourceId(attr, View.NO_ID); - break; case R.styleable.View_minWidth: mMinWidth = a.getDimensionPixelSize(attr, 0); break; @@ -4769,11 +4754,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setKeyboardNavigationCluster(a.getBoolean(attr, true)); } break; - case R.styleable.View_keyboardNavigationSection: - if (a.peekValue(attr) != null) { - setKeyboardNavigationSection(a.getBoolean(attr, true)); - } - break; case R.styleable.View_focusedByDefault: if (a.peekValue(attr) != null) { setFocusedByDefault(a.getBoolean(attr, true)); @@ -5047,7 +5027,7 @@ 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' : '.'); @@ -8043,28 +8023,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Gets the id of the root of the next keyboard navigation section. - * @return The next keyboard navigation section ID, or {@link #NO_ID} if the framework should - * decide automatically. - * - * @attr ref android.R.styleable#View_nextSectionForward - */ - public int getNextSectionForwardId() { - return mNextSectionForwardId; - } - - /** - * Sets the id of the view to use as the root of the next keyboard navigation section. - * @param nextSectionForwardId The next section ID, or {@link #NO_ID} if the framework should - * decide automatically. - * - * @attr ref android.R.styleable#View_nextSectionForward - */ - public void setNextSectionForwardId(int nextSectionForwardId) { - mNextSectionForwardId = nextSectionForwardId; - } - - /** * Returns the visibility of this view and all of its ancestors * * @return True if this view and all of its ancestors are {@link #VISIBLE} @@ -8516,20 +8474,39 @@ 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); } /** @@ -9119,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; } /** @@ -9186,49 +9178,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Returns whether this View is a root of a keyboard navigation section. - * - * @return True if this view is a root of a section, or false otherwise. - * @attr ref android.R.styleable#View_keyboardNavigationSection - */ - @ViewDebug.ExportedProperty(category = "keyboardNavigationSection") - public final boolean isKeyboardNavigationSection() { - return (mPrivateFlags3 & PFLAG3_SECTION) != 0; - } - - /** - * Set whether this view is a root of a keyboard navigation section. - * - * @param isSection If true, this view is a root of a section. - * - * @attr ref android.R.styleable#View_keyboardNavigationSection - */ - public void setKeyboardNavigationSection(boolean isSection) { - if (isSection) { - mPrivateFlags3 |= PFLAG3_SECTION; - } else { - mPrivateFlags3 &= ~PFLAG3_SECTION; - } - } - - final boolean isKeyboardNavigationGroupOfType(@KeyboardNavigationGroupType int groupType) { - switch (groupType) { - case KEYBOARD_NAVIGATION_GROUP_CLUSTER: - return isKeyboardNavigationCluster(); - case KEYBOARD_NAVIGATION_GROUP_SECTION: - return isKeyboardNavigationSection(); - default: - throw new IllegalArgumentException( - "Unknown keyboard navigation group type: " + groupType); - } - } - - /** * Returns whether this View should receive focus when the focus is restored for the view * hierarchy containing this view. * <p> * Focus gets restored for a view hierarchy when the root of the hierarchy gets added to a - * window or serves as a target of cluster or section navigation. + * window or serves as a target of cluster navigation. * * @see #restoreDefaultFocus(int) * @@ -9245,7 +9199,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * hierarchy containing this view. * <p> * Focus gets restored for a view hierarchy when the root of the hierarchy gets added to a - * window or serves as a target of cluster or section navigation. + * window or serves as a target of cluster navigation. * * @param isFocusedByDefault {@code true} to set this view as the default-focus view, * {@code false} otherwise. @@ -9284,35 +9238,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Find the nearest keyboard navigation group in the specified direction. The group type can be - * either a cluster or a section. - * This does not actually give focus to that group. + * Find the nearest keyboard navigation cluster in the specified direction. + * This does not actually give focus to that cluster. * - * @param groupType Type of the keyboard navigation group - * @param currentGroup The starting point of the search. Null means the current group is not - * found yet + * @param currentCluster The starting point of the search. Null means the current cluster is not + * found yet * @param direction Direction to look * - * @return The nearest keyboard navigation group in the specified direction, or null if none + * @return The nearest keyboard navigation cluster in the specified direction, or null if none * can be found */ - public View keyboardNavigationGroupSearch( - @KeyboardNavigationGroupType int groupType, View currentGroup, int direction) { - if (isKeyboardNavigationGroupOfType(groupType)) { - currentGroup = this; + public View keyboardNavigationClusterSearch(View currentCluster, int direction) { + if (isKeyboardNavigationCluster()) { + currentCluster = this; } - if (isRootNamespace() - || (groupType == KEYBOARD_NAVIGATION_GROUP_SECTION - && isKeyboardNavigationCluster())) { + if (isRootNamespace()) { // Root namespace means we should consider ourselves the top of the // tree for group searching; otherwise we could be group searching // into other tabs. see LocalActivityManager and TabHost for more info. - // In addition, a cluster node works as a root for section searches. - return FocusFinder.getInstance().findNextKeyboardNavigationGroup( - groupType, this, currentGroup, direction); + return FocusFinder.getInstance().findNextKeyboardNavigationCluster( + this, currentCluster, direction); } else if (mParent != null) { - return mParent.keyboardNavigationGroupSearch( - groupType, currentGroup, direction); + return mParent.keyboardNavigationClusterSearch(currentCluster, direction); } return null; } @@ -9440,19 +9387,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Adds any keyboard navigation group roots that are descendants of this view (possibly - * including this view if it is a group root itself) to views. The group type can be either a - * cluster or a section. + * Adds any keyboard navigation cluster roots that are descendants of this view (possibly + * including this view if it is a cluster root itself) to views. * - * @param groupType Type of the keyboard navigation group - * @param views Keyboard navigation group roots found so far + * @param views Keyboard navigation cluster roots found so far * @param direction Direction to look */ - public void addKeyboardNavigationGroups( - @KeyboardNavigationGroupType int groupType, + public void addKeyboardNavigationClusters( @NonNull Collection<View> views, int direction) { - if (!(isKeyboardNavigationGroupOfType(groupType))) { + if (!(isKeyboardNavigationCluster())) { return; } views.add(this); @@ -9726,8 +9670,8 @@ 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; } @@ -12081,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 @@ -12231,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()) { @@ -16641,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(); - } } /** @@ -16817,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(); } } @@ -18160,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++; } @@ -24664,16 +24617,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * To be removed once the support library has stopped using it. - * - * @deprecated use {@link #setTooltipText} instead - */ - @Deprecated - public final void setTooltip(@Nullable CharSequence tooltipText) { - setTooltipText(tooltipText); - } - - /** * Returns the view's tooltip text. * * @return the tooltip text @@ -24683,17 +24626,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return mTooltipInfo != null ? mTooltipInfo.mTooltipText : null; } - /** - * To be removed once the support library has stopped using it. - * - * @deprecated use {@link #getTooltipText} instead - */ - @Deprecated - @Nullable - public final CharSequence getTooltip() { - return getTooltipText(); - } - private boolean showTooltip(int x, int y, boolean fromLongClick) { if (mAttachInfo == null) { return false; @@ -24806,6 +24738,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 d252d75856da..ba73c5f1be60 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -915,13 +915,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ @Override public View focusSearch(View focused, int direction) { - if (isRootNamespace() - || isKeyboardNavigationCluster() - && (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD)) { + if (isRootNamespace()) { // root namespace means we should consider ourselves the top of the // tree for focus searching; otherwise we could be focus searching // into other tabs. see LocalActivityManager and TabHost for more info. - // Cluster's root works same way for the forward and backward navigation. return FocusFinder.getInstance().findNextFocus(this, focused, direction); } else if (mParent != null) { return mParent.focusSearch(focused, direction); @@ -1136,12 +1133,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { - if (isKeyboardNavigationCluster() - && (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD) && !hasFocus()) { - // A cluster cannot be focus-entered from outside using forward/backward navigation. - return; - } - final int focusableCount = views.size(); final int descendantFocusability = getDescendantFocusability(); @@ -1175,11 +1166,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } @Override - public void addKeyboardNavigationGroups( - @KeyboardNavigationGroupType int groupType, Collection<View> views, int direction) { + public void addKeyboardNavigationClusters(Collection<View> views, int direction) { final int focusableCount = views.size(); - super.addKeyboardNavigationGroups(groupType, views, direction); + super.addKeyboardNavigationClusters(views, direction); if (focusableCount != views.size()) { // No need to look for groups inside a group. @@ -1195,14 +1185,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = 0; i < count; i++) { final View child = children[i]; - if (groupType == KEYBOARD_NAVIGATION_GROUP_SECTION - && child.isKeyboardNavigationCluster()) { - // When the current cluster is the default cluster, and we are searching for - // sections, ignore sections inside non-default clusters. - continue; - } if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { - child.addKeyboardNavigationGroups(groupType, views, direction); + child.addKeyboardNavigationClusters(views, direction); } } } @@ -3072,8 +3056,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final View[] children = mChildren; for (int i = index; i != end; i += increment) { View child = children[i]; - if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE - && !child.isKeyboardNavigationCluster()) { + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { if (child.requestFocus(direction, previouslyFocusedRect)) { return true; } @@ -3450,16 +3433,6 @@ 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/ViewParent.java b/core/java/android/view/ViewParent.java index c9277ca0bfa1..79b05cdb6e50 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -18,7 +18,6 @@ package android.view; import android.graphics.Rect; import android.os.Bundle; -import android.view.View.KeyboardNavigationGroupType; import android.view.accessibility.AccessibilityEvent; /** @@ -148,20 +147,17 @@ public interface ViewParent { public View focusSearch(View v, int direction); /** - * Find the nearest keyboard navigation group in the specified direction. The group type can be - * either a cluster or a section. - * This does not actually give focus to that group. + * Find the nearest keyboard navigation cluster in the specified direction. + * This does not actually give focus to that cluster. * - * @param groupType Type of the keyboard navigation group - * @param currentGroup The starting point of the search. Null means the current group is not - * found yet + * @param currentCluster The starting point of the search. Null means the current cluster is not + * found yet * @param direction Direction to look * - * @return The nearest keyboard navigation group in the specified direction, or null if none + * @return The nearest keyboard navigation cluster in the specified direction, or null if none * can be found */ - View keyboardNavigationGroupSearch( - @KeyboardNavigationGroupType int groupType, View currentGroup, int direction); + View keyboardNavigationClusterSearch(View currentCluster, int direction); /** * Change the z order of the child so it's on top of all other children. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index c0f2c377ee88..3cbe82e8b6b9 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -16,8 +16,6 @@ package android.view; -import static android.view.View.KEYBOARD_NAVIGATION_GROUP_CLUSTER; -import static android.view.View.KEYBOARD_NAVIGATION_GROUP_SECTION; import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER; import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; @@ -73,7 +71,6 @@ import android.util.TimeUtils; import android.util.TypedValue; import android.view.Surface.OutOfResourcesException; import android.view.View.AttachInfo; -import android.view.View.KeyboardNavigationGroupType; import android.view.View.MeasureSpec; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; @@ -4397,14 +4394,13 @@ public final class ViewRootImpl implements ViewParent, return false; } - private boolean performKeyboardGroupNavigation( - @KeyboardNavigationGroupType int groupType, int direction) { + private boolean performKeyboardGroupNavigation(int direction) { final View focused = mView.findFocus(); - final View group = focused != null - ? focused.keyboardNavigationGroupSearch(groupType, null, direction) - : keyboardNavigationGroupSearch(groupType, null, direction); + final View cluster = focused != null + ? focused.keyboardNavigationClusterSearch(null, direction) + : keyboardNavigationClusterSearch(null, direction); - if (group != null && group.restoreDefaultFocus(View.FOCUS_DOWN)) { + if (cluster != null && cluster.restoreDefaultFocus(View.FOCUS_DOWN)) { return true; } @@ -4424,32 +4420,15 @@ public final class ViewRootImpl implements ViewParent, } int groupNavigationDirection = 0; - @KeyboardNavigationGroupType int groupType = 0; if (event.getAction() == KeyEvent.ACTION_DOWN && event.isCtrlPressed()) { final int character = event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_CTRL_MASK); if (character == '+') { - groupType = KEYBOARD_NAVIGATION_GROUP_CLUSTER; groupNavigationDirection = View.FOCUS_FORWARD; } if (character == '_') { - groupType = KEYBOARD_NAVIGATION_GROUP_CLUSTER; - groupNavigationDirection = View.FOCUS_BACKWARD; - } - } - - if (event.getAction() == KeyEvent.ACTION_DOWN && event.isAltPressed()) { - final int character = - event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_ALT_MASK); - if (character == '+') { - groupType = KEYBOARD_NAVIGATION_GROUP_SECTION; - groupNavigationDirection = View.FOCUS_FORWARD; - } - - if (character == '_') { - groupType = KEYBOARD_NAVIGATION_GROUP_SECTION; groupNavigationDirection = View.FOCUS_BACKWARD; } } @@ -4479,7 +4458,7 @@ public final class ViewRootImpl implements ViewParent, // Handle automatic focus changes. if (event.getAction() == KeyEvent.ACTION_DOWN) { if (groupNavigationDirection != 0) { - if (performKeyboardGroupNavigation(groupType, groupNavigationDirection)) { + if (performKeyboardGroupNavigation(groupNavigationDirection)) { return FINISH_HANDLED; } } else { @@ -5910,11 +5889,10 @@ public final class ViewRootImpl implements ViewParent, * {@inheritDoc} */ @Override - public View keyboardNavigationGroupSearch( - @KeyboardNavigationGroupType int groupType, View currentGroup, int direction) { + public View keyboardNavigationClusterSearch(View currentCluster, int direction) { checkThread(); - return FocusFinder.getInstance().findNextKeyboardNavigationGroup(groupType, - mView, currentGroup, direction); + return FocusFinder.getInstance().findNextKeyboardNavigationCluster( + mView, currentCluster, direction); } public void debug() { diff --git a/core/java/android/view/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/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java index 716997f815dc..c08cd7284876 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java +++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java @@ -1080,22 +1080,6 @@ public class InputMethodUtils { return enabledSubtypes; } - // At the initial boot, the settings for input methods are not set, - // so we need to enable IME in that case. - public void enableAllIMEsIfThereIsNoEnabledIME() { - if (TextUtils.isEmpty(getEnabledInputMethodsStr())) { - StringBuilder sb = new StringBuilder(); - final int N = mMethodList.size(); - for (int i = 0; i < N; i++) { - InputMethodInfo imi = mMethodList.get(i); - Slog.i(TAG, "Adding: " + imi.getId()); - if (i > 0) sb.append(':'); - sb.append(imi.getId()); - } - putEnabledInputMethodsStr(sb.toString()); - } - } - public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(), mInputMethodSplitter, diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java index 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/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/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 87c2b255115a..0789241c45d2 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2274,13 +2274,16 @@ <!-- Sets the padding, in pixels, of the end edge; see {@link android.R.attr#padding}. --> <attr name="paddingEnd" format="dimension" /> - <!-- Boolean that controls whether a view can take focus. By default the user can not - move focus to a view; by setting this attribute to true the view is - allowed to take focus. This value does not impact the behavior of + <!-- Controls whether a view can take focus. By default, this is "auto" which lets the + framework determine whether a user can move focus to a view. By setting this attribute + to true the view is allowed to take focus. By setting it to "false" the view will not + take focus. This value does not impact the behavior of directly calling {@link android.view.View#requestFocus}, which will always request focus regardless of this view. It only impacts where focus navigation will try to move focus. --> - <attr name="focusable" format="boolean" /> + <attr name="focusable" format="boolean|enum"> + <enum name="auto" value="0x00000010" /> + </attr> <!-- Boolean that controls whether a view can take focus while in touch mode. If this is true for a view, that view can gain focus when clicked on, and can keep @@ -2896,9 +2899,7 @@ See {@link android.view.View#setKeyboardNavigationCluster(boolean)}. --> <attr name="keyboardNavigationCluster" format="boolean" /> - <!-- Whether this view is a root of a keyboard navigation section. - See {@link android.view.View#setKeyboardNavigationSection(boolean)}. --> - <attr name="keyboardNavigationSection" format="boolean" /> + <attr name="__removed0" format="boolean" /> <!-- Defines the next keyboard navigation cluster. @@ -2907,12 +2908,7 @@ will result when the reference is accessed.--> <attr name="nextClusterForward" format="reference"/> - <!-- Defines the next keyboard navigation section. - - If the reference refers to a view that does not exist or is part - of a hierarchy that is invisible, a {@link java.lang.RuntimeException} - will result when the reference is accessed.--> - <attr name="nextSectionForward" format="reference"/> + <attr name="__removed1" format="reference"/> <!-- Whether this view is a default-focus view. Only one view per keyboard navigation cluster can have this attribute set to true. @@ -7919,7 +7915,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..64a64f79b4c3 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1159,18 +1159,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 +1237,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 +1824,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 +1916,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 +2041,8 @@ <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" /> </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..c36279c28fdb 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 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 060c59ed4a31..66dd1274632d 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2777,9 +2777,9 @@ <public name="paddingVertical" /> <public name="visibleToInstantApps" /> <public name="keyboardNavigationCluster" /> - <public name="keyboardNavigationSection" /> + <public name="__removed0" /> <public name="nextClusterForward" /> - <public name="nextSectionForward" /> + <public name="__removed1" /> <public name="textColorError" /> <public name="focusedByDefault" /> <public name="appCategory" /> @@ -2787,12 +2787,14 @@ <public name="supportsDismissingWindow" /> <public name="restartOnConfigChanges" /> <public name="certDigest" /> + <public name="splitName" /> </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..16356c73f79b 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" /> 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/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/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/Typeface.java b/graphics/java/android/graphics/Typeface.java index 0a349e91ad1e..4e863e37f363 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 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/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/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/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/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/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_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/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/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/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 93f72a8bdf78..f24e40b13578 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -72,6 +72,7 @@ public class CarStatusBar extends PhoneStatusBar implements SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener); registerPackageChangeReceivers(); + createBatteryController(); mCarBatteryController.startListening(); mConnectedDeviceSignalController.startListening(); } @@ -113,8 +114,7 @@ public class CarStatusBar extends PhoneStatusBar implements return statusBarView; } - @Override - protected BatteryController createBatteryController() { + private BatteryController createBatteryController() { mCarBatteryController = new CarBatteryController(mContext); mCarBatteryController.addBatteryViewHandler(this); return mCarBatteryController; 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 80ad9d2d780a..fdf7296f6922 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); @@ -989,10 +990,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - protected BatteryController createBatteryController() { - return new BatteryControllerImpl(mContext); - } - private void inflateShelf() { mNotificationShelf = (NotificationShelf) LayoutInflater.from(mContext).inflate( @@ -1183,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); @@ -1392,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()); @@ -4608,12 +4607,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/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index 1b73a3f53997..aa29e43be467 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -263,12 +263,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/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/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/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index f718fa11598e..bee1f9729eea 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -243,7 +243,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private PendingIntent mImeSwitchPendingIntent; private boolean mShowOngoingImeSwitcherForPhones; private boolean mNotificationShown; - private final boolean mImeSelectedOnBoot; static class SessionState { final ClientState client; @@ -566,7 +565,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - class ImmsBroadcastReceiver extends android.content.BroadcastReceiver { + class ImmsBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); @@ -587,6 +586,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Intent.EXTRA_SETTING_NEW_VALUE); restoreEnabledInputMethods(mContext, prevValue, newValue); } + } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { + synchronized (mMethodMap) { + resetStateIfCurrentLocaleChangedLocked(); + } } else { Slog.w(TAG, "Unexpected intent " + intent); } @@ -845,9 +848,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return; } mSettings.switchCurrentUser(currentUserId, !mSystemReady); - // We need to rebuild IMEs. - buildInputMethodListLocked(false /* resetDefaultEnabledIme */); - updateInputMethodsFromSettingsLocked(true /* enabledChanged */); + if (mSystemReady) { + // We need to rebuild IMEs. + buildInputMethodListLocked(false /* resetDefaultEnabledIme */); + updateInputMethodsFromSettingsLocked(true /* enabledChanged */); + } } } @@ -897,13 +902,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mShowOngoingImeSwitcherForPhones = false; - final IntentFilter broadcastFilter = new IntentFilter(); - broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - broadcastFilter.addAction(Intent.ACTION_USER_ADDED); - broadcastFilter.addAction(Intent.ACTION_USER_REMOVED); - broadcastFilter.addAction(Intent.ACTION_SETTING_RESTORED); - mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter); - mNotificationShown = false; int userId = 0; try { @@ -911,7 +909,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } catch (RemoteException e) { Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e); } - mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true); // mSettings should be created before buildInputMethodListLocked mSettings = new InputMethodSettings( @@ -919,48 +916,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub updateCurrentProfileIds(); mFileManager = new InputMethodFileManager(mMethodMap, userId); - synchronized (mMethodMap) { - mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( - mSettings, context); - } - - // Just checking if defaultImiId is empty or not - final String defaultImiId = mSettings.getSelectedInputMethod(); - if (DEBUG) { - Slog.d(TAG, "Initial default ime = " + defaultImiId); - } - mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); - - synchronized (mMethodMap) { - buildInputMethodListLocked(!mImeSelectedOnBoot /* resetDefaultEnabledIme */); - } - mSettings.enableAllIMEsIfThereIsNoEnabledIME(); - - if (!mImeSelectedOnBoot) { - Slog.w(TAG, "No IME selected. Choose the most applicable IME."); - synchronized (mMethodMap) { - resetDefaultImeLocked(context); - } - } - - synchronized (mMethodMap) { - mSettingsObserver.registerContentObserverLocked(userId); - updateFromSettingsLocked(true); - } - - // IMMS wants to receive Intent.ACTION_LOCALE_CHANGED in order to update the current IME - // according to the new system locale. - final IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_LOCALE_CHANGED); - mContext.registerReceiver( - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - synchronized(mMethodMap) { - resetStateIfCurrentLocaleChangedLocked(); - } - } - }, filter); + mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( + mSettings, context); } private void resetDefaultImeLocked(Context context) { @@ -1089,6 +1046,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } if (!mSystemReady) { mSystemReady = true; + mLastSystemLocales = mRes.getConfiguration().getLocales(); final int currentUserId = mSettings.getCurrentUserId(); mSettings.switchCurrentUser(currentUserId, !mUserManager.isUserUnlockingOrUnlocked(currentUserId)); @@ -1105,14 +1063,25 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mWindowManagerInternal.setOnHardKeyboardStatusChangeListener( mHardKeyboardListener); } - if (!mImeSelectedOnBoot) { - Slog.w(TAG, "Reset the default IME as \"Resource\" is ready here."); - resetStateIfCurrentLocaleChangedLocked(); - InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager, - mSettings.getEnabledInputMethodListLocked(), - mSettings.getCurrentUserId(), mContext.getBasePackageName()); - } - mLastSystemLocales = mRes.getConfiguration().getLocales(); + + mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true); + mSettingsObserver.registerContentObserverLocked(currentUserId); + + final IntentFilter broadcastFilter = new IntentFilter(); + broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + broadcastFilter.addAction(Intent.ACTION_USER_ADDED); + broadcastFilter.addAction(Intent.ACTION_USER_REMOVED); + broadcastFilter.addAction(Intent.ACTION_SETTING_RESTORED); + broadcastFilter.addAction(Intent.ACTION_LOCALE_CHANGED); + mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter); + + buildInputMethodListLocked(true /* resetDefaultEnabledIme */); + resetDefaultImeLocked(mContext); + updateFromSettingsLocked(true); + InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager, + mSettings.getEnabledInputMethodListLocked(), currentUserId, + mContext.getBasePackageName()); + try { startInputInnerLocked(); } catch (RuntimeException e) { @@ -2624,6 +2593,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // additional input method subtypes to the IME. if (TextUtils.isEmpty(imiId) || subtypes == null) return; synchronized (mMethodMap) { + if (!mSystemReady) { + return; + } final InputMethodInfo imi = mMethodMap.get(imiId); if (imi == null) return; final String[] packageInfos; @@ -3048,6 +3020,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme + " \n ------ caller=" + Debug.getCallers(10)); } + if (!mSystemReady) { + Slog.e(TAG, "buildInputMethodListLocked is not allowed until system is ready"); + return; + } mMethodList.clear(); mMethodMap.clear(); diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 94acd751c6c4..e11dd1aae400 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -33,6 +33,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.IActivityManager; +import android.app.usage.StorageStatsManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -3270,6 +3271,29 @@ class StorageManagerService extends IStorageManager.Stub } } + @Override + public long getCacheQuotaBytes(String volumeUuid, int uid) { + if (uid != Binder.getCallingUid()) { + mContext.enforceCallingPermission(android.Manifest.permission.STORAGE_INTERNAL, TAG); + } + // TODO: wire up to cache quota once merged + return 64 * TrafficStats.MB_IN_BYTES; + } + + @Override + public long getCacheSizeBytes(String volumeUuid, int uid) { + if (uid != Binder.getCallingUid()) { + mContext.enforceCallingPermission(android.Manifest.permission.STORAGE_INTERNAL, TAG); + } + final long token = Binder.clearCallingIdentity(); + try { + return mContext.getSystemService(StorageStatsManager.class) + .queryStatsForUid(volumeUuid, uid).getCacheBytes(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + private void addObbStateLocked(ObbState obbState) throws RemoteException { final IBinder binder = obbState.getBinder(); List<ObbState> obbStates = mObbMounts.get(binder); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a72950b01dc8..f6fbaf96644a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -9341,7 +9341,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; @@ -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 @@ -19615,7 +19614,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..7bfea9aeb67f 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; @@ -1545,7 +1543,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 +1793,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 */ @@ -4659,7 +4663,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); } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 14899b4d5256..24ff1d7471f0 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; @@ -2576,7 +2575,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; @@ -3229,6 +3228,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; } @@ -3884,14 +3894,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( diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 26d2ee2e1547..8d3277893fde 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; @@ -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..2373ea8975d8 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; @@ -415,8 +422,8 @@ 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); + userId, bounds, overrideConfig, mResizeMode, mSupportsPictureInPicture, + isHomeTask(), isOnTopLauncher(), onTop, showForAllUsers, lastTaskDescription); } void removeWindowContainer() { @@ -691,6 +698,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 +1309,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 +1349,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 +1417,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 +1497,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 +1569,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 +1636,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 +1710,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 +1726,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 +2014,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 +2114,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/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..96459be9234a 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()); @@ -2495,15 +2529,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 +2556,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 +2638,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 +2910,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 +2974,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 +3518,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 +3538,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; } @@ -4246,9 +4279,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 +4304,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 +4318,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/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/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index a74e14150001..50309bedfb85 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -232,7 +232,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 + " for another profile " + targetUserId + + " from " + callingUserId); } UserInfo targetUserInfo = mUm.getUserInfo(targetUserId); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 7b32d20d76dc..63a5d145fe62 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -17703,6 +17703,13 @@ public class PackageManagerService extends IPackageManager.Stub { return false; } + try { + // update shared libraries for the newly re-installed system package + updateSharedLibrariesLPr(newPkg, null); + } catch (PackageManagerException e) { + Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage()); + } + prepareAppDataAfterInstallLIF(newPkg); // writer diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 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/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..914cc8de37ed 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2282,7 +2282,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; } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 680d0f2881a6..c5ed6eca79d4 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -30,6 +30,7 @@ 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 +82,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 +95,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 +107,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 +333,8 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU } boolean isResizeable() { - return ActivityInfo.isResizeableMode(mResizeMode) || mService.mForceResizableTasks; + return ActivityInfo.isResizeableMode(mResizeMode) || mSupportsPictureInPicture + || mService.mForceResizableTasks; } /** @@ -647,6 +658,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 +707,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/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java index 3c438ca3195f..fbc3389881b0 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; @@ -62,7 +63,8 @@ public class TaskWindowContainerController public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener, int stackId, int userId, Rect bounds, Configuration overrideConfig, int resizeMode, - boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers) { + boolean supportsPictureInPicture, boolean homeTask, boolean isOnTopLauncher, + boolean toTop, boolean showForAllUsers, TaskDescription taskDescription) { super(listener, WindowManagerService.getInstance()); mTaskId = taskId; @@ -79,7 +81,7 @@ public class TaskWindowContainerController } EventLog.writeEvent(WM_TASK_CREATED, taskId, stackId); 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 +89,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 @@ -263,6 +265,16 @@ 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(); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 623a0a59fd1a..f3b013122890 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -5574,7 +5574,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } /** - * Set whether auto time is required by the specified admin (must be device owner). + * Set whether auto time is required by the specified admin (must be device or profile owner). */ @Override public void setAutoTimeRequired(ComponentName who, boolean required) { @@ -5585,7 +5585,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { ActiveAdmin admin = getActiveAdminForCallerLocked(who, - DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); if (admin.requireAutoTime != required) { admin.requireAutoTime = required; saveSettingsLocked(userHandle); @@ -5604,7 +5604,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } /** - * Returns whether or not auto time is required by the device owner. + * Returns whether or not auto time is required by the device owner or any profile owner. */ @Override public boolean getAutoTimeRequired() { @@ -5613,7 +5613,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } synchronized (this) { ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); - return (deviceOwner != null) ? deviceOwner.requireAutoTime : false; + if (deviceOwner != null && deviceOwner.requireAutoTime) { + // If the device owner enforces auto time, we don't need to check the PO's + return true; + } + + // Now check to see if any profile owner on any user enforces auto time + for (Integer userId : mOwners.getProfileOwnerKeys()) { + ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId); + if (profileOwner != null && profileOwner.requireAutoTime) { + return true; + } + } + + return false; } } 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..92d9810f7c3f 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 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..b7931d4f6a01 100644 --- a/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java +++ b/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java @@ -199,8 +199,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/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/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/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..847f34dfd729 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java @@ -16,15 +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; import android.support.test.InstrumentationRegistry; @@ -50,13 +53,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 +79,27 @@ 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 = 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 +123,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 +142,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 +154,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 +172,22 @@ class WindowTestsBase { } /** Creates a {@link TaskStack} and adds it to the specified {@link DisplayContent}. */ - TaskStack createTaskStackOnDisplay(DisplayContent dc) { + static TaskStack createTaskStackOnDisplay(DisplayContent dc) { final int stackId = sNextStackId++; dc.addStackToDisplay(stackId, true); return sWm.mStackIdToStack.get(stackId); } /**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 +207,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); @@ -216,9 +238,11 @@ class WindowTestsBase { 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() { @@ -248,16 +272,18 @@ class WindowTestsBase { TestTaskWindowContainerController(int stackId) { super(sNextTaskId++, snapshot -> {}, stackId, 0 /* userId */, null /* bounds */, - EMPTY /* overrideConfig*/, RESIZE_MODE_UNRESIZEABLE, false /* homeTask*/, - false /* isOnTopLauncher */, true /* toTop*/, true /* showForAllUsers */); + EMPTY /* overrideConfig*/, RESIZE_MODE_UNRESIZEABLE, + false /* supportsPictureInPicture */, false /* homeTask*/, + false /* isOnTopLauncher */, true /* toTop*/, true /* showForAllUsers */, + new TaskDescription()); } @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 +305,10 @@ class WindowTestsBase { 0 /* inputDispatchingTimeoutNanos */, sWm); mToken = token; } + + AppWindowToken getAppWindowToken() { + return (AppWindowToken) sDisplayContent.getWindowToken(mToken.asBinder()); + } } class TestIApplicationToken implements IApplicationToken { @@ -295,7 +325,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/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java index 68765b643c66..68269751efc1 100644 --- a/services/usage/java/com/android/server/usage/StorageStatsService.java +++ b/services/usage/java/com/android/server/usage/StorageStatsService.java @@ -37,6 +37,7 @@ import android.os.storage.VolumeInfo; import android.util.Slog; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.Preconditions; import com.android.server.SystemService; import com.android.server.pm.Installer; import com.android.server.pm.Installer.InstallerException; @@ -46,8 +47,6 @@ public class StorageStatsService extends IStorageStatsManager.Stub { private static final String PROP_VERIFY_STORAGE = "fw.verify_storage"; - // TODO: pivot all methods to manual mode when quota isn't supported - public static class Lifecycle extends SystemService { private StorageStatsService mService; @@ -71,11 +70,11 @@ public class StorageStatsService extends IStorageStatsManager.Stub { private final Installer mInstaller; public StorageStatsService(Context context) { - mContext = context; - mAppOps = context.getSystemService(AppOpsManager.class); - mUser = context.getSystemService(UserManager.class); - mPackage = context.getSystemService(PackageManager.class); - mStorage = context.getSystemService(StorageManager.class); + mContext = Preconditions.checkNotNull(context); + mAppOps = Preconditions.checkNotNull(context.getSystemService(AppOpsManager.class)); + mUser = Preconditions.checkNotNull(context.getSystemService(UserManager.class)); + mPackage = Preconditions.checkNotNull(context.getPackageManager()); + mStorage = Preconditions.checkNotNull(context.getSystemService(StorageManager.class)); mInstaller = new Installer(context); mInstaller.onStart(); @@ -107,7 +106,7 @@ public class StorageStatsService extends IStorageStatsManager.Stub { case AppOpsManager.MODE_ALLOWED: return; case AppOpsManager.MODE_DEFAULT: - mContext.enforceCallingPermission( + mContext.enforceCallingOrSelfPermission( android.Manifest.permission.PACKAGE_USAGE_STATS, TAG); return; default: diff --git a/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); |