Merge "API to select backup transport"
diff --git a/Android.mk b/Android.mk
index 2ccdc6de..71b77d5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -347,6 +347,7 @@
core/java/com/android/internal/appwidget/IAppWidgetHost.aidl \
core/java/com/android/internal/backup/IBackupTransport.aidl \
core/java/com/android/internal/backup/IObbBackupService.aidl \
+ core/java/com/android/internal/font/IFontManager.aidl \
core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl \
core/java/com/android/internal/policy/IKeyguardDrawnCallback.aidl \
core/java/com/android/internal/policy/IKeyguardDismissCallback.aidl \
diff --git a/api/current.txt b/api/current.txt
index 5795575..d811020 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -203,6 +203,8 @@
public static final class R.attr {
ctor public R.attr();
+ field public static final int __removed0 = 16844097; // 0x1010541
+ field public static final int __removed1 = 16844099; // 0x1010543
field public static final int absListViewStyle = 16842858; // 0x101006a
field public static final int accessibilityEventTypes = 16843648; // 0x1010380
field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
@@ -758,7 +760,6 @@
field public static final int keyboardLayout = 16843691; // 0x10103ab
field public static final int keyboardMode = 16843341; // 0x101024d
field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
- field public static final int keyboardNavigationSection = 16844097; // 0x1010541
field public static final int keycode = 16842949; // 0x10100c5
field public static final int killAfterRestore = 16843420; // 0x101029c
field public static final int label = 16842753; // 0x1010001
@@ -908,7 +909,6 @@
field public static final int nextFocusLeft = 16842977; // 0x10100e1
field public static final int nextFocusRight = 16842978; // 0x10100e2
field public static final int nextFocusUp = 16842979; // 0x10100e3
- field public static final int nextSectionForward = 16844099; // 0x1010543
field public static final int noHistory = 16843309; // 0x101022d
field public static final int normalScreens = 16843397; // 0x1010285
field public static final int notificationTimeout = 16843651; // 0x1010383
@@ -1169,6 +1169,7 @@
field public static final int spinnerStyle = 16842881; // 0x1010081
field public static final int spinnersShown = 16843595; // 0x101034b
field public static final int splitMotionEvents = 16843503; // 0x10102ef
+ field public static final int splitName = 16844107; // 0x101054b
field public static final int splitTrack = 16843852; // 0x101044c
field public static final int spotShadowAlpha = 16843967; // 0x10104bf
field public static final int src = 16843033; // 0x1010119
@@ -1817,6 +1818,7 @@
field public static final int tabs = 16908307; // 0x1020013
field public static final int text1 = 16908308; // 0x1020014
field public static final int text2 = 16908309; // 0x1020015
+ field public static final int textAssist = 16908353; // 0x1020041
field public static final int title = 16908310; // 0x1020016
field public static final int toggle = 16908311; // 0x1020017
field public static final int undo = 16908338; // 0x1020032
@@ -3536,7 +3538,6 @@
method public int getRequestedOrientation();
method public final android.view.SearchEvent getSearchEvent();
method public int getTaskId();
- method public android.text.TextAssistant getTextAssistant();
method public final java.lang.CharSequence getTitle();
method public final int getTitleColor();
method public android.app.VoiceInteractor getVoiceInteractor();
@@ -3686,7 +3687,6 @@
method public final void setResult(int, android.content.Intent);
method public final deprecated void setSecondaryProgress(int);
method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
- method public void setTextAssistant(android.text.TextAssistant);
method public void setTitle(java.lang.CharSequence);
method public void setTitle(int);
method public deprecated void setTitleColor(int);
@@ -5583,6 +5583,18 @@
field public static final int STYLE_SPINNER = 0; // 0x0
}
+ public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+ ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
+ method public int describeContents();
+ method public android.app.PendingIntent getUserAction();
+ method public java.lang.CharSequence getUserActionTitle();
+ method public java.lang.CharSequence getUserMessage();
+ method public void showAsDialog(android.app.Activity);
+ method public void showAsNotification(android.content.Context);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
+ }
+
public final class RemoteAction implements android.os.Parcelable {
ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener);
method public android.app.RemoteAction clone();
@@ -6366,8 +6378,12 @@
public final class SystemUpdateInfo implements android.os.Parcelable {
method public int describeContents();
method public long getReceivedTime();
+ method public int getSecurityPatchState();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.admin.SystemUpdateInfo> CREATOR;
+ field public static final int SECURITY_PATCH_STATE_FALSE = 1; // 0x1
+ field public static final int SECURITY_PATCH_STATE_TRUE = 2; // 0x2
+ field public static final int SECURITY_PATCH_STATE_UNKNOWN = 0; // 0x0
}
public class SystemUpdatePolicy implements android.os.Parcelable {
@@ -8472,6 +8488,7 @@
field public static final java.lang.String DOWNLOAD_SERVICE = "download";
field public static final java.lang.String DROPBOX_SERVICE = "dropbox";
field public static final java.lang.String FINGERPRINT_SERVICE = "fingerprint";
+ field public static final java.lang.String FONT_SERVICE = "font";
field public static final java.lang.String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
field public static final java.lang.String INPUT_SERVICE = "input";
@@ -9743,6 +9760,7 @@
field public boolean enabled;
field public boolean exported;
field public java.lang.String processName;
+ field public java.lang.String splitName;
}
public class ConfigurationInfo implements android.os.Parcelable {
@@ -10723,6 +10741,7 @@
method public android.graphics.drawable.Drawable getDrawable(int, android.content.res.Resources.Theme) throws android.content.res.Resources.NotFoundException;
method public deprecated android.graphics.drawable.Drawable getDrawableForDensity(int, int) throws android.content.res.Resources.NotFoundException;
method public android.graphics.drawable.Drawable getDrawableForDensity(int, int, android.content.res.Resources.Theme);
+ method public android.graphics.Typeface getFont(int) throws android.content.res.Resources.NotFoundException;
method public float getFraction(int, int, int);
method public int getIdentifier(java.lang.String, java.lang.String, java.lang.String);
method public int[] getIntArray(int) throws android.content.res.Resources.NotFoundException;
@@ -13208,6 +13227,7 @@
}
public class Typeface {
+ method public static void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback);
method public static android.graphics.Typeface create(java.lang.String, int);
method public static android.graphics.Typeface create(android.graphics.Typeface, int);
method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
@@ -13228,6 +13248,14 @@
field public static final android.graphics.Typeface SERIF;
}
+ public static abstract interface Typeface.FontRequestCallback {
+ method public abstract void onTypefaceRequestFailed(int);
+ method public abstract void onTypefaceRetrieved(android.graphics.Typeface);
+ field public static final int FAIL_REASON_FONT_LOAD_ERROR = 1; // 0x1
+ field public static final int FAIL_REASON_FONT_NOT_FOUND = 2; // 0x2
+ field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = 0; // 0x0
+ }
+
public class Xfermode {
ctor public Xfermode();
}
@@ -13782,6 +13810,19 @@
}
+package android.graphics.fonts {
+
+ public final class FontRequest implements android.os.Parcelable {
+ ctor public FontRequest(java.lang.String, java.lang.String);
+ method public int describeContents();
+ method public java.lang.String getProviderAuthority();
+ method public java.lang.String getQuery();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR;
+ }
+
+}
+
package android.graphics.pdf {
public class PdfDocument {
@@ -14109,6 +14150,37 @@
method public float getZ();
}
+ public final class HardwareBuffer implements android.os.Parcelable {
+ method public static android.hardware.HardwareBuffer create(int, int, int, int, long);
+ method public int describeContents();
+ method public void destroy();
+ method public int getFormat();
+ method public int getHeight();
+ method public int getLayers();
+ method public long getUsage();
+ method public int getWidth();
+ method public boolean isDestroyed();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.HardwareBuffer> CREATOR;
+ field public static final int RGBA_8888 = 1; // 0x1
+ field public static final int RGBA_FP16 = 5; // 0x5
+ field public static final int RGBX_8888 = 2; // 0x2
+ field public static final int RGB_565 = 4; // 0x4
+ field public static final int RGB_888 = 3; // 0x3
+ field public static final long USAGE0_CPU_READ = 2L; // 0x2L
+ field public static final long USAGE0_CPU_READ_OFTEN = 6L; // 0x6L
+ field public static final long USAGE0_CPU_WRITE = 32L; // 0x20L
+ field public static final long USAGE0_CPU_WRITE_OFTEN = 96L; // 0x60L
+ field public static final long USAGE0_GPU_COLOR_OUTPUT = 2048L; // 0x800L
+ field public static final long USAGE0_GPU_CUBEMAP = 8192L; // 0x2000L
+ field public static final long USAGE0_GPU_DATA_BUFFER = 16384L; // 0x4000L
+ field public static final long USAGE0_GPU_SAMPLED_IMAGE = 1024L; // 0x400L
+ field public static final long USAGE0_GPU_STORAGE_IMAGE = 3072L; // 0xc00L
+ field public static final long USAGE0_PROTECTED_CONTENT = 262144L; // 0x40000L
+ field public static final long USAGE0_SENSOR_DIRECT_DATA = 536870912L; // 0x20000000L
+ field public static final long USAGE0_VIDEO_ENCODE = 2097152L; // 0x200000L
+ }
+
public final class Sensor {
method public int getFifoMaxEventCount();
method public int getFifoReservedEventCount();
@@ -17556,6 +17628,15 @@
method public boolean isTransitionalDifferent();
}
+ public final class ListFormatter {
+ method public java.lang.String format(java.lang.Object...);
+ method public java.lang.String format(java.util.Collection<?>);
+ method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale);
+ method public static android.icu.text.ListFormatter getInstance(java.util.Locale);
+ method public static android.icu.text.ListFormatter getInstance();
+ method public java.lang.String getPatternForNumItems(int);
+ }
+
public abstract class LocaleDisplayNames {
method public abstract android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type);
method public abstract android.icu.text.LocaleDisplayNames.DialectHandling getDialectHandling();
@@ -17565,6 +17646,8 @@
method public static android.icu.text.LocaleDisplayNames getInstance(android.icu.util.ULocale, android.icu.text.DisplayContext...);
method public static android.icu.text.LocaleDisplayNames getInstance(java.util.Locale, android.icu.text.DisplayContext...);
method public abstract android.icu.util.ULocale getLocale();
+ method public java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiList(java.util.Set<android.icu.util.ULocale>, boolean, java.util.Comparator<java.lang.Object>);
+ method public abstract java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiListCompareWholeItems(java.util.Set<android.icu.util.ULocale>, java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem>);
method public abstract java.lang.String keyDisplayName(java.lang.String);
method public abstract java.lang.String keyValueDisplayName(java.lang.String, java.lang.String);
method public abstract java.lang.String languageDisplayName(java.lang.String);
@@ -17584,9 +17667,19 @@
enum_constant public static final android.icu.text.LocaleDisplayNames.DialectHandling STANDARD_NAMES;
}
+ public static class LocaleDisplayNames.UiListItem {
+ ctor public LocaleDisplayNames.UiListItem(android.icu.util.ULocale, android.icu.util.ULocale, java.lang.String, java.lang.String);
+ method public static java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem> getComparator(java.util.Comparator<java.lang.Object>, boolean);
+ field public final android.icu.util.ULocale minimized;
+ field public final android.icu.util.ULocale modified;
+ field public final java.lang.String nameInDisplayLocale;
+ field public final java.lang.String nameInSelf;
+ }
+
public class MeasureFormat extends android.icu.text.UFormat {
method public final boolean equals(java.lang.Object);
method public java.lang.StringBuffer format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition);
+ method public java.lang.StringBuilder formatMeasurePerUnit(android.icu.util.Measure, android.icu.util.MeasureUnit, java.lang.StringBuilder, java.text.FieldPosition);
method public final java.lang.String formatMeasures(android.icu.util.Measure...);
method public java.lang.StringBuilder formatMeasures(java.lang.StringBuilder, java.text.FieldPosition, android.icu.util.Measure...);
method public static android.icu.text.MeasureFormat getCurrencyFormat(android.icu.util.ULocale);
@@ -18055,6 +18148,14 @@
method public void setUpperCaseFirst(boolean);
}
+ public final class ScientificNumberFormatter {
+ method public java.lang.String format(java.lang.Object);
+ method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.util.ULocale, java.lang.String, java.lang.String);
+ method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.text.DecimalFormat, java.lang.String, java.lang.String);
+ method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.util.ULocale);
+ method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.text.DecimalFormat);
+ }
+
public abstract class SearchIterator {
ctor protected SearchIterator(java.text.CharacterIterator, android.icu.text.BreakIterator);
method public final int first();
@@ -18830,6 +18931,34 @@
method public long getToDate();
}
+ public final class EthiopicCalendar extends android.icu.util.CECalendar {
+ ctor public EthiopicCalendar();
+ ctor public EthiopicCalendar(android.icu.util.TimeZone);
+ ctor public EthiopicCalendar(java.util.Locale);
+ ctor public EthiopicCalendar(android.icu.util.ULocale);
+ ctor public EthiopicCalendar(android.icu.util.TimeZone, java.util.Locale);
+ ctor public EthiopicCalendar(android.icu.util.TimeZone, android.icu.util.ULocale);
+ ctor public EthiopicCalendar(int, int, int);
+ ctor public EthiopicCalendar(java.util.Date);
+ ctor public EthiopicCalendar(int, int, int, int, int, int);
+ method protected deprecated int handleGetExtendedYear();
+ method public boolean isAmeteAlemEra();
+ method public void setAmeteAlemEra(boolean);
+ field public static final int GENBOT = 8; // 0x8
+ field public static final int HAMLE = 10; // 0xa
+ field public static final int HEDAR = 2; // 0x2
+ field public static final int MEGABIT = 6; // 0x6
+ field public static final int MESKEREM = 0; // 0x0
+ field public static final int MIAZIA = 7; // 0x7
+ field public static final int NEHASSE = 11; // 0xb
+ field public static final int PAGUMEN = 12; // 0xc
+ field public static final int SENE = 9; // 0x9
+ field public static final int TAHSAS = 3; // 0x3
+ field public static final int TEKEMT = 1; // 0x1
+ field public static final int TER = 4; // 0x4
+ field public static final int YEKATIT = 5; // 0x5
+ }
+
public abstract interface Freezable<T> implements java.lang.Cloneable {
method public abstract T cloneAsThawed();
method public abstract T freeze();
@@ -19358,6 +19487,35 @@
enum_constant public static final android.icu.util.ULocale.Category FORMAT;
}
+ public final class UniversalTimeScale {
+ method public static android.icu.math.BigDecimal bigDecimalFrom(double, int);
+ method public static android.icu.math.BigDecimal bigDecimalFrom(long, int);
+ method public static android.icu.math.BigDecimal bigDecimalFrom(android.icu.math.BigDecimal, int);
+ method public static long from(long, int);
+ method public static long getTimeScaleValue(int, int);
+ method public static android.icu.math.BigDecimal toBigDecimal(long, int);
+ method public static android.icu.math.BigDecimal toBigDecimal(android.icu.math.BigDecimal, int);
+ method public static long toLong(long, int);
+ field public static final int DB2_TIME = 8; // 0x8
+ field public static final int DOTNET_DATE_TIME = 4; // 0x4
+ field public static final int EPOCH_OFFSET_PLUS_1_VALUE = 6; // 0x6
+ field public static final int EPOCH_OFFSET_VALUE = 1; // 0x1
+ field public static final int EXCEL_TIME = 7; // 0x7
+ field public static final int FROM_MAX_VALUE = 3; // 0x3
+ field public static final int FROM_MIN_VALUE = 2; // 0x2
+ field public static final int ICU4C_TIME = 2; // 0x2
+ field public static final int JAVA_TIME = 0; // 0x0
+ field public static final int MAC_OLD_TIME = 5; // 0x5
+ field public static final int MAC_TIME = 6; // 0x6
+ field public static final int MAX_SCALE = 10; // 0xa
+ field public static final int TO_MAX_VALUE = 5; // 0x5
+ field public static final int TO_MIN_VALUE = 4; // 0x4
+ field public static final int UNITS_VALUE = 0; // 0x0
+ field public static final int UNIX_MICROSECONDS_TIME = 9; // 0x9
+ field public static final int UNIX_TIME = 1; // 0x1
+ field public static final int WINDOWS_FILE_TIME = 3; // 0x3
+ }
+
public abstract interface ValueIterator {
method public abstract boolean next(android.icu.util.ValueIterator.Element);
method public abstract void reset();
@@ -23531,6 +23689,7 @@
field public static final java.lang.String TYPE_NTSC = "TYPE_NTSC";
field public static final java.lang.String TYPE_OTHER = "TYPE_OTHER";
field public static final java.lang.String TYPE_PAL = "TYPE_PAL";
+ field public static final java.lang.String TYPE_PREVIEW = "TYPE_PREVIEW";
field public static final java.lang.String TYPE_SECAM = "TYPE_SECAM";
field public static final java.lang.String TYPE_S_DMB = "TYPE_S_DMB";
field public static final java.lang.String TYPE_T_DMB = "TYPE_T_DMB";
@@ -23571,8 +23730,14 @@
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";
@@ -23701,6 +23866,7 @@
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
@@ -30417,14 +30583,22 @@
}
public class StorageManager {
+ method public long getCacheQuotaBytes();
+ method public long getCacheSizeBytes();
+ method public long getExternalCacheQuotaBytes();
+ method public long getExternalCacheSizeBytes();
method public java.lang.String getMountedObbPath(java.lang.String);
method public android.os.storage.StorageVolume getPrimaryStorageVolume();
method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
method public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
+ method public boolean isCacheBehaviorAtomic(java.io.File) throws java.io.IOException;
+ method public boolean isCacheBehaviorTombstone(java.io.File) throws java.io.IOException;
method public boolean isEncrypted(java.io.File);
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
+ method public void setCacheBehaviorAtomic(java.io.File, boolean) throws java.io.IOException;
+ method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
}
@@ -32894,6 +33068,16 @@
method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
}
+ public class FontsContract {
+ }
+
+ public static final class FontsContract.Columns implements android.provider.BaseColumns {
+ ctor public FontsContract.Columns();
+ field public static final java.lang.String STYLE = "font_style";
+ field public static final java.lang.String TTC_INDEX = "font_ttc_index";
+ field public static final java.lang.String VARIATION_SETTINGS = "font_variation_settings";
+ }
+
public final deprecated class LiveFolders implements android.provider.BaseColumns {
field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
field public static final java.lang.String DESCRIPTION = "description";
@@ -35731,6 +35915,7 @@
public static class NotificationListenerService.Ranking {
ctor public NotificationListenerService.Ranking();
+ method public boolean canShowBadge();
method public java.util.List<java.lang.String> getAdditionalPeople();
method public android.app.NotificationChannel getChannel();
method public int getImportance();
@@ -35772,7 +35957,6 @@
method public int getId();
method public java.lang.String getKey();
method public android.app.Notification getNotification();
- method public android.app.NotificationChannel getNotificationChannel();
method public java.lang.String getOverrideGroupKey();
method public java.lang.String getPackageName();
method public long getPostTime();
@@ -36188,6 +36372,7 @@
method public abstract int getMaxBufferSize();
method public abstract boolean hasFinished();
method public abstract boolean hasStarted();
+ method public default void rangeStart(int, int, int);
method public abstract int start(int, int, int);
}
@@ -36340,6 +36525,7 @@
method public void onError(java.lang.String, int);
method public abstract void onStart(java.lang.String);
method public void onStop(java.lang.String, boolean);
+ method public void onUtteranceRangeStart(java.lang.String, int, int);
}
public class Voice implements android.os.Parcelable {
@@ -37690,7 +37876,7 @@
field public static final java.lang.String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
field public static final java.lang.String ACTION_CONFIGURE_PHONE_ACCOUNT = "android.telecom.action.CONFIGURE_PHONE_ACCOUNT";
field public static final java.lang.String ACTION_DEFAULT_DIALER_CHANGED = "android.telecom.action.DEFAULT_DIALER_CHANGED";
- field public static final java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
+ field public static final deprecated java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS";
field public static final java.lang.String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS";
field public static final java.lang.String ACTION_SHOW_MISSED_CALLS_NOTIFICATION = "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION";
@@ -37793,7 +37979,8 @@
field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
- field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final deprecated java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY = "carrier_vvm_package_name_string_array";
field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool";
field public static final java.lang.String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
@@ -37885,9 +38072,13 @@
field public static final java.lang.String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final java.lang.String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
field public static final java.lang.String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
+ field public static final java.lang.String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string";
field public static final java.lang.String KEY_VVM_DESTINATION_NUMBER_STRING = "vvm_destination_number_string";
+ field public static final java.lang.String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY = "vvm_disabled_capabilities_string_array";
+ field public static final java.lang.String KEY_VVM_LEGACY_MODE_ENABLED_BOOL = "vvm_legacy_mode_enabled_bool";
field public static final java.lang.String KEY_VVM_PORT_NUMBER_INT = "vvm_port_number_int";
field public static final java.lang.String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
+ field public static final java.lang.String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
field public static final java.lang.String KEY_VVM_TYPE_STRING = "vvm_type_string";
field public static final java.lang.String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
}
@@ -38426,6 +38617,7 @@
method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+ method public boolean isConcurrentVoiceAndDataAllowed();
method public boolean isHearingAidCompatibilitySupported();
method public boolean isNetworkRoaming();
method public boolean isSmsCapable();
@@ -39449,6 +39641,65 @@
method public android.text.Editable newEditable(java.lang.CharSequence);
}
+ public final class FontConfig implements android.os.Parcelable {
+ ctor public FontConfig();
+ ctor public FontConfig(android.text.FontConfig);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Alias> getAliases();
+ method public java.util.List<android.text.FontConfig.Family> getFamilies();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR;
+ }
+
+ public static final class FontConfig.Alias implements android.os.Parcelable {
+ ctor public FontConfig.Alias(java.lang.String, java.lang.String, int);
+ method public int describeContents();
+ method public java.lang.String getName();
+ method public java.lang.String getToName();
+ method public int getWeight();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Alias> CREATOR;
+ }
+
+ public static final class FontConfig.Axis implements android.os.Parcelable {
+ ctor public FontConfig.Axis(int, float);
+ method public int describeContents();
+ method public float getStyleValue();
+ method public int getTag();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Axis> CREATOR;
+ }
+
+ public static final class FontConfig.Family implements android.os.Parcelable {
+ ctor public FontConfig.Family(java.lang.String, java.util.List<android.text.FontConfig.Font>, java.lang.String, java.lang.String);
+ ctor public FontConfig.Family(android.text.FontConfig.Family);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Font> getFonts();
+ method public java.lang.String getLanguage();
+ method public java.lang.String getName();
+ method public java.lang.String getVariant();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Family> CREATOR;
+ }
+
+ public static final class FontConfig.Font implements android.os.Parcelable {
+ ctor public FontConfig.Font(java.lang.String, int, java.util.List<android.text.FontConfig.Axis>, int, boolean);
+ ctor public FontConfig.Font(android.text.FontConfig.Font);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Axis> getAxes();
+ method public android.os.ParcelFileDescriptor getFd();
+ method public java.lang.String getFontName();
+ method public int getTtcIndex();
+ method public int getWeight();
+ method public boolean isItalic();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Font> CREATOR;
+ }
+
+ public final class FontManager {
+ method public android.text.FontConfig getSystemFonts();
+ }
+
public abstract interface GetChars implements java.lang.CharSequence {
method public abstract void getChars(int, int, char[], int);
}
@@ -39804,22 +40055,6 @@
method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
}
- public abstract interface TextAssistant {
- method public abstract void addLinks(android.text.Spannable, int);
- method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
- public class TextClassification {
- ctor public TextClassification();
- method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence();
- }
-
- public final class TextClassificationManager implements android.text.TextAssistant {
- method public void addLinks(android.text.Spannable, int);
- method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence);
- method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
public abstract interface TextDirectionHeuristic {
method public abstract boolean isRtl(char[], int, int);
method public abstract boolean isRtl(java.lang.CharSequence, int, int);
@@ -39835,13 +40070,6 @@
field public static final android.text.TextDirectionHeuristic RTL;
}
- public final class TextLanguage {
- ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>);
- method public int getEndIndex();
- method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence();
- method public int getStartIndex();
- }
-
public class TextPaint extends android.graphics.Paint {
ctor public TextPaint();
ctor public TextPaint(int);
@@ -39854,13 +40082,6 @@
field public int linkColor;
}
- public class TextSelection {
- ctor public TextSelection();
- method public int getSelectionEndIndex();
- method public int getSelectionStartIndex();
- method public android.text.TextClassification getTextClassification();
- }
-
public class TextUtils {
method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String);
method public static java.lang.CharSequence concat(java.lang.CharSequence...);
@@ -42114,7 +42335,9 @@
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
@@ -42183,7 +42406,7 @@
method public android.view.View findNearestTouchable(android.view.ViewGroup, int, int, int, int[]);
method public final android.view.View findNextFocus(android.view.ViewGroup, android.view.View, int);
method public android.view.View findNextFocusFromRect(android.view.ViewGroup, android.graphics.Rect, int);
- method public android.view.View findNextKeyboardNavigationGroup(int, android.view.View, android.view.View, int);
+ method public android.view.View findNextKeyboardNavigationCluster(android.view.View, android.view.View, int);
method public static android.view.FocusFinder getInstance();
}
@@ -43481,7 +43704,7 @@
method public void addChildrenForAccessibility(java.util.ArrayList<android.view.View>);
method public void addFocusables(java.util.ArrayList<android.view.View>, int);
method public void addFocusables(java.util.ArrayList<android.view.View>, int, int);
- method public void addKeyboardNavigationGroups(int, java.util.Collection<android.view.View>, int);
+ method public void addKeyboardNavigationClusters(java.util.Collection<android.view.View>, int);
method public void addOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener);
method public void addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener);
method public void addTouchables(java.util.ArrayList<android.view.View>);
@@ -43603,6 +43826,7 @@
method public float getElevation();
method public boolean getFilterTouchesWhenObscured();
method public boolean getFitsSystemWindows();
+ method public int getFocusable();
method public java.util.ArrayList<android.view.View> getFocusables(int);
method public void getFocusedRect(android.graphics.Rect);
method public android.graphics.drawable.Drawable getForeground();
@@ -43645,7 +43869,6 @@
method public int getNextFocusLeftId();
method public int getNextFocusRightId();
method public int getNextFocusUpId();
- method public int getNextSectionForwardId();
method public android.view.View.OnFocusChangeListener getOnFocusChangeListener();
method public android.view.ViewOutlineProvider getOutlineProvider();
method public int getOverScrollMode();
@@ -43689,7 +43912,6 @@
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();
@@ -43751,7 +43973,6 @@
method public boolean isInLayout();
method public boolean isInTouchMode();
method public final boolean isKeyboardNavigationCluster();
- method public final boolean isKeyboardNavigationSection();
method public boolean isLaidOut();
method public boolean isLayoutDirectionResolved();
method public boolean isLayoutRequested();
@@ -43774,7 +43995,7 @@
method public boolean isVerticalFadingEdgeEnabled();
method public boolean isVerticalScrollBarEnabled();
method public void jumpDrawablesToCurrentState();
- method public android.view.View keyboardNavigationGroupSearch(int, android.view.View, int);
+ method public android.view.View keyboardNavigationClusterSearch(android.view.View, int);
method public void layout(int, int, int, int);
method public final void measure(int, int);
method protected static int[] mergeDrawableStates(int[], int[]);
@@ -43909,6 +44130,7 @@
method public void setFilterTouchesWhenObscured(boolean);
method public void setFitsSystemWindows(boolean);
method public void setFocusable(boolean);
+ method public void setFocusable(int);
method public void setFocusableInTouchMode(boolean);
method public void setFocusedByDefault(boolean);
method public void setForeground(android.graphics.drawable.Drawable);
@@ -43924,7 +44146,6 @@
method public void setImportantForAccessibility(int);
method public void setKeepScreenOn(boolean);
method public void setKeyboardNavigationCluster(boolean);
- method public void setKeyboardNavigationSection(boolean);
method public void setLabelFor(int);
method public void setLayerPaint(android.graphics.Paint);
method public void setLayerType(int, android.graphics.Paint);
@@ -43942,7 +44163,6 @@
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);
@@ -43991,7 +44211,6 @@
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);
@@ -44049,8 +44268,10 @@
field protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET;
field public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 2; // 0x2
field public static final int FIND_VIEWS_WITH_TEXT = 1; // 0x1
+ field public static final int FOCUSABLE = 1; // 0x1
field public static final int FOCUSABLES_ALL = 0; // 0x0
field public static final int FOCUSABLES_TOUCH_MODE = 1; // 0x1
+ field public static final int FOCUSABLE_AUTO = 16; // 0x10
field protected static final int[] FOCUSED_SELECTED_STATE_SET;
field protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
field protected static final int[] FOCUSED_STATE_SET;
@@ -44069,8 +44290,6 @@
field public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1
field public static final int INVISIBLE = 4; // 0x4
field public static final int KEEP_SCREEN_ON = 67108864; // 0x4000000
- field public static final int KEYBOARD_NAVIGATION_GROUP_CLUSTER = 1; // 0x1
- field public static final int KEYBOARD_NAVIGATION_GROUP_SECTION = 2; // 0x2
field public static final int LAYER_TYPE_HARDWARE = 2; // 0x2
field public static final int LAYER_TYPE_NONE = 0; // 0x0
field public static final int LAYER_TYPE_SOFTWARE = 1; // 0x1
@@ -44082,6 +44301,7 @@
field public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff
field public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000
field public static final int MEASURED_STATE_TOO_SMALL = 16777216; // 0x1000000
+ field public static final int NOT_FOCUSABLE = 0; // 0x0
field public static final int NO_ID = -1; // 0xffffffff
field public static final int OVER_SCROLL_ALWAYS = 0; // 0x0
field public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; // 0x1
@@ -44589,7 +44809,7 @@
method public abstract boolean isLayoutRequested();
method public abstract boolean isTextAlignmentResolved();
method public abstract boolean isTextDirectionResolved();
- method public abstract android.view.View keyboardNavigationGroupSearch(int, android.view.View, int);
+ method public abstract android.view.View keyboardNavigationClusterSearch(android.view.View, int);
method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int);
method public abstract boolean onNestedFling(android.view.View, float, float, boolean);
method public abstract boolean onNestedPreFling(android.view.View, float, float);
@@ -46376,6 +46596,83 @@
}
+package android.view.textclassifier {
+
+ public abstract interface LinksInfo {
+ method public abstract boolean apply(java.lang.CharSequence);
+ }
+
+ public final class TextClassificationManager {
+ method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence);
+ method public android.view.textclassifier.TextClassifier getDefaultTextClassifier();
+ }
+
+ public final class TextClassificationResult {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public android.graphics.drawable.Drawable getIcon();
+ method public android.content.Intent getIntent();
+ method public java.lang.CharSequence getLabel();
+ method public android.view.View.OnClickListener getOnClickListener();
+ method public java.lang.String getText();
+ }
+
+ public static final class TextClassificationResult.Builder {
+ ctor public TextClassificationResult.Builder();
+ method public android.view.textclassifier.TextClassificationResult build();
+ method public android.view.textclassifier.TextClassificationResult.Builder setEntityType(java.lang.String, float);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIcon(android.graphics.drawable.Drawable);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIntent(android.content.Intent);
+ method public android.view.textclassifier.TextClassificationResult.Builder setLabel(java.lang.String);
+ method public android.view.textclassifier.TextClassificationResult.Builder setOnClickListener(android.view.View.OnClickListener);
+ method public android.view.textclassifier.TextClassificationResult.Builder setText(java.lang.String);
+ }
+
+ public abstract interface TextClassifier {
+ method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
+ method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
+ method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
+ field public static final android.view.textclassifier.TextClassifier NO_OP;
+ field public static final java.lang.String TYPE_ADDRESS = "address";
+ field public static final java.lang.String TYPE_EMAIL = "email";
+ field public static final java.lang.String TYPE_OTHER = "other";
+ field public static final java.lang.String TYPE_PHONE = "phone";
+ }
+
+ public static abstract class TextClassifier.EntityType implements java.lang.annotation.Annotation {
+ }
+
+ public final class TextLanguage {
+ method public float getConfidenceScore(java.util.Locale);
+ method public int getEndIndex();
+ method public java.util.Locale getLanguage(int);
+ method public int getLanguageCount();
+ method public int getStartIndex();
+ }
+
+ public static final class TextLanguage.Builder {
+ ctor public TextLanguage.Builder(int, int);
+ method public android.view.textclassifier.TextLanguage build();
+ method public android.view.textclassifier.TextLanguage.Builder setLanguage(java.util.Locale, float);
+ }
+
+ public final class TextSelection {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public int getSelectionEndIndex();
+ method public int getSelectionStartIndex();
+ }
+
+ public static final class TextSelection.Builder {
+ ctor public TextSelection.Builder(int, int);
+ method public android.view.textclassifier.TextSelection build();
+ method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float);
+ }
+
+}
+
package android.view.textservice {
public final class SentenceSuggestionsInfo implements android.os.Parcelable {
@@ -46606,6 +46903,7 @@
public abstract class RenderProcessGoneDetail {
ctor public RenderProcessGoneDetail();
method public abstract boolean didCrash();
+ method public abstract int rendererPriorityAtExit();
}
public class ServiceWorkerClient {
@@ -47022,6 +47320,8 @@
method public deprecated java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String);
method public java.lang.String getOriginalUrl();
method public int getProgress();
+ method public boolean getRendererPriorityWaivedWhenNotVisible();
+ method public int getRendererRequestedPriority();
method public deprecated float getScale();
method public android.webkit.WebSettings getSettings();
method public java.lang.String getTitle();
@@ -47069,6 +47369,7 @@
method public deprecated void setMapTrackballToArrowKeys(boolean);
method public void setNetworkAvailable(boolean);
method public deprecated void setPictureListener(android.webkit.WebView.PictureListener);
+ method public void setRendererPriorityPolicy(int, boolean);
method public deprecated void setVerticalScrollbarOverlay(boolean);
method public void setWebChromeClient(android.webkit.WebChromeClient);
method public static void setWebContentsDebuggingEnabled(boolean);
@@ -47078,6 +47379,9 @@
method public void zoomBy(float);
method public boolean zoomIn();
method public boolean zoomOut();
+ field public static final int RENDERER_PRIORITY_BOUND = 1; // 0x1
+ field public static final int RENDERER_PRIORITY_IMPORTANT = 2; // 0x2
+ field public static final int RENDERER_PRIORITY_WAIVED = 0; // 0x0
field public static final java.lang.String SCHEME_GEO = "geo:0,0?q=";
field public static final java.lang.String SCHEME_MAILTO = "mailto:";
field public static final java.lang.String SCHEME_TEL = "tel:";
@@ -49383,6 +49687,10 @@
method public void endBatchEdit();
method public boolean extractText(android.view.inputmethod.ExtractedTextRequest, android.view.inputmethod.ExtractedText);
method public final int getAutoLinkMask();
+ method public int getAutoSizeMaxTextSize();
+ method public int getAutoSizeMinTextSize();
+ method public int getAutoSizeStepGranularity();
+ method public int getAutoSizeTextType();
method public int getBreakStrategy();
method public int getCompoundDrawablePadding();
method public android.content.res.ColorStateList getCompoundDrawableTintList();
@@ -49454,7 +49762,7 @@
method public float getShadowRadius();
method public final boolean getShowSoftInputOnFocus();
method public java.lang.CharSequence getText();
- method public android.text.TextAssistant getTextAssistant();
+ method public android.view.textclassifier.TextClassifier getTextClassifier();
method public final android.content.res.ColorStateList getTextColors();
method public java.util.Locale getTextLocale();
method public android.os.LocaleList getTextLocales();
@@ -49491,6 +49799,10 @@
method public void removeTextChangedListener(android.text.TextWatcher);
method public void setAllCaps(boolean);
method public final void setAutoLinkMask(int);
+ method public void setAutoSizeMaxTextSize(int, float);
+ method public void setAutoSizeMinTextSize(int, float);
+ method public void setAutoSizeStepGranularity(int, float);
+ method public void setAutoSizeTextType(int);
method public void setBreakStrategy(int);
method public void setCompoundDrawablePadding(int);
method public void setCompoundDrawableTintList(android.content.res.ColorStateList);
@@ -49566,7 +49878,7 @@
method public final void setText(int, android.widget.TextView.BufferType);
method public void setTextAppearance(int);
method public deprecated void setTextAppearance(android.content.Context, int);
- method public void setTextAssistant(android.text.TextAssistant);
+ method public void setTextClassifier(android.view.textclassifier.TextClassifier);
method public void setTextColor(int);
method public void setTextColor(android.content.res.ColorStateList);
method public void setTextIsSelectable(boolean);
@@ -49581,8 +49893,8 @@
method public void setTypeface(android.graphics.Typeface, int);
method public void setTypeface(android.graphics.Typeface);
method public void setWidth(int);
- field public static final int AUTO_SIZE_TYPE_NONE = 0; // 0x0
- field public static final int AUTO_SIZE_TYPE_XY = 1; // 0x1
+ field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
+ field public static final int AUTO_SIZE_TEXT_TYPE_XY = 1; // 0x1
}
public static final class TextView.BufferType extends java.lang.Enum {
diff --git a/api/system-current.txt b/api/system-current.txt
index 144bce8..5cd33ba 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -312,6 +312,8 @@
public static final class R.attr {
ctor public R.attr();
+ field public static final int __removed0 = 16844097; // 0x1010541
+ field public static final int __removed1 = 16844099; // 0x1010543
field public static final int absListViewStyle = 16842858; // 0x101006a
field public static final int accessibilityEventTypes = 16843648; // 0x1010380
field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
@@ -867,7 +869,6 @@
field public static final int keyboardLayout = 16843691; // 0x10103ab
field public static final int keyboardMode = 16843341; // 0x101024d
field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
- field public static final int keyboardNavigationSection = 16844097; // 0x1010541
field public static final int keycode = 16842949; // 0x10100c5
field public static final int killAfterRestore = 16843420; // 0x101029c
field public static final int label = 16842753; // 0x1010001
@@ -1017,7 +1018,6 @@
field public static final int nextFocusLeft = 16842977; // 0x10100e1
field public static final int nextFocusRight = 16842978; // 0x10100e2
field public static final int nextFocusUp = 16842979; // 0x10100e3
- field public static final int nextSectionForward = 16844099; // 0x1010543
field public static final int noHistory = 16843309; // 0x101022d
field public static final int normalScreens = 16843397; // 0x1010285
field public static final int notificationTimeout = 16843651; // 0x1010383
@@ -1282,6 +1282,7 @@
field public static final int spinnerStyle = 16842881; // 0x1010081
field public static final int spinnersShown = 16843595; // 0x101034b
field public static final int splitMotionEvents = 16843503; // 0x10102ef
+ field public static final int splitName = 16844107; // 0x101054b
field public static final int splitTrack = 16843852; // 0x101044c
field public static final int spotShadowAlpha = 16843967; // 0x10104bf
field public static final int src = 16843033; // 0x1010119
@@ -1930,6 +1931,7 @@
field public static final int tabs = 16908307; // 0x1020013
field public static final int text1 = 16908308; // 0x1020014
field public static final int text2 = 16908309; // 0x1020015
+ field public static final int textAssist = 16908353; // 0x1020041
field public static final int title = 16908310; // 0x1020016
field public static final int toggle = 16908311; // 0x1020017
field public static final int undo = 16908338; // 0x1020032
@@ -3655,7 +3657,6 @@
method public int getRequestedOrientation();
method public final android.view.SearchEvent getSearchEvent();
method public int getTaskId();
- method public android.text.TextAssistant getTextAssistant();
method public final java.lang.CharSequence getTitle();
method public final int getTitleColor();
method public android.app.VoiceInteractor getVoiceInteractor();
@@ -3807,7 +3808,6 @@
method public final void setResult(int, android.content.Intent);
method public final deprecated void setSecondaryProgress(int);
method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
- method public void setTextAssistant(android.text.TextAssistant);
method public void setTitle(java.lang.CharSequence);
method public void setTitle(int);
method public deprecated void setTitleColor(int);
@@ -5504,9 +5504,11 @@
ctor public Notification.TvExtender();
ctor public Notification.TvExtender(android.app.Notification);
method public android.app.Notification.Builder extend(android.app.Notification.Builder);
+ method public java.lang.String getChannel();
method public android.app.PendingIntent getContentIntent();
method public android.app.PendingIntent getDeleteIntent();
method public boolean isAvailableOnTv();
+ method public android.app.Notification.TvExtender setChannel(java.lang.String);
method public android.app.Notification.TvExtender setContentIntent(android.app.PendingIntent);
method public android.app.Notification.TvExtender setDeleteIntent(android.app.PendingIntent);
}
@@ -5770,6 +5772,18 @@
field public static final int STYLE_SPINNER = 0; // 0x0
}
+ public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+ ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
+ method public int describeContents();
+ method public android.app.PendingIntent getUserAction();
+ method public java.lang.CharSequence getUserActionTitle();
+ method public java.lang.CharSequence getUserMessage();
+ method public void showAsDialog(android.app.Activity);
+ method public void showAsNotification(android.content.Context);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
+ }
+
public final class RemoteAction implements android.os.Parcelable {
ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener);
method public android.app.RemoteAction clone();
@@ -6382,6 +6396,7 @@
method public void lockNow();
method public void lockNow(int);
method public void notifyPendingSystemUpdate(long);
+ method public void notifyPendingSystemUpdate(long, boolean);
method public boolean packageHasActiveAdmins(java.lang.String);
method public void reboot(android.content.ComponentName);
method public void removeActiveAdmin(android.content.ComponentName);
@@ -6589,8 +6604,12 @@
public final class SystemUpdateInfo implements android.os.Parcelable {
method public int describeContents();
method public long getReceivedTime();
+ method public int getSecurityPatchState();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.admin.SystemUpdateInfo> CREATOR;
+ field public static final int SECURITY_PATCH_STATE_FALSE = 1; // 0x1
+ field public static final int SECURITY_PATCH_STATE_TRUE = 2; // 0x2
+ field public static final int SECURITY_PATCH_STATE_UNKNOWN = 0; // 0x0
}
public class SystemUpdatePolicy implements android.os.Parcelable {
@@ -8856,6 +8875,7 @@
field public static final java.lang.String DOWNLOAD_SERVICE = "download";
field public static final java.lang.String DROPBOX_SERVICE = "dropbox";
field public static final java.lang.String FINGERPRINT_SERVICE = "fingerprint";
+ field public static final java.lang.String FONT_SERVICE = "font";
field public static final java.lang.String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
field public static final java.lang.String HDMI_CONTROL_SERVICE = "hdmi_control";
field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
@@ -10153,6 +10173,7 @@
field public boolean enabled;
field public boolean exported;
field public java.lang.String processName;
+ field public java.lang.String splitName;
}
public class ConfigurationInfo implements android.os.Parcelable {
@@ -10183,7 +10204,8 @@
public final class EphemeralResolveInfo implements android.os.Parcelable {
ctor public deprecated EphemeralResolveInfo(android.net.Uri, java.lang.String, java.util.List<android.content.IntentFilter>);
- ctor public EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List<android.content.pm.EphemeralIntentFilter>);
+ ctor public deprecated EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List<android.content.pm.EphemeralIntentFilter>);
+ ctor public EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List<android.content.pm.EphemeralIntentFilter>, int);
ctor public EphemeralResolveInfo(java.lang.String, java.lang.String, java.util.List<android.content.pm.EphemeralIntentFilter>);
method public int describeContents();
method public byte[] getDigestBytes();
@@ -10191,6 +10213,7 @@
method public deprecated java.util.List<android.content.IntentFilter> getFilters();
method public java.util.List<android.content.pm.EphemeralIntentFilter> getIntentFilters();
method public java.lang.String getPackageName();
+ method public int getVersionCode();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.pm.EphemeralResolveInfo> CREATOR;
field public static final java.lang.String SHA_ALGORITHM = "SHA-256";
@@ -10244,6 +10267,15 @@
field public java.lang.String targetPackage;
}
+ public final class IntentFilterVerificationInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.util.Set<java.lang.String> getDomains();
+ method public java.lang.String getPackageName();
+ method public int getStatus();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.pm.IntentFilterVerificationInfo> CREATOR;
+ }
+
public class LabeledIntent extends android.content.Intent {
ctor public LabeledIntent(android.content.Intent, java.lang.String, int, int);
ctor public LabeledIntent(android.content.Intent, java.lang.String, java.lang.CharSequence, int);
@@ -10509,6 +10541,7 @@
method public abstract android.content.pm.ActivityInfo getActivityInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.graphics.drawable.Drawable getActivityLogo(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.graphics.drawable.Drawable getActivityLogo(android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
method public abstract java.util.List<android.content.pm.PermissionGroupInfo> getAllPermissionGroups(int);
method public abstract android.graphics.drawable.Drawable getApplicationBanner(android.content.pm.ApplicationInfo);
method public abstract android.graphics.drawable.Drawable getApplicationBanner(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -10521,12 +10554,15 @@
method public abstract android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract int getComponentEnabledSetting(android.content.ComponentName);
method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
+ method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int);
method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
method public abstract java.lang.String getInstallerPackageName(java.lang.String);
method public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
+ method public abstract int getIntentVerificationStatusAsUser(java.lang.String, int);
method public abstract android.content.Intent getLaunchIntentForPackage(java.lang.String);
method public abstract android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
method public abstract java.lang.String getNameForUid(int);
@@ -10582,7 +10618,9 @@
method public abstract void setApplicationCategoryHint(java.lang.String, int);
method public abstract void setApplicationEnabledSetting(java.lang.String, int, int);
method public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
+ method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
method public abstract void setInstallerPackageName(java.lang.String, java.lang.String);
+ method public abstract boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
method public abstract void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
method public abstract void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
method public abstract void verifyPendingInstall(int, int);
@@ -10743,6 +10781,11 @@
field public static final int INSTALL_REASON_POLICY = 1; // 0x1
field public static final int INSTALL_REASON_UNKNOWN = 0; // 0x0
field public static final int INSTALL_SUCCEEDED = 1; // 0x1
+ field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2; // 0x2
+ field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4; // 0x4
+ field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1; // 0x1
+ field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3; // 0x3
+ field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0; // 0x0
field public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1; // 0xffffffff
field public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
field public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
@@ -11254,6 +11297,7 @@
method public android.graphics.drawable.Drawable getDrawable(int, android.content.res.Resources.Theme) throws android.content.res.Resources.NotFoundException;
method public deprecated android.graphics.drawable.Drawable getDrawableForDensity(int, int) throws android.content.res.Resources.NotFoundException;
method public android.graphics.drawable.Drawable getDrawableForDensity(int, int, android.content.res.Resources.Theme);
+ method public android.graphics.Typeface getFont(int) throws android.content.res.Resources.NotFoundException;
method public float getFraction(int, int, int);
method public int getIdentifier(java.lang.String, java.lang.String, java.lang.String);
method public int[] getIntArray(int) throws android.content.res.Resources.NotFoundException;
@@ -13739,6 +13783,7 @@
}
public class Typeface {
+ method public static void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback);
method public static android.graphics.Typeface create(java.lang.String, int);
method public static android.graphics.Typeface create(android.graphics.Typeface, int);
method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
@@ -13759,6 +13804,14 @@
field public static final android.graphics.Typeface SERIF;
}
+ public static abstract interface Typeface.FontRequestCallback {
+ method public abstract void onTypefaceRequestFailed(int);
+ method public abstract void onTypefaceRetrieved(android.graphics.Typeface);
+ field public static final int FAIL_REASON_FONT_LOAD_ERROR = 1; // 0x1
+ field public static final int FAIL_REASON_FONT_NOT_FOUND = 2; // 0x2
+ field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = 0; // 0x0
+ }
+
public class Xfermode {
ctor public Xfermode();
}
@@ -14313,6 +14366,19 @@
}
+package android.graphics.fonts {
+
+ public final class FontRequest implements android.os.Parcelable {
+ ctor public FontRequest(java.lang.String, java.lang.String);
+ method public int describeContents();
+ method public java.lang.String getProviderAuthority();
+ method public java.lang.String getQuery();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR;
+ }
+
+}
+
package android.graphics.pdf {
public class PdfDocument {
@@ -14640,6 +14706,37 @@
method public float getZ();
}
+ public final class HardwareBuffer implements android.os.Parcelable {
+ method public static android.hardware.HardwareBuffer create(int, int, int, int, long);
+ method public int describeContents();
+ method public void destroy();
+ method public int getFormat();
+ method public int getHeight();
+ method public int getLayers();
+ method public long getUsage();
+ method public int getWidth();
+ method public boolean isDestroyed();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.HardwareBuffer> CREATOR;
+ field public static final int RGBA_8888 = 1; // 0x1
+ field public static final int RGBA_FP16 = 5; // 0x5
+ field public static final int RGBX_8888 = 2; // 0x2
+ field public static final int RGB_565 = 4; // 0x4
+ field public static final int RGB_888 = 3; // 0x3
+ field public static final long USAGE0_CPU_READ = 2L; // 0x2L
+ field public static final long USAGE0_CPU_READ_OFTEN = 6L; // 0x6L
+ field public static final long USAGE0_CPU_WRITE = 32L; // 0x20L
+ field public static final long USAGE0_CPU_WRITE_OFTEN = 96L; // 0x60L
+ field public static final long USAGE0_GPU_COLOR_OUTPUT = 2048L; // 0x800L
+ field public static final long USAGE0_GPU_CUBEMAP = 8192L; // 0x2000L
+ field public static final long USAGE0_GPU_DATA_BUFFER = 16384L; // 0x4000L
+ field public static final long USAGE0_GPU_SAMPLED_IMAGE = 1024L; // 0x400L
+ field public static final long USAGE0_GPU_STORAGE_IMAGE = 3072L; // 0xc00L
+ field public static final long USAGE0_PROTECTED_CONTENT = 262144L; // 0x40000L
+ field public static final long USAGE0_SENSOR_DIRECT_DATA = 536870912L; // 0x20000000L
+ field public static final long USAGE0_VIDEO_ENCODE = 2097152L; // 0x200000L
+ }
+
public final class Sensor {
method public int getFifoMaxEventCount();
method public int getFifoReservedEventCount();
@@ -18809,6 +18906,15 @@
method public boolean isTransitionalDifferent();
}
+ public final class ListFormatter {
+ method public java.lang.String format(java.lang.Object...);
+ method public java.lang.String format(java.util.Collection<?>);
+ method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale);
+ method public static android.icu.text.ListFormatter getInstance(java.util.Locale);
+ method public static android.icu.text.ListFormatter getInstance();
+ method public java.lang.String getPatternForNumItems(int);
+ }
+
public abstract class LocaleDisplayNames {
method public abstract android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type);
method public abstract android.icu.text.LocaleDisplayNames.DialectHandling getDialectHandling();
@@ -18818,6 +18924,8 @@
method public static android.icu.text.LocaleDisplayNames getInstance(android.icu.util.ULocale, android.icu.text.DisplayContext...);
method public static android.icu.text.LocaleDisplayNames getInstance(java.util.Locale, android.icu.text.DisplayContext...);
method public abstract android.icu.util.ULocale getLocale();
+ method public java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiList(java.util.Set<android.icu.util.ULocale>, boolean, java.util.Comparator<java.lang.Object>);
+ method public abstract java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiListCompareWholeItems(java.util.Set<android.icu.util.ULocale>, java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem>);
method public abstract java.lang.String keyDisplayName(java.lang.String);
method public abstract java.lang.String keyValueDisplayName(java.lang.String, java.lang.String);
method public abstract java.lang.String languageDisplayName(java.lang.String);
@@ -18837,9 +18945,19 @@
enum_constant public static final android.icu.text.LocaleDisplayNames.DialectHandling STANDARD_NAMES;
}
+ public static class LocaleDisplayNames.UiListItem {
+ ctor public LocaleDisplayNames.UiListItem(android.icu.util.ULocale, android.icu.util.ULocale, java.lang.String, java.lang.String);
+ method public static java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem> getComparator(java.util.Comparator<java.lang.Object>, boolean);
+ field public final android.icu.util.ULocale minimized;
+ field public final android.icu.util.ULocale modified;
+ field public final java.lang.String nameInDisplayLocale;
+ field public final java.lang.String nameInSelf;
+ }
+
public class MeasureFormat extends android.icu.text.UFormat {
method public final boolean equals(java.lang.Object);
method public java.lang.StringBuffer format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition);
+ method public java.lang.StringBuilder formatMeasurePerUnit(android.icu.util.Measure, android.icu.util.MeasureUnit, java.lang.StringBuilder, java.text.FieldPosition);
method public final java.lang.String formatMeasures(android.icu.util.Measure...);
method public java.lang.StringBuilder formatMeasures(java.lang.StringBuilder, java.text.FieldPosition, android.icu.util.Measure...);
method public static android.icu.text.MeasureFormat getCurrencyFormat(android.icu.util.ULocale);
@@ -19308,6 +19426,14 @@
method public void setUpperCaseFirst(boolean);
}
+ public final class ScientificNumberFormatter {
+ method public java.lang.String format(java.lang.Object);
+ method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.util.ULocale, java.lang.String, java.lang.String);
+ method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.text.DecimalFormat, java.lang.String, java.lang.String);
+ method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.util.ULocale);
+ method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.text.DecimalFormat);
+ }
+
public abstract class SearchIterator {
ctor protected SearchIterator(java.text.CharacterIterator, android.icu.text.BreakIterator);
method public final int first();
@@ -20083,6 +20209,34 @@
method public long getToDate();
}
+ public final class EthiopicCalendar extends android.icu.util.CECalendar {
+ ctor public EthiopicCalendar();
+ ctor public EthiopicCalendar(android.icu.util.TimeZone);
+ ctor public EthiopicCalendar(java.util.Locale);
+ ctor public EthiopicCalendar(android.icu.util.ULocale);
+ ctor public EthiopicCalendar(android.icu.util.TimeZone, java.util.Locale);
+ ctor public EthiopicCalendar(android.icu.util.TimeZone, android.icu.util.ULocale);
+ ctor public EthiopicCalendar(int, int, int);
+ ctor public EthiopicCalendar(java.util.Date);
+ ctor public EthiopicCalendar(int, int, int, int, int, int);
+ method protected deprecated int handleGetExtendedYear();
+ method public boolean isAmeteAlemEra();
+ method public void setAmeteAlemEra(boolean);
+ field public static final int GENBOT = 8; // 0x8
+ field public static final int HAMLE = 10; // 0xa
+ field public static final int HEDAR = 2; // 0x2
+ field public static final int MEGABIT = 6; // 0x6
+ field public static final int MESKEREM = 0; // 0x0
+ field public static final int MIAZIA = 7; // 0x7
+ field public static final int NEHASSE = 11; // 0xb
+ field public static final int PAGUMEN = 12; // 0xc
+ field public static final int SENE = 9; // 0x9
+ field public static final int TAHSAS = 3; // 0x3
+ field public static final int TEKEMT = 1; // 0x1
+ field public static final int TER = 4; // 0x4
+ field public static final int YEKATIT = 5; // 0x5
+ }
+
public abstract interface Freezable<T> implements java.lang.Cloneable {
method public abstract T cloneAsThawed();
method public abstract T freeze();
@@ -20611,6 +20765,35 @@
enum_constant public static final android.icu.util.ULocale.Category FORMAT;
}
+ public final class UniversalTimeScale {
+ method public static android.icu.math.BigDecimal bigDecimalFrom(double, int);
+ method public static android.icu.math.BigDecimal bigDecimalFrom(long, int);
+ method public static android.icu.math.BigDecimal bigDecimalFrom(android.icu.math.BigDecimal, int);
+ method public static long from(long, int);
+ method public static long getTimeScaleValue(int, int);
+ method public static android.icu.math.BigDecimal toBigDecimal(long, int);
+ method public static android.icu.math.BigDecimal toBigDecimal(android.icu.math.BigDecimal, int);
+ method public static long toLong(long, int);
+ field public static final int DB2_TIME = 8; // 0x8
+ field public static final int DOTNET_DATE_TIME = 4; // 0x4
+ field public static final int EPOCH_OFFSET_PLUS_1_VALUE = 6; // 0x6
+ field public static final int EPOCH_OFFSET_VALUE = 1; // 0x1
+ field public static final int EXCEL_TIME = 7; // 0x7
+ field public static final int FROM_MAX_VALUE = 3; // 0x3
+ field public static final int FROM_MIN_VALUE = 2; // 0x2
+ field public static final int ICU4C_TIME = 2; // 0x2
+ field public static final int JAVA_TIME = 0; // 0x0
+ field public static final int MAC_OLD_TIME = 5; // 0x5
+ field public static final int MAC_TIME = 6; // 0x6
+ field public static final int MAX_SCALE = 10; // 0xa
+ field public static final int TO_MAX_VALUE = 5; // 0x5
+ field public static final int TO_MIN_VALUE = 4; // 0x4
+ field public static final int UNITS_VALUE = 0; // 0x0
+ field public static final int UNIX_MICROSECONDS_TIME = 9; // 0x9
+ field public static final int UNIX_TIME = 1; // 0x1
+ field public static final int WINDOWS_FILE_TIME = 3; // 0x3
+ }
+
public abstract interface ValueIterator {
method public abstract boolean next(android.icu.util.ValueIterator.Element);
method public abstract void reset();
@@ -25205,6 +25388,7 @@
field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
field public static final java.lang.String COLUMN_SERVICE_ID = "service_id";
field public static final java.lang.String COLUMN_SERVICE_TYPE = "service_type";
+ field public static final java.lang.String COLUMN_TRANSIENT = "transient";
field public static final java.lang.String COLUMN_TRANSPORT_STREAM_ID = "transport_stream_id";
field public static final java.lang.String COLUMN_TYPE = "type";
field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number";
@@ -25236,6 +25420,7 @@
field public static final java.lang.String TYPE_NTSC = "TYPE_NTSC";
field public static final java.lang.String TYPE_OTHER = "TYPE_OTHER";
field public static final java.lang.String TYPE_PAL = "TYPE_PAL";
+ field public static final java.lang.String TYPE_PREVIEW = "TYPE_PREVIEW";
field public static final java.lang.String TYPE_SECAM = "TYPE_SECAM";
field public static final java.lang.String TYPE_S_DMB = "TYPE_S_DMB";
field public static final java.lang.String TYPE_T_DMB = "TYPE_T_DMB";
@@ -25276,8 +25461,14 @@
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";
@@ -25287,6 +25478,7 @@
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";
@@ -25486,6 +25678,7 @@
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
@@ -25741,6 +25934,47 @@
}
+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 {
@@ -33157,14 +33391,22 @@
}
public class StorageManager {
+ method public long getCacheQuotaBytes();
+ method public long getCacheSizeBytes();
+ method public long getExternalCacheQuotaBytes();
+ method public long getExternalCacheSizeBytes();
method public java.lang.String getMountedObbPath(java.lang.String);
method public android.os.storage.StorageVolume getPrimaryStorageVolume();
method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
method public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
+ method public boolean isCacheBehaviorAtomic(java.io.File) throws java.io.IOException;
+ method public boolean isCacheBehaviorTombstone(java.io.File) throws java.io.IOException;
method public boolean isEncrypted(java.io.File);
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
+ method public void setCacheBehaviorAtomic(java.io.File, boolean) throws java.io.IOException;
+ method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
}
@@ -35703,6 +35945,16 @@
method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
}
+ public class FontsContract {
+ }
+
+ public static final class FontsContract.Columns implements android.provider.BaseColumns {
+ ctor public FontsContract.Columns();
+ field public static final java.lang.String STYLE = "font_style";
+ field public static final java.lang.String TTC_INDEX = "font_ttc_index";
+ field public static final java.lang.String VARIATION_SETTINGS = "font_variation_settings";
+ }
+
public final deprecated class LiveFolders implements android.provider.BaseColumns {
field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
field public static final java.lang.String DESCRIPTION = "description";
@@ -38662,6 +38914,7 @@
public static class NotificationListenerService.Ranking {
ctor public NotificationListenerService.Ranking();
+ method public boolean canShowBadge();
method public java.util.List<java.lang.String> getAdditionalPeople();
method public android.app.NotificationChannel getChannel();
method public int getImportance();
@@ -38703,7 +38956,6 @@
method public int getId();
method public java.lang.String getKey();
method public android.app.Notification getNotification();
- method public android.app.NotificationChannel getNotificationChannel();
method public java.lang.String getOverrideGroupKey();
method public java.lang.String getPackageName();
method public long getPostTime();
@@ -39164,6 +39416,7 @@
method public abstract int getMaxBufferSize();
method public abstract boolean hasFinished();
method public abstract boolean hasStarted();
+ method public default void rangeStart(int, int, int);
method public abstract int start(int, int, int);
}
@@ -39316,6 +39569,7 @@
method public void onError(java.lang.String, int);
method public abstract void onStart(java.lang.String);
method public void onStop(java.lang.String, boolean);
+ method public void onUtteranceRangeStart(java.lang.String, int, int);
}
public class Voice implements android.os.Parcelable {
@@ -40881,7 +41135,7 @@
field public static final java.lang.String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
field public static final java.lang.String ACTION_CONFIGURE_PHONE_ACCOUNT = "android.telecom.action.CONFIGURE_PHONE_ACCOUNT";
field public static final java.lang.String ACTION_DEFAULT_DIALER_CHANGED = "android.telecom.action.DEFAULT_DIALER_CHANGED";
- field public static final java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
+ field public static final deprecated java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
field public static final java.lang.String ACTION_PHONE_ACCOUNT_REGISTERED = "android.telecom.action.PHONE_ACCOUNT_REGISTERED";
field public static final java.lang.String ACTION_PHONE_ACCOUNT_UNREGISTERED = "android.telecom.action.PHONE_ACCOUNT_UNREGISTERED";
field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS";
@@ -40991,7 +41245,8 @@
field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
- field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final deprecated java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY = "carrier_vvm_package_name_string_array";
field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool";
field public static final java.lang.String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
@@ -41083,9 +41338,13 @@
field public static final java.lang.String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final java.lang.String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
field public static final java.lang.String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
+ field public static final java.lang.String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string";
field public static final java.lang.String KEY_VVM_DESTINATION_NUMBER_STRING = "vvm_destination_number_string";
+ field public static final java.lang.String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY = "vvm_disabled_capabilities_string_array";
+ field public static final java.lang.String KEY_VVM_LEGACY_MODE_ENABLED_BOOL = "vvm_legacy_mode_enabled_bool";
field public static final java.lang.String KEY_VVM_PORT_NUMBER_INT = "vvm_port_number_int";
field public static final java.lang.String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
+ field public static final java.lang.String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
field public static final java.lang.String KEY_VVM_TYPE_STRING = "vvm_type_string";
field public static final java.lang.String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
}
@@ -41668,6 +41927,7 @@
method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+ method public boolean isConcurrentVoiceAndDataAllowed();
method public boolean isDataConnectivityPossible();
method public boolean isHearingAidCompatibilitySupported();
method public boolean isIdle();
@@ -42470,12 +42730,15 @@
method public android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public int getComponentEnabledSetting(android.content.ComponentName);
method public android.graphics.drawable.Drawable getDefaultActivityIcon();
+ method public java.lang.String getDefaultBrowserPackageNameAsUser(int);
method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
method public java.lang.String getInstallerPackageName(java.lang.String);
method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
+ method public int getIntentVerificationStatusAsUser(java.lang.String, int);
method public android.content.Intent getLaunchIntentForPackage(java.lang.String);
method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
method public java.lang.String getNameForUid(int);
@@ -42529,7 +42792,9 @@
method public void setApplicationCategoryHint(java.lang.String, int);
method public void setApplicationEnabledSetting(java.lang.String, int, int);
method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
+ method public boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
method public void setInstallerPackageName(java.lang.String, java.lang.String);
+ method public boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
method public void verifyPendingInstall(int, int);
@@ -42733,6 +42998,65 @@
method public android.text.Editable newEditable(java.lang.CharSequence);
}
+ public final class FontConfig implements android.os.Parcelable {
+ ctor public FontConfig();
+ ctor public FontConfig(android.text.FontConfig);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Alias> getAliases();
+ method public java.util.List<android.text.FontConfig.Family> getFamilies();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR;
+ }
+
+ public static final class FontConfig.Alias implements android.os.Parcelable {
+ ctor public FontConfig.Alias(java.lang.String, java.lang.String, int);
+ method public int describeContents();
+ method public java.lang.String getName();
+ method public java.lang.String getToName();
+ method public int getWeight();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Alias> CREATOR;
+ }
+
+ public static final class FontConfig.Axis implements android.os.Parcelable {
+ ctor public FontConfig.Axis(int, float);
+ method public int describeContents();
+ method public float getStyleValue();
+ method public int getTag();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Axis> CREATOR;
+ }
+
+ public static final class FontConfig.Family implements android.os.Parcelable {
+ ctor public FontConfig.Family(java.lang.String, java.util.List<android.text.FontConfig.Font>, java.lang.String, java.lang.String);
+ ctor public FontConfig.Family(android.text.FontConfig.Family);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Font> getFonts();
+ method public java.lang.String getLanguage();
+ method public java.lang.String getName();
+ method public java.lang.String getVariant();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Family> CREATOR;
+ }
+
+ public static final class FontConfig.Font implements android.os.Parcelable {
+ ctor public FontConfig.Font(java.lang.String, int, java.util.List<android.text.FontConfig.Axis>, int, boolean);
+ ctor public FontConfig.Font(android.text.FontConfig.Font);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Axis> getAxes();
+ method public android.os.ParcelFileDescriptor getFd();
+ method public java.lang.String getFontName();
+ method public int getTtcIndex();
+ method public int getWeight();
+ method public boolean isItalic();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Font> CREATOR;
+ }
+
+ public final class FontManager {
+ method public android.text.FontConfig getSystemFonts();
+ }
+
public abstract interface GetChars implements java.lang.CharSequence {
method public abstract void getChars(int, int, char[], int);
}
@@ -43088,22 +43412,6 @@
method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
}
- public abstract interface TextAssistant {
- method public abstract void addLinks(android.text.Spannable, int);
- method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
- public class TextClassification {
- ctor public TextClassification();
- method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence();
- }
-
- public final class TextClassificationManager implements android.text.TextAssistant {
- method public void addLinks(android.text.Spannable, int);
- method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence);
- method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
public abstract interface TextDirectionHeuristic {
method public abstract boolean isRtl(char[], int, int);
method public abstract boolean isRtl(java.lang.CharSequence, int, int);
@@ -43119,13 +43427,6 @@
field public static final android.text.TextDirectionHeuristic RTL;
}
- public final class TextLanguage {
- ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>);
- method public int getEndIndex();
- method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence();
- method public int getStartIndex();
- }
-
public class TextPaint extends android.graphics.Paint {
ctor public TextPaint();
ctor public TextPaint(int);
@@ -43138,13 +43439,6 @@
field public int linkColor;
}
- public class TextSelection {
- ctor public TextSelection();
- method public int getSelectionEndIndex();
- method public int getSelectionStartIndex();
- method public android.text.TextClassification getTextClassification();
- }
-
public class TextUtils {
method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String);
method public static java.lang.CharSequence concat(java.lang.CharSequence...);
@@ -44698,6 +44992,7 @@
method public static int getTagCode(java.lang.String);
method public static java.lang.String getTagName(int);
method public static void readEvents(int[], java.util.Collection<android.util.EventLog.Event>) throws java.io.IOException;
+ method public static void readEventsOnWrapping(int[], long, java.util.Collection<android.util.EventLog.Event>) throws java.io.IOException;
method public static int writeEvent(int, int);
method public static int writeEvent(int, long);
method public static int writeEvent(int, float);
@@ -45398,7 +45693,9 @@
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
@@ -45467,7 +45764,7 @@
method public android.view.View findNearestTouchable(android.view.ViewGroup, int, int, int, int[]);
method public final android.view.View findNextFocus(android.view.ViewGroup, android.view.View, int);
method public android.view.View findNextFocusFromRect(android.view.ViewGroup, android.graphics.Rect, int);
- method public android.view.View findNextKeyboardNavigationGroup(int, android.view.View, android.view.View, int);
+ method public android.view.View findNextKeyboardNavigationCluster(android.view.View, android.view.View, int);
method public static android.view.FocusFinder getInstance();
}
@@ -46765,7 +47062,7 @@
method public void addChildrenForAccessibility(java.util.ArrayList<android.view.View>);
method public void addFocusables(java.util.ArrayList<android.view.View>, int);
method public void addFocusables(java.util.ArrayList<android.view.View>, int, int);
- method public void addKeyboardNavigationGroups(int, java.util.Collection<android.view.View>, int);
+ method public void addKeyboardNavigationClusters(java.util.Collection<android.view.View>, int);
method public void addOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener);
method public void addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener);
method public void addTouchables(java.util.ArrayList<android.view.View>);
@@ -46887,6 +47184,7 @@
method public float getElevation();
method public boolean getFilterTouchesWhenObscured();
method public boolean getFitsSystemWindows();
+ method public int getFocusable();
method public java.util.ArrayList<android.view.View> getFocusables(int);
method public void getFocusedRect(android.graphics.Rect);
method public android.graphics.drawable.Drawable getForeground();
@@ -46929,7 +47227,6 @@
method public int getNextFocusLeftId();
method public int getNextFocusRightId();
method public int getNextFocusUpId();
- method public int getNextSectionForwardId();
method public android.view.View.OnFocusChangeListener getOnFocusChangeListener();
method public android.view.ViewOutlineProvider getOutlineProvider();
method public int getOverScrollMode();
@@ -46973,7 +47270,6 @@
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();
@@ -47035,7 +47331,6 @@
method public boolean isInLayout();
method public boolean isInTouchMode();
method public final boolean isKeyboardNavigationCluster();
- method public final boolean isKeyboardNavigationSection();
method public boolean isLaidOut();
method public boolean isLayoutDirectionResolved();
method public boolean isLayoutRequested();
@@ -47058,7 +47353,7 @@
method public boolean isVerticalFadingEdgeEnabled();
method public boolean isVerticalScrollBarEnabled();
method public void jumpDrawablesToCurrentState();
- method public android.view.View keyboardNavigationGroupSearch(int, android.view.View, int);
+ method public android.view.View keyboardNavigationClusterSearch(android.view.View, int);
method public void layout(int, int, int, int);
method public final void measure(int, int);
method protected static int[] mergeDrawableStates(int[], int[]);
@@ -47193,6 +47488,7 @@
method public void setFilterTouchesWhenObscured(boolean);
method public void setFitsSystemWindows(boolean);
method public void setFocusable(boolean);
+ method public void setFocusable(int);
method public void setFocusableInTouchMode(boolean);
method public void setFocusedByDefault(boolean);
method public void setForeground(android.graphics.drawable.Drawable);
@@ -47208,7 +47504,6 @@
method public void setImportantForAccessibility(int);
method public void setKeepScreenOn(boolean);
method public void setKeyboardNavigationCluster(boolean);
- method public void setKeyboardNavigationSection(boolean);
method public void setLabelFor(int);
method public void setLayerPaint(android.graphics.Paint);
method public void setLayerType(int, android.graphics.Paint);
@@ -47226,7 +47521,6 @@
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);
@@ -47275,7 +47569,6 @@
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);
@@ -47333,8 +47626,10 @@
field protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET;
field public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 2; // 0x2
field public static final int FIND_VIEWS_WITH_TEXT = 1; // 0x1
+ field public static final int FOCUSABLE = 1; // 0x1
field public static final int FOCUSABLES_ALL = 0; // 0x0
field public static final int FOCUSABLES_TOUCH_MODE = 1; // 0x1
+ field public static final int FOCUSABLE_AUTO = 16; // 0x10
field protected static final int[] FOCUSED_SELECTED_STATE_SET;
field protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
field protected static final int[] FOCUSED_STATE_SET;
@@ -47353,8 +47648,6 @@
field public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1
field public static final int INVISIBLE = 4; // 0x4
field public static final int KEEP_SCREEN_ON = 67108864; // 0x4000000
- field public static final int KEYBOARD_NAVIGATION_GROUP_CLUSTER = 1; // 0x1
- field public static final int KEYBOARD_NAVIGATION_GROUP_SECTION = 2; // 0x2
field public static final int LAYER_TYPE_HARDWARE = 2; // 0x2
field public static final int LAYER_TYPE_NONE = 0; // 0x0
field public static final int LAYER_TYPE_SOFTWARE = 1; // 0x1
@@ -47366,6 +47659,7 @@
field public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff
field public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000
field public static final int MEASURED_STATE_TOO_SMALL = 16777216; // 0x1000000
+ field public static final int NOT_FOCUSABLE = 0; // 0x0
field public static final int NO_ID = -1; // 0xffffffff
field public static final int OVER_SCROLL_ALWAYS = 0; // 0x0
field public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; // 0x1
@@ -47873,7 +48167,7 @@
method public abstract boolean isLayoutRequested();
method public abstract boolean isTextAlignmentResolved();
method public abstract boolean isTextDirectionResolved();
- method public abstract android.view.View keyboardNavigationGroupSearch(int, android.view.View, int);
+ method public abstract android.view.View keyboardNavigationClusterSearch(android.view.View, int);
method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int);
method public abstract boolean onNestedFling(android.view.View, float, float, boolean);
method public abstract boolean onNestedPreFling(android.view.View, float, float);
@@ -49663,6 +49957,83 @@
}
+package android.view.textclassifier {
+
+ public abstract interface LinksInfo {
+ method public abstract boolean apply(java.lang.CharSequence);
+ }
+
+ public final class TextClassificationManager {
+ method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence);
+ method public android.view.textclassifier.TextClassifier getDefaultTextClassifier();
+ }
+
+ public final class TextClassificationResult {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public android.graphics.drawable.Drawable getIcon();
+ method public android.content.Intent getIntent();
+ method public java.lang.CharSequence getLabel();
+ method public android.view.View.OnClickListener getOnClickListener();
+ method public java.lang.String getText();
+ }
+
+ public static final class TextClassificationResult.Builder {
+ ctor public TextClassificationResult.Builder();
+ method public android.view.textclassifier.TextClassificationResult build();
+ method public android.view.textclassifier.TextClassificationResult.Builder setEntityType(java.lang.String, float);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIcon(android.graphics.drawable.Drawable);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIntent(android.content.Intent);
+ method public android.view.textclassifier.TextClassificationResult.Builder setLabel(java.lang.String);
+ method public android.view.textclassifier.TextClassificationResult.Builder setOnClickListener(android.view.View.OnClickListener);
+ method public android.view.textclassifier.TextClassificationResult.Builder setText(java.lang.String);
+ }
+
+ public abstract interface TextClassifier {
+ method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
+ method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
+ method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
+ field public static final android.view.textclassifier.TextClassifier NO_OP;
+ field public static final java.lang.String TYPE_ADDRESS = "address";
+ field public static final java.lang.String TYPE_EMAIL = "email";
+ field public static final java.lang.String TYPE_OTHER = "other";
+ field public static final java.lang.String TYPE_PHONE = "phone";
+ }
+
+ public static abstract class TextClassifier.EntityType implements java.lang.annotation.Annotation {
+ }
+
+ public final class TextLanguage {
+ method public float getConfidenceScore(java.util.Locale);
+ method public int getEndIndex();
+ method public java.util.Locale getLanguage(int);
+ method public int getLanguageCount();
+ method public int getStartIndex();
+ }
+
+ public static final class TextLanguage.Builder {
+ ctor public TextLanguage.Builder(int, int);
+ method public android.view.textclassifier.TextLanguage build();
+ method public android.view.textclassifier.TextLanguage.Builder setLanguage(java.util.Locale, float);
+ }
+
+ public final class TextSelection {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public int getSelectionEndIndex();
+ method public int getSelectionStartIndex();
+ }
+
+ public static final class TextSelection.Builder {
+ ctor public TextSelection.Builder(int, int);
+ method public android.view.textclassifier.TextSelection build();
+ method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float);
+ }
+
+}
+
package android.view.textservice {
public final class SentenceSuggestionsInfo implements android.os.Parcelable {
@@ -49947,6 +50318,7 @@
public abstract class RenderProcessGoneDetail {
ctor public RenderProcessGoneDetail();
method public abstract boolean didCrash();
+ method public abstract int rendererPriorityAtExit();
}
public class ServiceWorkerClient {
@@ -50402,6 +50774,8 @@
method public deprecated java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String);
method public java.lang.String getOriginalUrl();
method public int getProgress();
+ method public boolean getRendererPriorityWaivedWhenNotVisible();
+ method public int getRendererRequestedPriority();
method public deprecated float getScale();
method public android.webkit.WebSettings getSettings();
method public java.lang.String getTitle();
@@ -50450,6 +50824,7 @@
method public deprecated void setMapTrackballToArrowKeys(boolean);
method public void setNetworkAvailable(boolean);
method public deprecated void setPictureListener(android.webkit.WebView.PictureListener);
+ method public void setRendererPriorityPolicy(int, boolean);
method public deprecated void setVerticalScrollbarOverlay(boolean);
method public void setWebChromeClient(android.webkit.WebChromeClient);
method public static void setWebContentsDebuggingEnabled(boolean);
@@ -50460,6 +50835,9 @@
method public boolean zoomIn();
method public boolean zoomOut();
field public static final java.lang.String DATA_REDUCTION_PROXY_SETTING_CHANGED = "android.webkit.DATA_REDUCTION_PROXY_SETTING_CHANGED";
+ field public static final int RENDERER_PRIORITY_BOUND = 1; // 0x1
+ field public static final int RENDERER_PRIORITY_IMPORTANT = 2; // 0x2
+ field public static final int RENDERER_PRIORITY_WAIVED = 0; // 0x0
field public static final java.lang.String SCHEME_GEO = "geo:0,0?q=";
field public static final java.lang.String SCHEME_MAILTO = "mailto:";
field public static final java.lang.String SCHEME_TEL = "tel:";
@@ -50593,6 +50971,7 @@
method public java.lang.String getErrorString(android.content.Context, int);
method public int getPackageId(android.content.res.Resources, java.lang.String);
method public void invokeDrawGlFunctor(android.view.View, long, boolean);
+ method public boolean isMultiProcessEnabled();
method public boolean isTraceTagEnabled();
method public void setOnTraceEnabledChangeListener(android.webkit.WebViewDelegate.OnTraceEnabledChangeListener);
}
@@ -50681,6 +51060,8 @@
method public abstract java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String);
method public abstract java.lang.String getOriginalUrl();
method public abstract int getProgress();
+ method public abstract boolean getRendererPriorityWaivedWhenNotVisible();
+ method public abstract int getRendererRequestedPriority();
method public abstract float getScale();
method public abstract android.webkit.WebViewProvider.ScrollDelegate getScrollDelegate();
method public abstract android.webkit.WebSettings getSettings();
@@ -50735,6 +51116,7 @@
method public abstract void setMapTrackballToArrowKeys(boolean);
method public abstract void setNetworkAvailable(boolean);
method public abstract void setPictureListener(android.webkit.WebView.PictureListener);
+ method public abstract void setRendererPriorityPolicy(int, boolean);
method public abstract void setVerticalScrollbarOverlay(boolean);
method public abstract void setWebChromeClient(android.webkit.WebChromeClient);
method public abstract void setWebViewClient(android.webkit.WebViewClient);
@@ -53027,6 +53409,10 @@
method public void endBatchEdit();
method public boolean extractText(android.view.inputmethod.ExtractedTextRequest, android.view.inputmethod.ExtractedText);
method public final int getAutoLinkMask();
+ method public int getAutoSizeMaxTextSize();
+ method public int getAutoSizeMinTextSize();
+ method public int getAutoSizeStepGranularity();
+ method public int getAutoSizeTextType();
method public int getBreakStrategy();
method public int getCompoundDrawablePadding();
method public android.content.res.ColorStateList getCompoundDrawableTintList();
@@ -53098,7 +53484,7 @@
method public float getShadowRadius();
method public final boolean getShowSoftInputOnFocus();
method public java.lang.CharSequence getText();
- method public android.text.TextAssistant getTextAssistant();
+ method public android.view.textclassifier.TextClassifier getTextClassifier();
method public final android.content.res.ColorStateList getTextColors();
method public java.util.Locale getTextLocale();
method public android.os.LocaleList getTextLocales();
@@ -53135,6 +53521,10 @@
method public void removeTextChangedListener(android.text.TextWatcher);
method public void setAllCaps(boolean);
method public final void setAutoLinkMask(int);
+ method public void setAutoSizeMaxTextSize(int, float);
+ method public void setAutoSizeMinTextSize(int, float);
+ method public void setAutoSizeStepGranularity(int, float);
+ method public void setAutoSizeTextType(int);
method public void setBreakStrategy(int);
method public void setCompoundDrawablePadding(int);
method public void setCompoundDrawableTintList(android.content.res.ColorStateList);
@@ -53210,7 +53600,7 @@
method public final void setText(int, android.widget.TextView.BufferType);
method public void setTextAppearance(int);
method public deprecated void setTextAppearance(android.content.Context, int);
- method public void setTextAssistant(android.text.TextAssistant);
+ method public void setTextClassifier(android.view.textclassifier.TextClassifier);
method public void setTextColor(int);
method public void setTextColor(android.content.res.ColorStateList);
method public void setTextIsSelectable(boolean);
@@ -53225,8 +53615,8 @@
method public void setTypeface(android.graphics.Typeface, int);
method public void setTypeface(android.graphics.Typeface);
method public void setWidth(int);
- field public static final int AUTO_SIZE_TYPE_NONE = 0; // 0x0
- field public static final int AUTO_SIZE_TYPE_XY = 1; // 0x1
+ field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
+ field public static final int AUTO_SIZE_TEXT_TYPE_XY = 1; // 0x1
}
public static final class TextView.BufferType extends java.lang.Enum {
diff --git a/api/test-current.txt b/api/test-current.txt
index 66071a2..94b4a00 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -203,6 +203,8 @@
public static final class R.attr {
ctor public R.attr();
+ field public static final int __removed0 = 16844097; // 0x1010541
+ field public static final int __removed1 = 16844099; // 0x1010543
field public static final int absListViewStyle = 16842858; // 0x101006a
field public static final int accessibilityEventTypes = 16843648; // 0x1010380
field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
@@ -758,7 +760,6 @@
field public static final int keyboardLayout = 16843691; // 0x10103ab
field public static final int keyboardMode = 16843341; // 0x101024d
field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
- field public static final int keyboardNavigationSection = 16844097; // 0x1010541
field public static final int keycode = 16842949; // 0x10100c5
field public static final int killAfterRestore = 16843420; // 0x101029c
field public static final int label = 16842753; // 0x1010001
@@ -908,7 +909,6 @@
field public static final int nextFocusLeft = 16842977; // 0x10100e1
field public static final int nextFocusRight = 16842978; // 0x10100e2
field public static final int nextFocusUp = 16842979; // 0x10100e3
- field public static final int nextSectionForward = 16844099; // 0x1010543
field public static final int noHistory = 16843309; // 0x101022d
field public static final int normalScreens = 16843397; // 0x1010285
field public static final int notificationTimeout = 16843651; // 0x1010383
@@ -1169,6 +1169,7 @@
field public static final int spinnerStyle = 16842881; // 0x1010081
field public static final int spinnersShown = 16843595; // 0x101034b
field public static final int splitMotionEvents = 16843503; // 0x10102ef
+ field public static final int splitName = 16844107; // 0x101054b
field public static final int splitTrack = 16843852; // 0x101044c
field public static final int spotShadowAlpha = 16843967; // 0x10104bf
field public static final int src = 16843033; // 0x1010119
@@ -1817,6 +1818,7 @@
field public static final int tabs = 16908307; // 0x1020013
field public static final int text1 = 16908308; // 0x1020014
field public static final int text2 = 16908309; // 0x1020015
+ field public static final int textAssist = 16908353; // 0x1020041
field public static final int title = 16908310; // 0x1020016
field public static final int toggle = 16908311; // 0x1020017
field public static final int undo = 16908338; // 0x1020032
@@ -3538,7 +3540,6 @@
method public int getRequestedOrientation();
method public final android.view.SearchEvent getSearchEvent();
method public int getTaskId();
- method public android.text.TextAssistant getTextAssistant();
method public final java.lang.CharSequence getTitle();
method public final int getTitleColor();
method public android.app.VoiceInteractor getVoiceInteractor();
@@ -3688,7 +3689,6 @@
method public final void setResult(int, android.content.Intent);
method public final deprecated void setSecondaryProgress(int);
method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
- method public void setTextAssistant(android.text.TextAssistant);
method public void setTitle(java.lang.CharSequence);
method public void setTitle(int);
method public deprecated void setTitleColor(int);
@@ -5594,6 +5594,18 @@
field public static final int STYLE_SPINNER = 0; // 0x0
}
+ public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+ ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
+ method public int describeContents();
+ method public android.app.PendingIntent getUserAction();
+ method public java.lang.CharSequence getUserActionTitle();
+ method public java.lang.CharSequence getUserMessage();
+ method public void showAsDialog(android.app.Activity);
+ method public void showAsNotification(android.content.Context);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
+ }
+
public final class RemoteAction implements android.os.Parcelable {
ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener);
method public android.app.RemoteAction clone();
@@ -6388,8 +6400,12 @@
public final class SystemUpdateInfo implements android.os.Parcelable {
method public int describeContents();
method public long getReceivedTime();
+ method public int getSecurityPatchState();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.admin.SystemUpdateInfo> CREATOR;
+ field public static final int SECURITY_PATCH_STATE_FALSE = 1; // 0x1
+ field public static final int SECURITY_PATCH_STATE_TRUE = 2; // 0x2
+ field public static final int SECURITY_PATCH_STATE_UNKNOWN = 0; // 0x0
}
public class SystemUpdatePolicy implements android.os.Parcelable {
@@ -8496,6 +8512,7 @@
field public static final java.lang.String DOWNLOAD_SERVICE = "download";
field public static final java.lang.String DROPBOX_SERVICE = "dropbox";
field public static final java.lang.String FINGERPRINT_SERVICE = "fingerprint";
+ field public static final java.lang.String FONT_SERVICE = "font";
field public static final java.lang.String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
field public static final java.lang.String INPUT_SERVICE = "input";
@@ -9770,6 +9787,7 @@
field public boolean enabled;
field public boolean exported;
field public java.lang.String processName;
+ field public java.lang.String splitName;
}
public class ConfigurationInfo implements android.os.Parcelable {
@@ -10755,6 +10773,7 @@
method public android.graphics.drawable.Drawable getDrawable(int, android.content.res.Resources.Theme) throws android.content.res.Resources.NotFoundException;
method public deprecated android.graphics.drawable.Drawable getDrawableForDensity(int, int) throws android.content.res.Resources.NotFoundException;
method public android.graphics.drawable.Drawable getDrawableForDensity(int, int, android.content.res.Resources.Theme);
+ method public android.graphics.Typeface getFont(int) throws android.content.res.Resources.NotFoundException;
method public float getFraction(int, int, int);
method public int getIdentifier(java.lang.String, java.lang.String, java.lang.String);
method public int[] getIntArray(int) throws android.content.res.Resources.NotFoundException;
@@ -13240,6 +13259,7 @@
}
public class Typeface {
+ method public static void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback);
method public static android.graphics.Typeface create(java.lang.String, int);
method public static android.graphics.Typeface create(android.graphics.Typeface, int);
method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
@@ -13260,6 +13280,14 @@
field public static final android.graphics.Typeface SERIF;
}
+ public static abstract interface Typeface.FontRequestCallback {
+ method public abstract void onTypefaceRequestFailed(int);
+ method public abstract void onTypefaceRetrieved(android.graphics.Typeface);
+ field public static final int FAIL_REASON_FONT_LOAD_ERROR = 1; // 0x1
+ field public static final int FAIL_REASON_FONT_NOT_FOUND = 2; // 0x2
+ field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = 0; // 0x0
+ }
+
public class Xfermode {
ctor public Xfermode();
}
@@ -13814,6 +13842,19 @@
}
+package android.graphics.fonts {
+
+ public final class FontRequest implements android.os.Parcelable {
+ ctor public FontRequest(java.lang.String, java.lang.String);
+ method public int describeContents();
+ method public java.lang.String getProviderAuthority();
+ method public java.lang.String getQuery();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR;
+ }
+
+}
+
package android.graphics.pdf {
public class PdfDocument {
@@ -14141,6 +14182,37 @@
method public float getZ();
}
+ public final class HardwareBuffer implements android.os.Parcelable {
+ method public static android.hardware.HardwareBuffer create(int, int, int, int, long);
+ method public int describeContents();
+ method public void destroy();
+ method public int getFormat();
+ method public int getHeight();
+ method public int getLayers();
+ method public long getUsage();
+ method public int getWidth();
+ method public boolean isDestroyed();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.HardwareBuffer> CREATOR;
+ field public static final int RGBA_8888 = 1; // 0x1
+ field public static final int RGBA_FP16 = 5; // 0x5
+ field public static final int RGBX_8888 = 2; // 0x2
+ field public static final int RGB_565 = 4; // 0x4
+ field public static final int RGB_888 = 3; // 0x3
+ field public static final long USAGE0_CPU_READ = 2L; // 0x2L
+ field public static final long USAGE0_CPU_READ_OFTEN = 6L; // 0x6L
+ field public static final long USAGE0_CPU_WRITE = 32L; // 0x20L
+ field public static final long USAGE0_CPU_WRITE_OFTEN = 96L; // 0x60L
+ field public static final long USAGE0_GPU_COLOR_OUTPUT = 2048L; // 0x800L
+ field public static final long USAGE0_GPU_CUBEMAP = 8192L; // 0x2000L
+ field public static final long USAGE0_GPU_DATA_BUFFER = 16384L; // 0x4000L
+ field public static final long USAGE0_GPU_SAMPLED_IMAGE = 1024L; // 0x400L
+ field public static final long USAGE0_GPU_STORAGE_IMAGE = 3072L; // 0xc00L
+ field public static final long USAGE0_PROTECTED_CONTENT = 262144L; // 0x40000L
+ field public static final long USAGE0_SENSOR_DIRECT_DATA = 536870912L; // 0x20000000L
+ field public static final long USAGE0_VIDEO_ENCODE = 2097152L; // 0x200000L
+ }
+
public final class Sensor {
method public int getFifoMaxEventCount();
method public int getFifoReservedEventCount();
@@ -17588,6 +17660,15 @@
method public boolean isTransitionalDifferent();
}
+ public final class ListFormatter {
+ method public java.lang.String format(java.lang.Object...);
+ method public java.lang.String format(java.util.Collection<?>);
+ method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale);
+ method public static android.icu.text.ListFormatter getInstance(java.util.Locale);
+ method public static android.icu.text.ListFormatter getInstance();
+ method public java.lang.String getPatternForNumItems(int);
+ }
+
public abstract class LocaleDisplayNames {
method public abstract android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type);
method public abstract android.icu.text.LocaleDisplayNames.DialectHandling getDialectHandling();
@@ -17597,6 +17678,8 @@
method public static android.icu.text.LocaleDisplayNames getInstance(android.icu.util.ULocale, android.icu.text.DisplayContext...);
method public static android.icu.text.LocaleDisplayNames getInstance(java.util.Locale, android.icu.text.DisplayContext...);
method public abstract android.icu.util.ULocale getLocale();
+ method public java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiList(java.util.Set<android.icu.util.ULocale>, boolean, java.util.Comparator<java.lang.Object>);
+ method public abstract java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiListCompareWholeItems(java.util.Set<android.icu.util.ULocale>, java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem>);
method public abstract java.lang.String keyDisplayName(java.lang.String);
method public abstract java.lang.String keyValueDisplayName(java.lang.String, java.lang.String);
method public abstract java.lang.String languageDisplayName(java.lang.String);
@@ -17616,9 +17699,19 @@
enum_constant public static final android.icu.text.LocaleDisplayNames.DialectHandling STANDARD_NAMES;
}
+ public static class LocaleDisplayNames.UiListItem {
+ ctor public LocaleDisplayNames.UiListItem(android.icu.util.ULocale, android.icu.util.ULocale, java.lang.String, java.lang.String);
+ method public static java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem> getComparator(java.util.Comparator<java.lang.Object>, boolean);
+ field public final android.icu.util.ULocale minimized;
+ field public final android.icu.util.ULocale modified;
+ field public final java.lang.String nameInDisplayLocale;
+ field public final java.lang.String nameInSelf;
+ }
+
public class MeasureFormat extends android.icu.text.UFormat {
method public final boolean equals(java.lang.Object);
method public java.lang.StringBuffer format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition);
+ method public java.lang.StringBuilder formatMeasurePerUnit(android.icu.util.Measure, android.icu.util.MeasureUnit, java.lang.StringBuilder, java.text.FieldPosition);
method public final java.lang.String formatMeasures(android.icu.util.Measure...);
method public java.lang.StringBuilder formatMeasures(java.lang.StringBuilder, java.text.FieldPosition, android.icu.util.Measure...);
method public static android.icu.text.MeasureFormat getCurrencyFormat(android.icu.util.ULocale);
@@ -18087,6 +18180,14 @@
method public void setUpperCaseFirst(boolean);
}
+ public final class ScientificNumberFormatter {
+ method public java.lang.String format(java.lang.Object);
+ method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.util.ULocale, java.lang.String, java.lang.String);
+ method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.text.DecimalFormat, java.lang.String, java.lang.String);
+ method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.util.ULocale);
+ method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.text.DecimalFormat);
+ }
+
public abstract class SearchIterator {
ctor protected SearchIterator(java.text.CharacterIterator, android.icu.text.BreakIterator);
method public final int first();
@@ -18862,6 +18963,34 @@
method public long getToDate();
}
+ public final class EthiopicCalendar extends android.icu.util.CECalendar {
+ ctor public EthiopicCalendar();
+ ctor public EthiopicCalendar(android.icu.util.TimeZone);
+ ctor public EthiopicCalendar(java.util.Locale);
+ ctor public EthiopicCalendar(android.icu.util.ULocale);
+ ctor public EthiopicCalendar(android.icu.util.TimeZone, java.util.Locale);
+ ctor public EthiopicCalendar(android.icu.util.TimeZone, android.icu.util.ULocale);
+ ctor public EthiopicCalendar(int, int, int);
+ ctor public EthiopicCalendar(java.util.Date);
+ ctor public EthiopicCalendar(int, int, int, int, int, int);
+ method protected deprecated int handleGetExtendedYear();
+ method public boolean isAmeteAlemEra();
+ method public void setAmeteAlemEra(boolean);
+ field public static final int GENBOT = 8; // 0x8
+ field public static final int HAMLE = 10; // 0xa
+ field public static final int HEDAR = 2; // 0x2
+ field public static final int MEGABIT = 6; // 0x6
+ field public static final int MESKEREM = 0; // 0x0
+ field public static final int MIAZIA = 7; // 0x7
+ field public static final int NEHASSE = 11; // 0xb
+ field public static final int PAGUMEN = 12; // 0xc
+ field public static final int SENE = 9; // 0x9
+ field public static final int TAHSAS = 3; // 0x3
+ field public static final int TEKEMT = 1; // 0x1
+ field public static final int TER = 4; // 0x4
+ field public static final int YEKATIT = 5; // 0x5
+ }
+
public abstract interface Freezable<T> implements java.lang.Cloneable {
method public abstract T cloneAsThawed();
method public abstract T freeze();
@@ -19390,6 +19519,35 @@
enum_constant public static final android.icu.util.ULocale.Category FORMAT;
}
+ public final class UniversalTimeScale {
+ method public static android.icu.math.BigDecimal bigDecimalFrom(double, int);
+ method public static android.icu.math.BigDecimal bigDecimalFrom(long, int);
+ method public static android.icu.math.BigDecimal bigDecimalFrom(android.icu.math.BigDecimal, int);
+ method public static long from(long, int);
+ method public static long getTimeScaleValue(int, int);
+ method public static android.icu.math.BigDecimal toBigDecimal(long, int);
+ method public static android.icu.math.BigDecimal toBigDecimal(android.icu.math.BigDecimal, int);
+ method public static long toLong(long, int);
+ field public static final int DB2_TIME = 8; // 0x8
+ field public static final int DOTNET_DATE_TIME = 4; // 0x4
+ field public static final int EPOCH_OFFSET_PLUS_1_VALUE = 6; // 0x6
+ field public static final int EPOCH_OFFSET_VALUE = 1; // 0x1
+ field public static final int EXCEL_TIME = 7; // 0x7
+ field public static final int FROM_MAX_VALUE = 3; // 0x3
+ field public static final int FROM_MIN_VALUE = 2; // 0x2
+ field public static final int ICU4C_TIME = 2; // 0x2
+ field public static final int JAVA_TIME = 0; // 0x0
+ field public static final int MAC_OLD_TIME = 5; // 0x5
+ field public static final int MAC_TIME = 6; // 0x6
+ field public static final int MAX_SCALE = 10; // 0xa
+ field public static final int TO_MAX_VALUE = 5; // 0x5
+ field public static final int TO_MIN_VALUE = 4; // 0x4
+ field public static final int UNITS_VALUE = 0; // 0x0
+ field public static final int UNIX_MICROSECONDS_TIME = 9; // 0x9
+ field public static final int UNIX_TIME = 1; // 0x1
+ field public static final int WINDOWS_FILE_TIME = 3; // 0x3
+ }
+
public abstract interface ValueIterator {
method public abstract boolean next(android.icu.util.ValueIterator.Element);
method public abstract void reset();
@@ -23621,6 +23779,7 @@
field public static final java.lang.String TYPE_NTSC = "TYPE_NTSC";
field public static final java.lang.String TYPE_OTHER = "TYPE_OTHER";
field public static final java.lang.String TYPE_PAL = "TYPE_PAL";
+ field public static final java.lang.String TYPE_PREVIEW = "TYPE_PREVIEW";
field public static final java.lang.String TYPE_SECAM = "TYPE_SECAM";
field public static final java.lang.String TYPE_S_DMB = "TYPE_S_DMB";
field public static final java.lang.String TYPE_T_DMB = "TYPE_T_DMB";
@@ -23661,8 +23820,14 @@
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";
@@ -23791,6 +23956,7 @@
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
@@ -30530,14 +30696,22 @@
}
public class StorageManager {
+ method public long getCacheQuotaBytes();
+ method public long getCacheSizeBytes();
+ method public long getExternalCacheQuotaBytes();
+ method public long getExternalCacheSizeBytes();
method public java.lang.String getMountedObbPath(java.lang.String);
method public android.os.storage.StorageVolume getPrimaryStorageVolume();
method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
method public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
+ method public boolean isCacheBehaviorAtomic(java.io.File) throws java.io.IOException;
+ method public boolean isCacheBehaviorTombstone(java.io.File) throws java.io.IOException;
method public boolean isEncrypted(java.io.File);
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
+ method public void setCacheBehaviorAtomic(java.io.File, boolean) throws java.io.IOException;
+ method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
}
@@ -33010,6 +33184,16 @@
method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
}
+ public class FontsContract {
+ }
+
+ public static final class FontsContract.Columns implements android.provider.BaseColumns {
+ ctor public FontsContract.Columns();
+ field public static final java.lang.String STYLE = "font_style";
+ field public static final java.lang.String TTC_INDEX = "font_ttc_index";
+ field public static final java.lang.String VARIATION_SETTINGS = "font_variation_settings";
+ }
+
public final deprecated class LiveFolders implements android.provider.BaseColumns {
field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
field public static final java.lang.String DESCRIPTION = "description";
@@ -35852,6 +36036,7 @@
public static class NotificationListenerService.Ranking {
ctor public NotificationListenerService.Ranking();
+ method public boolean canShowBadge();
method public java.util.List<java.lang.String> getAdditionalPeople();
method public android.app.NotificationChannel getChannel();
method public int getImportance();
@@ -35893,7 +36078,6 @@
method public int getId();
method public java.lang.String getKey();
method public android.app.Notification getNotification();
- method public android.app.NotificationChannel getNotificationChannel();
method public java.lang.String getOverrideGroupKey();
method public java.lang.String getPackageName();
method public long getPostTime();
@@ -36309,6 +36493,7 @@
method public abstract int getMaxBufferSize();
method public abstract boolean hasFinished();
method public abstract boolean hasStarted();
+ method public default void rangeStart(int, int, int);
method public abstract int start(int, int, int);
}
@@ -36461,6 +36646,7 @@
method public void onError(java.lang.String, int);
method public abstract void onStart(java.lang.String);
method public void onStop(java.lang.String, boolean);
+ method public void onUtteranceRangeStart(java.lang.String, int, int);
}
public class Voice implements android.os.Parcelable {
@@ -37811,7 +37997,7 @@
field public static final java.lang.String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
field public static final java.lang.String ACTION_CONFIGURE_PHONE_ACCOUNT = "android.telecom.action.CONFIGURE_PHONE_ACCOUNT";
field public static final java.lang.String ACTION_DEFAULT_DIALER_CHANGED = "android.telecom.action.DEFAULT_DIALER_CHANGED";
- field public static final java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
+ field public static final deprecated java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS";
field public static final java.lang.String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS";
field public static final java.lang.String ACTION_SHOW_MISSED_CALLS_NOTIFICATION = "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION";
@@ -37914,7 +38100,8 @@
field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
- field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final deprecated java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY = "carrier_vvm_package_name_string_array";
field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool";
field public static final java.lang.String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
@@ -38006,9 +38193,13 @@
field public static final java.lang.String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final java.lang.String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
field public static final java.lang.String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
+ field public static final java.lang.String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string";
field public static final java.lang.String KEY_VVM_DESTINATION_NUMBER_STRING = "vvm_destination_number_string";
+ field public static final java.lang.String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY = "vvm_disabled_capabilities_string_array";
+ field public static final java.lang.String KEY_VVM_LEGACY_MODE_ENABLED_BOOL = "vvm_legacy_mode_enabled_bool";
field public static final java.lang.String KEY_VVM_PORT_NUMBER_INT = "vvm_port_number_int";
field public static final java.lang.String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
+ field public static final java.lang.String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
field public static final java.lang.String KEY_VVM_TYPE_STRING = "vvm_type_string";
field public static final java.lang.String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
}
@@ -38547,6 +38738,7 @@
method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+ method public boolean isConcurrentVoiceAndDataAllowed();
method public boolean isHearingAidCompatibilitySupported();
method public boolean isNetworkRoaming();
method public boolean isSmsCapable();
@@ -39573,6 +39765,65 @@
method public android.text.Editable newEditable(java.lang.CharSequence);
}
+ public final class FontConfig implements android.os.Parcelable {
+ ctor public FontConfig();
+ ctor public FontConfig(android.text.FontConfig);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Alias> getAliases();
+ method public java.util.List<android.text.FontConfig.Family> getFamilies();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR;
+ }
+
+ public static final class FontConfig.Alias implements android.os.Parcelable {
+ ctor public FontConfig.Alias(java.lang.String, java.lang.String, int);
+ method public int describeContents();
+ method public java.lang.String getName();
+ method public java.lang.String getToName();
+ method public int getWeight();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Alias> CREATOR;
+ }
+
+ public static final class FontConfig.Axis implements android.os.Parcelable {
+ ctor public FontConfig.Axis(int, float);
+ method public int describeContents();
+ method public float getStyleValue();
+ method public int getTag();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Axis> CREATOR;
+ }
+
+ public static final class FontConfig.Family implements android.os.Parcelable {
+ ctor public FontConfig.Family(java.lang.String, java.util.List<android.text.FontConfig.Font>, java.lang.String, java.lang.String);
+ ctor public FontConfig.Family(android.text.FontConfig.Family);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Font> getFonts();
+ method public java.lang.String getLanguage();
+ method public java.lang.String getName();
+ method public java.lang.String getVariant();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Family> CREATOR;
+ }
+
+ public static final class FontConfig.Font implements android.os.Parcelable {
+ ctor public FontConfig.Font(java.lang.String, int, java.util.List<android.text.FontConfig.Axis>, int, boolean);
+ ctor public FontConfig.Font(android.text.FontConfig.Font);
+ method public int describeContents();
+ method public java.util.List<android.text.FontConfig.Axis> getAxes();
+ method public android.os.ParcelFileDescriptor getFd();
+ method public java.lang.String getFontName();
+ method public int getTtcIndex();
+ method public int getWeight();
+ method public boolean isItalic();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.text.FontConfig.Font> CREATOR;
+ }
+
+ public final class FontManager {
+ method public android.text.FontConfig getSystemFonts();
+ }
+
public abstract interface GetChars implements java.lang.CharSequence {
method public abstract void getChars(int, int, char[], int);
}
@@ -39928,22 +40179,6 @@
method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
}
- public abstract interface TextAssistant {
- method public abstract void addLinks(android.text.Spannable, int);
- method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
- public class TextClassification {
- ctor public TextClassification();
- method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence();
- }
-
- public final class TextClassificationManager implements android.text.TextAssistant {
- method public void addLinks(android.text.Spannable, int);
- method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence);
- method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
public abstract interface TextDirectionHeuristic {
method public abstract boolean isRtl(char[], int, int);
method public abstract boolean isRtl(java.lang.CharSequence, int, int);
@@ -39959,13 +40194,6 @@
field public static final android.text.TextDirectionHeuristic RTL;
}
- public final class TextLanguage {
- ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>);
- method public int getEndIndex();
- method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence();
- method public int getStartIndex();
- }
-
public class TextPaint extends android.graphics.Paint {
ctor public TextPaint();
ctor public TextPaint(int);
@@ -39978,13 +40206,6 @@
field public int linkColor;
}
- public class TextSelection {
- ctor public TextSelection();
- method public int getSelectionEndIndex();
- method public int getSelectionStartIndex();
- method public android.text.TextClassification getTextClassification();
- }
-
public class TextUtils {
method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String);
method public static java.lang.CharSequence concat(java.lang.CharSequence...);
@@ -42403,7 +42624,9 @@
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
@@ -42472,7 +42695,7 @@
method public android.view.View findNearestTouchable(android.view.ViewGroup, int, int, int, int[]);
method public final android.view.View findNextFocus(android.view.ViewGroup, android.view.View, int);
method public android.view.View findNextFocusFromRect(android.view.ViewGroup, android.graphics.Rect, int);
- method public android.view.View findNextKeyboardNavigationGroup(int, android.view.View, android.view.View, int);
+ method public android.view.View findNextKeyboardNavigationCluster(android.view.View, android.view.View, int);
method public static android.view.FocusFinder getInstance();
}
@@ -43772,7 +43995,7 @@
method public void addChildrenForAccessibility(java.util.ArrayList<android.view.View>);
method public void addFocusables(java.util.ArrayList<android.view.View>, int);
method public void addFocusables(java.util.ArrayList<android.view.View>, int, int);
- method public void addKeyboardNavigationGroups(int, java.util.Collection<android.view.View>, int);
+ method public void addKeyboardNavigationClusters(java.util.Collection<android.view.View>, int);
method public void addOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener);
method public void addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener);
method public void addTouchables(java.util.ArrayList<android.view.View>);
@@ -43894,6 +44117,7 @@
method public float getElevation();
method public boolean getFilterTouchesWhenObscured();
method public boolean getFitsSystemWindows();
+ method public int getFocusable();
method public java.util.ArrayList<android.view.View> getFocusables(int);
method public void getFocusedRect(android.graphics.Rect);
method public android.graphics.drawable.Drawable getForeground();
@@ -43936,7 +44160,6 @@
method public int getNextFocusLeftId();
method public int getNextFocusRightId();
method public int getNextFocusUpId();
- method public int getNextSectionForwardId();
method public android.view.View.OnFocusChangeListener getOnFocusChangeListener();
method public android.view.ViewOutlineProvider getOutlineProvider();
method public int getOverScrollMode();
@@ -43980,7 +44203,6 @@
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();
@@ -44043,7 +44265,6 @@
method public boolean isInLayout();
method public boolean isInTouchMode();
method public final boolean isKeyboardNavigationCluster();
- method public final boolean isKeyboardNavigationSection();
method public boolean isLaidOut();
method public boolean isLayoutDirectionResolved();
method public boolean isLayoutRequested();
@@ -44066,7 +44287,7 @@
method public boolean isVerticalFadingEdgeEnabled();
method public boolean isVerticalScrollBarEnabled();
method public void jumpDrawablesToCurrentState();
- method public android.view.View keyboardNavigationGroupSearch(int, android.view.View, int);
+ method public android.view.View keyboardNavigationClusterSearch(android.view.View, int);
method public void layout(int, int, int, int);
method public final void measure(int, int);
method protected static int[] mergeDrawableStates(int[], int[]);
@@ -44201,6 +44422,7 @@
method public void setFilterTouchesWhenObscured(boolean);
method public void setFitsSystemWindows(boolean);
method public void setFocusable(boolean);
+ method public void setFocusable(int);
method public void setFocusableInTouchMode(boolean);
method public void setFocusedByDefault(boolean);
method public void setForeground(android.graphics.drawable.Drawable);
@@ -44216,7 +44438,6 @@
method public void setImportantForAccessibility(int);
method public void setKeepScreenOn(boolean);
method public void setKeyboardNavigationCluster(boolean);
- method public void setKeyboardNavigationSection(boolean);
method public void setLabelFor(int);
method public void setLayerPaint(android.graphics.Paint);
method public void setLayerType(int, android.graphics.Paint);
@@ -44234,7 +44455,6 @@
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);
@@ -44283,7 +44503,6 @@
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);
@@ -44341,8 +44560,10 @@
field protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET;
field public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 2; // 0x2
field public static final int FIND_VIEWS_WITH_TEXT = 1; // 0x1
+ field public static final int FOCUSABLE = 1; // 0x1
field public static final int FOCUSABLES_ALL = 0; // 0x0
field public static final int FOCUSABLES_TOUCH_MODE = 1; // 0x1
+ field public static final int FOCUSABLE_AUTO = 16; // 0x10
field protected static final int[] FOCUSED_SELECTED_STATE_SET;
field protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
field protected static final int[] FOCUSED_STATE_SET;
@@ -44361,8 +44582,6 @@
field public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1
field public static final int INVISIBLE = 4; // 0x4
field public static final int KEEP_SCREEN_ON = 67108864; // 0x4000000
- field public static final int KEYBOARD_NAVIGATION_GROUP_CLUSTER = 1; // 0x1
- field public static final int KEYBOARD_NAVIGATION_GROUP_SECTION = 2; // 0x2
field public static final int LAYER_TYPE_HARDWARE = 2; // 0x2
field public static final int LAYER_TYPE_NONE = 0; // 0x0
field public static final int LAYER_TYPE_SOFTWARE = 1; // 0x1
@@ -44374,6 +44593,7 @@
field public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff
field public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000
field public static final int MEASURED_STATE_TOO_SMALL = 16777216; // 0x1000000
+ field public static final int NOT_FOCUSABLE = 0; // 0x0
field public static final int NO_ID = -1; // 0xffffffff
field public static final int OVER_SCROLL_ALWAYS = 0; // 0x0
field public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; // 0x1
@@ -44885,7 +45105,7 @@
method public abstract boolean isLayoutRequested();
method public abstract boolean isTextAlignmentResolved();
method public abstract boolean isTextDirectionResolved();
- method public abstract android.view.View keyboardNavigationGroupSearch(int, android.view.View, int);
+ method public abstract android.view.View keyboardNavigationClusterSearch(android.view.View, int);
method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int);
method public abstract boolean onNestedFling(android.view.View, float, float, boolean);
method public abstract boolean onNestedPreFling(android.view.View, float, float);
@@ -46674,6 +46894,83 @@
}
+package android.view.textclassifier {
+
+ public abstract interface LinksInfo {
+ method public abstract boolean apply(java.lang.CharSequence);
+ }
+
+ public final class TextClassificationManager {
+ method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence);
+ method public android.view.textclassifier.TextClassifier getDefaultTextClassifier();
+ }
+
+ public final class TextClassificationResult {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public android.graphics.drawable.Drawable getIcon();
+ method public android.content.Intent getIntent();
+ method public java.lang.CharSequence getLabel();
+ method public android.view.View.OnClickListener getOnClickListener();
+ method public java.lang.String getText();
+ }
+
+ public static final class TextClassificationResult.Builder {
+ ctor public TextClassificationResult.Builder();
+ method public android.view.textclassifier.TextClassificationResult build();
+ method public android.view.textclassifier.TextClassificationResult.Builder setEntityType(java.lang.String, float);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIcon(android.graphics.drawable.Drawable);
+ method public android.view.textclassifier.TextClassificationResult.Builder setIntent(android.content.Intent);
+ method public android.view.textclassifier.TextClassificationResult.Builder setLabel(java.lang.String);
+ method public android.view.textclassifier.TextClassificationResult.Builder setOnClickListener(android.view.View.OnClickListener);
+ method public android.view.textclassifier.TextClassificationResult.Builder setText(java.lang.String);
+ }
+
+ public abstract interface TextClassifier {
+ method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
+ method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
+ method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
+ field public static final android.view.textclassifier.TextClassifier NO_OP;
+ field public static final java.lang.String TYPE_ADDRESS = "address";
+ field public static final java.lang.String TYPE_EMAIL = "email";
+ field public static final java.lang.String TYPE_OTHER = "other";
+ field public static final java.lang.String TYPE_PHONE = "phone";
+ }
+
+ public static abstract class TextClassifier.EntityType implements java.lang.annotation.Annotation {
+ }
+
+ public final class TextLanguage {
+ method public float getConfidenceScore(java.util.Locale);
+ method public int getEndIndex();
+ method public java.util.Locale getLanguage(int);
+ method public int getLanguageCount();
+ method public int getStartIndex();
+ }
+
+ public static final class TextLanguage.Builder {
+ ctor public TextLanguage.Builder(int, int);
+ method public android.view.textclassifier.TextLanguage build();
+ method public android.view.textclassifier.TextLanguage.Builder setLanguage(java.util.Locale, float);
+ }
+
+ public final class TextSelection {
+ method public float getConfidenceScore(java.lang.String);
+ method public java.lang.String getEntity(int);
+ method public int getEntityCount();
+ method public int getSelectionEndIndex();
+ method public int getSelectionStartIndex();
+ }
+
+ public static final class TextSelection.Builder {
+ ctor public TextSelection.Builder(int, int);
+ method public android.view.textclassifier.TextSelection build();
+ method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float);
+ }
+
+}
+
package android.view.textservice {
public final class SentenceSuggestionsInfo implements android.os.Parcelable {
@@ -46904,6 +47201,7 @@
public abstract class RenderProcessGoneDetail {
ctor public RenderProcessGoneDetail();
method public abstract boolean didCrash();
+ method public abstract int rendererPriorityAtExit();
}
public class ServiceWorkerClient {
@@ -47320,6 +47618,8 @@
method public deprecated java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String);
method public java.lang.String getOriginalUrl();
method public int getProgress();
+ method public boolean getRendererPriorityWaivedWhenNotVisible();
+ method public int getRendererRequestedPriority();
method public deprecated float getScale();
method public android.webkit.WebSettings getSettings();
method public java.lang.String getTitle();
@@ -47367,6 +47667,7 @@
method public deprecated void setMapTrackballToArrowKeys(boolean);
method public void setNetworkAvailable(boolean);
method public deprecated void setPictureListener(android.webkit.WebView.PictureListener);
+ method public void setRendererPriorityPolicy(int, boolean);
method public deprecated void setVerticalScrollbarOverlay(boolean);
method public void setWebChromeClient(android.webkit.WebChromeClient);
method public static void setWebContentsDebuggingEnabled(boolean);
@@ -47376,6 +47677,9 @@
method public void zoomBy(float);
method public boolean zoomIn();
method public boolean zoomOut();
+ field public static final int RENDERER_PRIORITY_BOUND = 1; // 0x1
+ field public static final int RENDERER_PRIORITY_IMPORTANT = 2; // 0x2
+ field public static final int RENDERER_PRIORITY_WAIVED = 0; // 0x0
field public static final java.lang.String SCHEME_GEO = "geo:0,0?q=";
field public static final java.lang.String SCHEME_MAILTO = "mailto:";
field public static final java.lang.String SCHEME_TEL = "tel:";
@@ -49688,6 +49992,10 @@
method public void endBatchEdit();
method public boolean extractText(android.view.inputmethod.ExtractedTextRequest, android.view.inputmethod.ExtractedText);
method public final int getAutoLinkMask();
+ method public int getAutoSizeMaxTextSize();
+ method public int getAutoSizeMinTextSize();
+ method public int getAutoSizeStepGranularity();
+ method public int getAutoSizeTextType();
method public int getBreakStrategy();
method public int getCompoundDrawablePadding();
method public android.content.res.ColorStateList getCompoundDrawableTintList();
@@ -49759,7 +50067,7 @@
method public float getShadowRadius();
method public final boolean getShowSoftInputOnFocus();
method public java.lang.CharSequence getText();
- method public android.text.TextAssistant getTextAssistant();
+ method public android.view.textclassifier.TextClassifier getTextClassifier();
method public final android.content.res.ColorStateList getTextColors();
method public java.util.Locale getTextLocale();
method public android.os.LocaleList getTextLocales();
@@ -49796,6 +50104,10 @@
method public void removeTextChangedListener(android.text.TextWatcher);
method public void setAllCaps(boolean);
method public final void setAutoLinkMask(int);
+ method public void setAutoSizeMaxTextSize(int, float);
+ method public void setAutoSizeMinTextSize(int, float);
+ method public void setAutoSizeStepGranularity(int, float);
+ method public void setAutoSizeTextType(int);
method public void setBreakStrategy(int);
method public void setCompoundDrawablePadding(int);
method public void setCompoundDrawableTintList(android.content.res.ColorStateList);
@@ -49871,7 +50183,7 @@
method public final void setText(int, android.widget.TextView.BufferType);
method public void setTextAppearance(int);
method public deprecated void setTextAppearance(android.content.Context, int);
- method public void setTextAssistant(android.text.TextAssistant);
+ method public void setTextClassifier(android.view.textclassifier.TextClassifier);
method public void setTextColor(int);
method public void setTextColor(android.content.res.ColorStateList);
method public void setTextIsSelectable(boolean);
@@ -49886,8 +50198,8 @@
method public void setTypeface(android.graphics.Typeface, int);
method public void setTypeface(android.graphics.Typeface);
method public void setWidth(int);
- field public static final int AUTO_SIZE_TYPE_NONE = 0; // 0x0
- field public static final int AUTO_SIZE_TYPE_XY = 1; // 0x1
+ field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
+ field public static final int AUTO_SIZE_TEXT_TYPE_XY = 1; // 0x1
}
public static final class TextView.BufferType extends java.lang.Enum {
diff --git a/compiled-classes-phone b/compiled-classes-phone
index ebc54f2..ed0a4a6 100644
--- a/compiled-classes-phone
+++ b/compiled-classes-phone
@@ -1195,13 +1195,13 @@
android.graphics.DiscretePathEffect
android.graphics.DrawFilter
android.graphics.EmbossMaskFilter
+android.graphics.FontConfig
+android.graphics.FontConfig$Alias
+android.graphics.FontConfig$Axis
+android.graphics.FontConfig$Family
+android.graphics.FontConfig$Font
android.graphics.FontFamily
android.graphics.FontListParser
-android.graphics.FontListParser$Alias
-android.graphics.FontListParser$Axis
-android.graphics.FontListParser$Config
-android.graphics.FontListParser$Family
-android.graphics.FontListParser$Font
android.graphics.ImageFormat
android.graphics.Insets
android.graphics.Interpolator
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 556d7ad..a9d1cf6 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -78,8 +78,6 @@
import android.service.autofill.IAutoFillAppCallback;
import android.text.Selection;
import android.text.SpannableStringBuilder;
-import android.text.TextAssistant;
-import android.text.TextClassificationManager;
import android.text.TextUtils;
import android.text.method.TextKeyListener;
import android.transition.Scene;
@@ -792,8 +790,6 @@
private VoiceInteractor mVoiceInteractor;
- private TextAssistant mTextAssistant;
-
private CharSequence mTitle;
private int mTitleColor = 0;
@@ -1398,24 +1394,6 @@
}
/**
- * Sets the default {@link TextAssistant} for {@link android.widget.TextView}s in this Activity.
- */
- public void setTextAssistant(TextAssistant textAssistant) {
- mTextAssistant = textAssistant;
- }
-
- /**
- * Returns the default {@link TextAssistant} for {@link android.widget.TextView}s
- * in this Activity.
- */
- public TextAssistant getTextAssistant() {
- if (mTextAssistant != null) {
- return mTextAssistant;
- }
- return getSystemService(TextClassificationManager.class);
- }
-
- /**
* This is called for activities that set launchMode to "singleTop" in
* their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP}
* flag when calling {@link #startActivity}. In either case, when the
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 1d4b038..c1a888d 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -520,17 +520,17 @@
/** @hide Flag for registerUidObserver: report uid has become active. */
public static final int UID_OBSERVER_ACTIVE = 1<<3;
- /** @hide Mode for {@link IActivityManager#getAppStartMode}: normal free-to-run operation. */
+ /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: normal free-to-run operation. */
public static final int APP_START_MODE_NORMAL = 0;
- /** @hide Mode for {@link IActivityManager#getAppStartMode}: delay running until later. */
+ /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: delay running until later. */
public static final int APP_START_MODE_DELAYED = 1;
- /** @hide Mode for {@link IActivityManager#getAppStartMode}: delay running until later, with
+ /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: delay running until later, with
* rigid errors (throwing exception). */
public static final int APP_START_MODE_DELAYED_RIGID = 2;
- /** @hide Mode for {@link IActivityManager#getAppStartMode}: disable/cancel pending
+ /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: disable/cancel pending
* launches; this is the mode for ephemeral apps. */
public static final int APP_START_MODE_DISABLED = 3;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 9cc13ab..603126b 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -245,8 +245,10 @@
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 @@
OP_AUDIO_ACCESSIBILITY_VOLUME,
OP_READ_PHONE_NUMBER,
OP_REQUEST_INSTALL_PACKAGES,
+ OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
};
/**
@@ -538,6 +541,7 @@
null, // OP_AUDIO_ACCESSIBILITY_VOLUME
OPSTR_READ_PHONE_NUMBER,
null, // OP_REQUEST_INSTALL_PACKAGES
+ null,
};
/**
@@ -612,6 +616,7 @@
"AUDIO_ACCESSIBILITY_VOLUME",
"READ_PHONE_NUMBER",
"REQUEST_INSTALL_PACKAGES",
+ "OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE",
};
/**
@@ -686,6 +691,7 @@
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 @@
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 @@
false, // AUDIO_ACCESSIBILITY_VOLUME
false, // READ_PHONE_NUMBER
false, // REQUEST_INSTALL_PACKAGES
+ false, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
};
/**
@@ -908,6 +916,7 @@
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 @@
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/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 1658e82..d37888d 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1341,8 +1341,8 @@
}
try {
final Intent intent = ActivityManager.getService().registerReceiver(
- mMainThread.getApplicationThread(), mBasePackageName,
- rd, filter, broadcastPermission, userId);
+ mMainThread.getApplicationThread(), mBasePackageName, rd, filter,
+ broadcastPermission, userId);
if (intent != null) {
intent.setExtrasClassLoader(getClassLoader());
intent.prepareToEnterProcess();
@@ -1591,12 +1591,15 @@
}
final IActivityManager am = ActivityManager.getService();
- if (am == null && UserHandle.getAppId(Binder.getCallingUid()) == Process.SYSTEM_UID) {
+ if (am == null) {
// Well this is super awkward; we somehow don't have an active
- // ActivityManager instance. If this is the system UID, then we
- // totally have whatever permission this is.
- Slog.w(TAG, "Missing ActivityManager; assuming system UID holds " + permission);
- return PackageManager.PERMISSION_GRANTED;
+ // ActivityManager instance. If we're testing a root or system
+ // UID, then they totally have whatever permission this is.
+ final int appId = UserHandle.getAppId(uid);
+ if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
+ Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " holds " + permission);
+ return PackageManager.PERMISSION_GRANTED;
+ }
}
try {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 135c2a4..5a48793 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -264,7 +264,7 @@
boolean isImmersive(in IBinder token);
void setImmersive(in IBinder token, boolean immersive);
boolean isTopActivityImmersive();
- void crashApplication(int uid, int initialPid, in String packageName, in String message);
+ void crashApplication(int uid, int initialPid, in String packageName, int userId, in String message);
String getProviderMimeType(in Uri uri, int userId);
IBinder newUriPermissionOwner(in String name);
void grantUriPermissionFromOwner(in IBinder owner, int fromUid, in String targetPkg,
@@ -475,7 +475,7 @@
void suppressResizeConfigChanges(boolean suppress);
void moveTasksToFullscreenStack(int fromStackId, boolean onTop);
boolean moveTopActivityToPinnedStack(int stackId, in Rect bounds);
- int getAppStartMode(int uid, in String packageName);
+ boolean isAppStartModeDisabled(int uid, in String packageName);
boolean unlockUser(int userid, in byte[] token, in byte[] secret,
in IProgressListener listener);
boolean isInMultiWindowMode(in IBinder token);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index f909af0..d674bfe 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -47,6 +47,8 @@
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 036b47c..c0bf0c4 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -24,25 +24,25 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.RemoteException;
import android.os.IBinder;
-import android.os.IUserManager;
+import android.os.Looper;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.UserHandle;
import android.util.Log;
-import android.view.IWindowManager;
import android.view.IOnKeyguardExitResult;
-import android.view.WindowManager;
+import android.view.IWindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
import com.android.internal.policy.IKeyguardDismissCallback;
+import java.util.List;
+
/**
* Class that can be used to lock and unlock the keyboard. Get an instance of this
* class by calling {@link android.content.Context#getSystemService(java.lang.String)}
@@ -100,12 +100,9 @@
Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL);
intent.putExtra(EXTRA_TITLE, title);
intent.putExtra(EXTRA_DESCRIPTION, description);
- if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- intent.setPackage("com.google.android.apps.wearable.settings");
- } else {
- // For security reasons, only allow this to come from system settings.
- intent.setPackage("com.android.settings");
- }
+
+ // explicitly set the package for security
+ intent.setPackage(getSettingsPackageForIntent(intent));
return intent;
}
@@ -126,15 +123,23 @@
intent.putExtra(EXTRA_TITLE, title);
intent.putExtra(EXTRA_DESCRIPTION, description);
intent.putExtra(Intent.EXTRA_USER_ID, userId);
- if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- intent.setPackage("com.google.android.apps.wearable.settings");
- } else {
- // For security reasons, only allow this to come from system settings.
- intent.setPackage("com.android.settings");
- }
+
+ // explicitly set the package for security
+ intent.setPackage(getSettingsPackageForIntent(intent));
+
return intent;
}
+ private String getSettingsPackageForIntent(Intent intent) {
+ List<ResolveInfo> resolveInfos = mContext.getPackageManager()
+ .queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY);
+ for (int i = 0; i < resolveInfos.size(); i++) {
+ return resolveInfos.get(i).activityInfo.packageName;
+ }
+
+ return "com.android.settings";
+ }
+
/**
* @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD}
* and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED}
@@ -322,7 +327,7 @@
* password.
*/
public boolean isDeviceLocked() {
- return isDeviceLocked(UserHandle.getCallingUserId());
+ return isDeviceLocked(UserHandle.myUserId());
}
/**
@@ -347,7 +352,7 @@
* @return true if a PIN, pattern or password was set.
*/
public boolean isDeviceSecure() {
- return isDeviceSecure(UserHandle.getCallingUserId());
+ return isDeviceSecure(UserHandle.myUserId());
}
/**
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 5b74e23..82917d2 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1051,7 +1051,7 @@
private final Bundle mExtras;
private Icon mIcon;
private final RemoteInput[] mRemoteInputs;
- private boolean mAllowGeneratedReplies = false;
+ private boolean mAllowGeneratedReplies = true;
/**
* Small icon representing the action.
@@ -1093,7 +1093,7 @@
*/
@Deprecated
public Action(int icon, CharSequence title, PendingIntent intent) {
- this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, false);
+ this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true);
}
/** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */
@@ -1166,7 +1166,7 @@
private final Icon mIcon;
private final CharSequence mTitle;
private final PendingIntent mIntent;
- private boolean mAllowGeneratedReplies;
+ private boolean mAllowGeneratedReplies = true;
private final Bundle mExtras;
private ArrayList<RemoteInput> mRemoteInputs;
@@ -1188,7 +1188,7 @@
* @param intent the {@link PendingIntent} to fire when users trigger this action
*/
public Builder(Icon icon, CharSequence title, PendingIntent intent) {
- this(icon, title, intent, new Bundle(), null, false);
+ this(icon, title, intent, new Bundle(), null, true);
}
/**
@@ -1260,7 +1260,7 @@
* @param allowGeneratedReplies {@code true} to allow generated replies, {@code false}
* otherwise
* @return this object for method chaining
- * The default value is {@code false}
+ * The default value is {@code true}
*/
public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) {
mAllowGeneratedReplies = allowGeneratedReplies;
@@ -7088,11 +7088,13 @@
private static final String EXTRA_FLAGS = "flags";
private static final String EXTRA_CONTENT_INTENT = "content_intent";
private static final String EXTRA_DELETE_INTENT = "delete_intent";
+ private static final String EXTRA_CHANNEL_ID = "channel_id";
// Flags bitwise-ored to mFlags
private static final int FLAG_AVAILABLE_ON_TV = 0x1;
private int mFlags;
+ private String mChannelId;
private PendingIntent mContentIntent;
private PendingIntent mDeleteIntent;
@@ -7113,6 +7115,7 @@
null : notif.extras.getBundle(EXTRA_TV_EXTENDER);
if (bundle != null) {
mFlags = bundle.getInt(EXTRA_FLAGS);
+ mChannelId = bundle.getString(EXTRA_CHANNEL_ID);
mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT);
mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT);
}
@@ -7128,6 +7131,7 @@
Bundle bundle = new Bundle();
bundle.putInt(EXTRA_FLAGS, mFlags);
+ bundle.putString(EXTRA_CHANNEL_ID, mChannelId);
if (mContentIntent != null) {
bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
}
@@ -7149,6 +7153,23 @@
}
/**
+ * Specifies the channel the notification should be delivered on when shown on TV.
+ * It can be different from the channel that the notification is delivered to when
+ * posting on a non-TV device.
+ */
+ public TvExtender setChannel(String channelId) {
+ mChannelId = channelId;
+ return this;
+ }
+
+ /**
+ * Returns the id of the channel this notification posts to on TV.
+ */
+ public String getChannel() {
+ return mChannelId;
+ }
+
+ /**
* Supplies a {@link PendingIntent} to be sent when the notification is selected on TV.
* If provided, it is used instead of the content intent specified
* at the level of Notification.
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 56ef791..be5f80a 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -122,6 +122,7 @@
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 @@
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 @@
/**
* 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 0000000..1f015a6
--- /dev/null
+++ b/core/java/android/app/RecoverableSecurityException.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Specialization of {@link SecurityException} that contains additional
+ * information about how to involve the end user to recover from the exception.
+ * <p>
+ * This exception is only appropriate where there is a concrete action the user
+ * can take to recover and make forward progress, such as confirming or entering
+ * authentication credentials.
+ * <p class="note">
+ * Note: legacy code that receives this exception may treat it as a general
+ * {@link SecurityException}, and thus there is no guarantee that the messages
+ * contained will be shown to the end user.
+ * </p>
+ */
+public final class RecoverableSecurityException extends SecurityException implements Parcelable {
+ private static final String TAG = "RecoverableSecurityException";
+
+ private final CharSequence mUserMessage;
+ private final CharSequence mUserActionTitle;
+ private final PendingIntent mUserAction;
+
+ /** {@hide} */
+ public RecoverableSecurityException(Parcel in) {
+ this(new SecurityException(in.readString()), in.readCharSequence(), in.readCharSequence(),
+ PendingIntent.CREATOR.createFromParcel(in));
+ }
+
+ /**
+ * Create an instance ready to be thrown.
+ *
+ * @param cause original cause with details designed for engineering
+ * audiences.
+ * @param userMessage short message describing the issue for end user
+ * audiences, which may be shown in a notification or dialog.
+ * This should be less than 64 characters. For example: <em>PIN
+ * required to access Document.pdf</em>
+ * @param userActionTitle short title describing the primary action. This
+ * should be less than 24 characters. For example: <em>Enter
+ * PIN</em>
+ * @param userAction primary action that will initiate the recovery. This
+ * must launch an activity that is expected to set
+ * {@link Activity#setResult(int)} before finishing to
+ * communicate the final status of the recovery. For example,
+ * apps that observe {@link Activity#RESULT_OK} may choose to
+ * immediately retry their operation.
+ */
+ public RecoverableSecurityException(Throwable cause, CharSequence userMessage,
+ CharSequence userActionTitle, PendingIntent userAction) {
+ super(cause.getMessage());
+ mUserMessage = Preconditions.checkNotNull(userMessage);
+ mUserActionTitle = Preconditions.checkNotNull(userActionTitle);
+ mUserAction = Preconditions.checkNotNull(userAction);
+ }
+
+ /**
+ * Return short message describing the issue for end user audiences, which
+ * may be shown in a notification or dialog.
+ */
+ public CharSequence getUserMessage() {
+ return mUserMessage;
+ }
+
+ /**
+ * Return short title describing the primary action.
+ */
+ public CharSequence getUserActionTitle() {
+ return mUserActionTitle;
+ }
+
+ /**
+ * Return primary action that will initiate the recovery.
+ */
+ public PendingIntent getUserAction() {
+ return mUserAction;
+ }
+
+ /**
+ * Convenience method that will show a very simple notification populated
+ * with the details from this exception.
+ * <p>
+ * If you want more flexibility over retrying your original operation once
+ * the user action has finished, consider presenting your own UI that uses
+ * {@link Activity#startIntentSenderForResult} to launch the
+ * {@link PendingIntent#getIntentSender()} from {@link #getUserAction()}
+ * when requested. If the result of that activity is
+ * {@link Activity#RESULT_OK}, you should consider retrying.
+ * <p>
+ * This method will only display the most recent exception from any single
+ * remote UID; notifications from older exceptions will always be replaced.
+ */
+ public void showAsNotification(Context context) {
+ final Notification.Builder builder = new Notification.Builder(context)
+ .setSmallIcon(com.android.internal.R.drawable.ic_print_error)
+ .setContentTitle(mUserActionTitle)
+ .setContentText(mUserMessage)
+ .setContentIntent(mUserAction)
+ .setCategory(Notification.CATEGORY_ERROR);
+
+ final NotificationManager nm = context.getSystemService(NotificationManager.class);
+ nm.notify(TAG, mUserAction.getCreatorUid(), builder.build());
+ }
+
+ /**
+ * Convenience method that will show a very simple dialog populated with the
+ * details from this exception.
+ * <p>
+ * If you want more flexibility over retrying your original operation once
+ * the user action has finished, consider presenting your own UI that uses
+ * {@link Activity#startIntentSenderForResult} to launch the
+ * {@link PendingIntent#getIntentSender()} from {@link #getUserAction()}
+ * when requested. If the result of that activity is
+ * {@link Activity#RESULT_OK}, you should consider retrying.
+ * <p>
+ * This method will only display the most recent exception from any single
+ * remote UID; dialogs from older exceptions will always be replaced.
+ */
+ public void showAsDialog(Activity activity) {
+ final LocalDialog dialog = new LocalDialog();
+ final Bundle args = new Bundle();
+ args.putParcelable(TAG, this);
+ dialog.setArguments(args);
+
+ final String tag = TAG + "_" + mUserAction.getCreatorUid();
+ final FragmentManager fm = activity.getFragmentManager();
+ final FragmentTransaction ft = fm.beginTransaction();
+ final Fragment old = fm.findFragmentByTag(tag);
+ if (old != null) {
+ ft.remove(old);
+ }
+ ft.add(dialog, tag);
+ ft.commitAllowingStateLoss();
+ }
+
+ /** {@hide} */
+ public static class LocalDialog extends DialogFragment {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final RecoverableSecurityException e = getArguments().getParcelable(TAG);
+ return new AlertDialog.Builder(getActivity())
+ .setMessage(e.mUserMessage)
+ .setPositiveButton(e.mUserActionTitle, (dialog, which) -> {
+ try {
+ e.mUserAction.send();
+ } catch (PendingIntent.CanceledException ignored) {
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(getMessage());
+ dest.writeCharSequence(mUserMessage);
+ dest.writeCharSequence(mUserActionTitle);
+ mUserAction.writeToParcel(dest, flags);
+ }
+
+ public static final Creator<RecoverableSecurityException> CREATOR =
+ new Creator<RecoverableSecurityException>() {
+ @Override
+ public RecoverableSecurityException createFromParcel(Parcel source) {
+ return new RecoverableSecurityException(source);
+ }
+
+ @Override
+ public RecoverableSecurityException[] newArray(int size) {
+ return new RecoverableSecurityException[size];
+ }
+ };
+}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 9387019..5d8909c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -118,7 +118,7 @@
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.text.TextClassificationManager;
+import android.text.FontManager;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -127,12 +127,14 @@
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;
import com.android.internal.app.IBatteryStats;
import com.android.internal.app.ISoundTriggerService;
import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.font.IFontManager;
import com.android.internal.os.IDropBoxManagerService;
import com.android.internal.policy.PhoneLayoutInflater;
@@ -226,10 +228,10 @@
}});
registerService(Context.TEXT_CLASSIFICATION_SERVICE, TextClassificationManager.class,
- new StaticServiceFetcher<TextClassificationManager>() {
+ new CachedServiceFetcher<TextClassificationManager>() {
@Override
- public TextClassificationManager createService() {
- return new TextClassificationManager();
+ public TextClassificationManager createService(ContextImpl ctx) {
+ return new TextClassificationManager(ctx);
}});
registerService(Context.CLIPBOARD_SERVICE, ClipboardManager.class,
@@ -793,6 +795,15 @@
public IncidentManager createService(ContextImpl ctx) throws ServiceNotFoundException {
return new IncidentManager(ctx);
}});
+
+ registerService(Context.FONT_SERVICE, FontManager.class,
+ new CachedServiceFetcher<FontManager>() {
+ @Override
+ public FontManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(Context.FONT_SERVICE);
+ return new FontManager(IFontManager.Stub.asInterface(b));
+ }});
}
/**
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index a248bce..34a0c30 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -707,17 +707,24 @@
}
/**
- * Allows the receiver to be notified when information about a pending system update is
+ * Called when the information about a pending system update is available.
+ *
+ * <p>Allows the receiver to be notified when information about a pending system update is
* available from the system update service. The same pending system update can trigger multiple
* calls to this method, so it is necessary to examine the incoming parameters for details about
* the update.
- * <p>
- * This callback is only applicable to device owners.
+ *
+ * <p>This callback is only applicable to device owners and profile owners.
+ *
+ * <p>To get further information about a pending system update (for example, whether or not the
+ * update is a security patch), the device owner or profile owner can call
+ * {@link DevicePolicyManager#getPendingSystemUpdate}.
*
* @param context The running context as per {@link #onReceive}.
* @param intent The received intent as per {@link #onReceive}.
* @param receivedTime The time as given by {@link System#currentTimeMillis()} indicating when
* the current pending update was first available. -1 if no pending update is available.
+ * @see DevicePolicyManager#getPendingSystemUpdate
*/
public void onSystemUpdatePending(Context context, Intent intent, long receivedTime) {
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index b8bc7f1..aa56be6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1438,6 +1438,7 @@
}
return false;
}
+
/**
* Return true if the given administrator component is currently being removed
* for the user.
@@ -1454,7 +1455,6 @@
return false;
}
-
/**
* Return a list of all currently active device administrators' component
* names. If there are no administrators {@code null} may be
@@ -3735,13 +3735,13 @@
}
/**
- * Called by a device owner to set whether auto time is required. If auto time is required the
- * user cannot set the date and time, but has to use network date and time.
+ * Called by a device or profile owner to set whether auto time is required. If auto time is
+ * required, no user will be able set the date and time and network date and time will be used.
* <p>
* Note: if auto time is required the user can still manually set the time zone.
* <p>
- * The calling device admin must be a device owner. If it is not, a security exception will be
- * thrown.
+ * The calling device admin must be a device or profile owner. If it is not, a security
+ * exception will be thrown.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param required Whether auto time is set required or not.
@@ -6199,12 +6199,18 @@
}
/**
- * Callable by the system update service to notify device owners about pending updates.
- * The caller must hold {@link android.Manifest.permission#NOTIFY_PENDING_SYSTEM_UPDATE}
- * permission.
+ * Called by the system update service to notify device and profile owners of pending system
+ * updates.
*
- * @param updateReceivedTime The time as given by {@link System#currentTimeMillis()} indicating
- * when the current pending update was first available. -1 if no update is available.
+ * The caller must hold {@link android.Manifest.permission#NOTIFY_PENDING_SYSTEM_UPDATE}
+ * permission. This method should only be used when it is unknown whether the pending system
+ * update is a security patch. Otherwise, use
+ * {@link #notifyPendingSystemUpdate(long, boolean)}.
+ *
+ * @param updateReceivedTime The time as given by {@link System#currentTimeMillis()}
+ * indicating when the current pending update was first available. {@code -1} if no
+ * update is available.
+ * @see #notifyPendingSystemUpdate(long, boolean)
* @hide
*/
@SystemApi
@@ -6212,7 +6218,36 @@
throwIfParentInstance("notifyPendingSystemUpdate");
if (mService != null) {
try {
- mService.notifyPendingSystemUpdate(updateReceivedTime);
+ mService.notifyPendingSystemUpdate(SystemUpdateInfo.of(updateReceivedTime));
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Called by the system update service to notify device and profile owners of pending system
+ * updates.
+ *
+ * The caller must hold {@link android.Manifest.permission#NOTIFY_PENDING_SYSTEM_UPDATE}
+ * permission. This method should be used instead of {@link #notifyPendingSystemUpdate(long)}
+ * when it is known whether the pending system update is a security patch.
+ *
+ * @param updateReceivedTime The time as given by {@link System#currentTimeMillis()}
+ * indicating when the current pending update was first available. {@code -1} if no
+ * update is available.
+ * @param isSecurityPatch {@code true} if this system update is purely a security patch;
+ * {@code false} if not.
+ * @see #notifyPendingSystemUpdate(long)
+ * @hide
+ */
+ @SystemApi
+ public void notifyPendingSystemUpdate(long updateReceivedTime, boolean isSecurityPatch) {
+ throwIfParentInstance("notifyPendingSystemUpdate");
+ if (mService != null) {
+ try {
+ mService.notifyPendingSystemUpdate(SystemUpdateInfo.of(updateReceivedTime,
+ isSecurityPatch));
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8891f93..80ef557 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -264,7 +264,7 @@
boolean setStatusBarDisabled(in ComponentName who, boolean disabled);
boolean getDoNotAskCredentialsOnBoot();
- void notifyPendingSystemUpdate(in long updateReceivedTime);
+ void notifyPendingSystemUpdate(in SystemUpdateInfo info);
SystemUpdateInfo getPendingSystemUpdate(in ComponentName admin);
void setPermissionPolicy(in ComponentName admin, int policy);
diff --git a/core/java/android/app/admin/SystemUpdateInfo.java b/core/java/android/app/admin/SystemUpdateInfo.java
index 0937f3c..6bb9f2d0 100644
--- a/core/java/android/app/admin/SystemUpdateInfo.java
+++ b/core/java/android/app/admin/SystemUpdateInfo.java
@@ -16,6 +16,7 @@
package android.app.admin;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.os.Build;
import android.os.Parcel;
@@ -25,41 +26,86 @@
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
* A class containing information about a pending system update.
*/
public final class SystemUpdateInfo implements Parcelable {
- private static final String ATTR_RECEIVED_TIME = "mReceivedTime";
- // Tag used to store original build fingerprint to detect when the update is applied.
- private static final String ATTR_ORIGINAL_BUILD = "originalBuild";
- private final long mReceivedTime;
- private SystemUpdateInfo(long receivedTime) {
+ /**
+ * Represents it is unknown whether the system update is a security patch.
+ */
+ public static final int SECURITY_PATCH_STATE_UNKNOWN = 0;
+
+ /**
+ * Represents the system update is not a security patch.
+ */
+ public static final int SECURITY_PATCH_STATE_FALSE = 1;
+
+ /**
+ * Represents the system update is a security patch.
+ */
+ public static final int SECURITY_PATCH_STATE_TRUE = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SECURITY_PATCH_STATE_FALSE, SECURITY_PATCH_STATE_TRUE, SECURITY_PATCH_STATE_UNKNOWN})
+ public @interface SecurityPatchState {}
+
+ private static final String ATTR_RECEIVED_TIME = "received-time";
+ private static final String ATTR_SECURITY_PATCH_STATE = "security-patch-state";
+ // Tag used to store original build fingerprint to detect when the update is applied.
+ private static final String ATTR_ORIGINAL_BUILD = "original-build";
+
+ private final long mReceivedTime;
+ @SecurityPatchState
+ private final int mSecurityPatchState;
+
+ private SystemUpdateInfo(long receivedTime, @SecurityPatchState int securityPatchState) {
this.mReceivedTime = receivedTime;
+ this.mSecurityPatchState = securityPatchState;
}
private SystemUpdateInfo(Parcel in) {
mReceivedTime = in.readLong();
+ mSecurityPatchState = in.readInt();
}
- /**
- * @hide
- */
+ /** @hide */
@Nullable
public static SystemUpdateInfo of(long receivedTime) {
- return receivedTime == -1 ? null : new SystemUpdateInfo(receivedTime);
+ return receivedTime == -1
+ ? null : new SystemUpdateInfo(receivedTime, SECURITY_PATCH_STATE_UNKNOWN);
+ }
+
+ /** @hide */
+ @Nullable
+ public static SystemUpdateInfo of(long receivedTime, boolean isSecurityPatch) {
+ return receivedTime == -1 ? null : new SystemUpdateInfo(receivedTime,
+ isSecurityPatch ? SECURITY_PATCH_STATE_TRUE : SECURITY_PATCH_STATE_FALSE);
}
/**
- * Get time when the update was first available.
- * @return time as given by {@link System#currentTimeMillis()}
+ * Gets time when the update was first available.
+ * @return Time as given by {@link System#currentTimeMillis()}
*/
public long getReceivedTime() {
return mReceivedTime;
}
+ /**
+ * Gets whether the update is a security patch.
+ * @return {@link #SECURITY_PATCH_STATE_FALSE}, {@link #SECURITY_PATCH_STATE_TRUE}, or
+ * {@link #SECURITY_PATCH_STATE_UNKNOWN}.
+ */
+ @SecurityPatchState
+ public int getSecurityPatchState() {
+ return mSecurityPatchState;
+ }
+
public static final Creator<SystemUpdateInfo> CREATOR =
new Creator<SystemUpdateInfo>() {
@Override
@@ -73,19 +119,16 @@
}
};
- /**
- * @hide
- */
+ /** @hide */
public void writeToXml(XmlSerializer out, String tag) throws IOException {
out.startTag(null, tag);
out.attribute(null, ATTR_RECEIVED_TIME, String.valueOf(mReceivedTime));
+ out.attribute(null, ATTR_SECURITY_PATCH_STATE, String.valueOf(mSecurityPatchState));
out.attribute(null, ATTR_ORIGINAL_BUILD , Build.FINGERPRINT);
out.endTag(null, tag);
}
- /**
- * @hide
- */
+ /** @hide */
@Nullable
public static SystemUpdateInfo readFromXml(XmlPullParser parser) {
// If an OTA has been applied (build fingerprint has changed), discard stale info.
@@ -95,7 +138,9 @@
}
final long receivedTime =
Long.parseLong(parser.getAttributeValue(null, ATTR_RECEIVED_TIME));
- return of(receivedTime);
+ final int securityPatchState =
+ Integer.parseInt(parser.getAttributeValue(null, ATTR_SECURITY_PATCH_STATE));
+ return new SystemUpdateInfo(receivedTime, securityPatchState);
}
@Override
@@ -106,11 +151,26 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(getReceivedTime());
+ dest.writeInt(getSecurityPatchState());
}
@Override
public String toString() {
- return String.format("SystemUpdateInfo (receivedTime = %d)", mReceivedTime);
+ return String.format("SystemUpdateInfo (receivedTime = %d, securityPatchState = %s)",
+ mReceivedTime, securityPatchStateToString(mSecurityPatchState));
+ }
+
+ private static String securityPatchStateToString(@SecurityPatchState int state) {
+ switch (state) {
+ case SECURITY_PATCH_STATE_FALSE:
+ return "false";
+ case SECURITY_PATCH_STATE_TRUE:
+ return "true";
+ case SECURITY_PATCH_STATE_UNKNOWN:
+ return "unknown";
+ default:
+ throw new IllegalArgumentException("Unrecognized security patch state: " + state);
+ }
}
@Override
@@ -118,11 +178,12 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SystemUpdateInfo that = (SystemUpdateInfo) o;
- return mReceivedTime == that.mReceivedTime;
+ return mReceivedTime == that.mReceivedTime
+ && mSecurityPatchState == that.mSecurityPatchState;
}
@Override
public int hashCode() {
- return Objects.hash(mReceivedTime);
+ return Objects.hash(mReceivedTime, mSecurityPatchState);
}
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 596a9fd..38e6fbe 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -34,9 +34,7 @@
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.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 @@
/**
* 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";
@@ -3744,6 +3743,11 @@
public static final String DEVICE_IDENTIFIERS_SERVICE = "device_identifiers";
/**
+ * Service that provides System font data.
+ */
+ public static final String FONT_SERVICE = "font";
+
+ /**
* Service to report a system health "incident"
* @hide
*/
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 8cc9a3a..90f08cd8 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3966,6 +3966,12 @@
public static final String EXTRA_EPHEMERAL_TOKEN = "android.intent.extra.EPHEMERAL_TOKEN";
/**
+ * The version code of the app to install components from.
+ * @hide
+ */
+ public static final String EXTRA_VERSION_CODE = "android.intent.extra.VERSION_CODE";
+
+ /**
* A Bundle forming a mapping of potential target package names to different extras Bundles
* to add to the default intent extras in {@link #EXTRA_INTENT} when used with
* {@link #ACTION_CHOOSER}. Each key should be a package name. The package need not
@@ -4842,6 +4848,10 @@
* or not running) apps, regardless of whether that would be done by default. By
* default they will only receive broadcasts if the broadcast has specified an
* explicit component or package name.
+ *
+ * NOTE: dumpstate uses this flag numerically, so when its value is changed
+ * the broadcast code there must also be changed to match.
+ *
* @hide
*/
public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000;
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
index 5cd15dd..b091d7e 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -72,6 +72,11 @@
*/
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 f620088..1d7b8f2 100644
--- a/core/java/android/content/pm/EphemeralResolveInfo.java
+++ b/core/java/android/content/pm/EphemeralResolveInfo.java
@@ -43,9 +43,11 @@
private final String mPackageName;
/** The filters used to match domain */
private final List<EphemeralIntentFilter> mFilters;
+ /** The version code of the app that this class resolves to */
+ private final int mVersionCode;
/** Filters only for legacy clients */
@Deprecated
- private List<IntentFilter> mLegacyFilters;
+ private final List<IntentFilter> mLegacyFilters;
@Deprecated
public EphemeralResolveInfo(@NonNull Uri uri, @NonNull String packageName,
@@ -59,10 +61,17 @@
mFilters.add(new EphemeralIntentFilter(packageName, filters));
mLegacyFilters = new ArrayList<IntentFilter>(filters.size());
mLegacyFilters.addAll(filters);
+ mVersionCode = -1;
+ }
+
+ @Deprecated
+ public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName,
+ @Nullable List<EphemeralIntentFilter> filters) {
+ this(digest, packageName, filters, -1 /*versionCode*/);
}
public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName,
- @Nullable List<EphemeralIntentFilter> filters) {
+ @Nullable List<EphemeralIntentFilter> filters, int versionConde) {
// validate arguments
if ((packageName == null && (filters != null && filters.size() != 0))
|| (packageName != null && (filters == null || filters.size() == 0))) {
@@ -75,7 +84,9 @@
} else {
mFilters = null;
}
+ mLegacyFilters = null;
mPackageName = packageName;
+ mVersionCode = versionConde;
}
public EphemeralResolveInfo(@NonNull String hostName, @Nullable String packageName,
@@ -88,6 +99,7 @@
mPackageName = in.readString();
mFilters = new ArrayList<EphemeralIntentFilter>();
in.readList(mFilters, null /*loader*/);
+ mVersionCode = in.readInt();
mLegacyFilters = new ArrayList<IntentFilter>();
in.readList(mLegacyFilters, null /*loader*/);
}
@@ -104,15 +116,19 @@
return mPackageName;
}
+ public List<EphemeralIntentFilter> getIntentFilters() {
+ return mFilters;
+ }
+
+ public int getVersionCode() {
+ return mVersionCode;
+ }
+
@Deprecated
public List<IntentFilter> getFilters() {
return mLegacyFilters;
}
- public List<EphemeralIntentFilter> getIntentFilters() {
- return mFilters;
- }
-
@Override
public int describeContents() {
return 0;
@@ -123,6 +139,7 @@
out.writeParcelable(mDigest, flags);
out.writeString(mPackageName);
out.writeList(mFilters);
+ out.writeInt(mVersionCode);
out.writeList(mLegacyFilters);
}
diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.java b/core/java/android/content/pm/IntentFilterVerificationInfo.java
index f12abf3..068973b 100644
--- a/core/java/android/content/pm/IntentFilterVerificationInfo.java
+++ b/core/java/android/content/pm/IntentFilterVerificationInfo.java
@@ -22,6 +22,7 @@
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -36,6 +37,7 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Set;
/**
* The {@link com.android.server.pm.PackageManagerService} maintains some
@@ -43,6 +45,7 @@
*
* @hide
*/
+@SystemApi
public final class IntentFilterVerificationInfo implements Parcelable {
private static final String TAG = IntentFilterVerificationInfo.class.getName();
@@ -55,22 +58,26 @@
private String mPackageName;
private int mMainStatus;
+ /** @hide */
public IntentFilterVerificationInfo() {
mPackageName = null;
mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
}
+ /** @hide */
public IntentFilterVerificationInfo(String packageName, ArraySet<String> domains) {
mPackageName = packageName;
mDomains = domains;
mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
}
+ /** @hide */
public IntentFilterVerificationInfo(XmlPullParser parser)
throws IOException, XmlPullParserException {
readFromXml(parser);
}
+ /** @hide */
public IntentFilterVerificationInfo(Parcel source) {
readFromParcel(source);
}
@@ -83,6 +90,7 @@
return mMainStatus;
}
+ /** @hide */
public void setStatus(int s) {
if (s >= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED &&
s <= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
@@ -92,14 +100,16 @@
}
}
- public ArraySet<String> getDomains() {
+ public Set<String> getDomains() {
return mDomains;
}
+ /** @hide */
public void setDomains(ArraySet<String> list) {
mDomains = list;
}
+ /** @hide */
public String getDomainsString() {
StringBuilder sb = new StringBuilder();
for (String str : mDomains) {
@@ -135,6 +145,7 @@
}
}
+ /** @hide */
public void readFromXml(XmlPullParser parser) throws XmlPullParserException,
IOException {
mPackageName = getStringFromXml(parser, ATTR_PACKAGE_NAME, null);
@@ -170,6 +181,7 @@
}
}
+ /** @hide */
public void writeToXml(XmlSerializer serializer) throws IOException {
serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName);
serializer.attribute(null, ATTR_STATUS, String.valueOf(mMainStatus));
@@ -180,10 +192,12 @@
}
}
+ /** @hide */
public String getStatusString() {
return getStatusStringFromValue(((long)mMainStatus) << 32);
}
+ /** @hide */
public static String getStatusStringFromValue(long val) {
StringBuilder sb = new StringBuilder();
switch ((int)(val >> 32)) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 7bdc56d..98edbf8 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1444,6 +1444,7 @@
*
* @hide
*/
+ @SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0;
/**
@@ -1454,6 +1455,7 @@
*
* @hide
*/
+ @SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1;
/**
@@ -1465,6 +1467,7 @@
*
* @hide
*/
+ @SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2;
/**
@@ -1476,6 +1479,7 @@
*
* @hide
*/
+ @SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3;
/**
@@ -1489,6 +1493,7 @@
*
* @hide
*/
+ @SystemApi
public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4;
/**
@@ -5070,6 +5075,7 @@
*
* @hide
*/
+ @SystemApi
public abstract int getIntentVerificationStatusAsUser(String packageName, @UserIdInt int userId);
/**
@@ -5092,6 +5098,7 @@
*
* @hide
*/
+ @SystemApi
public abstract boolean updateIntentVerificationStatusAsUser(String packageName, int status,
@UserIdInt int userId);
@@ -5107,6 +5114,7 @@
*
* @hide
*/
+ @SystemApi
public abstract List<IntentFilterVerificationInfo> getIntentFilterVerifications(
String packageName);
@@ -5121,6 +5129,7 @@
*
* @hide
*/
+ @SystemApi
public abstract List<IntentFilter> getAllIntentFilters(String packageName);
/**
@@ -5134,6 +5143,7 @@
* @hide
*/
@TestApi
+ @SystemApi
public abstract String getDefaultBrowserPackageNameAsUser(@UserIdInt int userId);
/**
@@ -5148,6 +5158,7 @@
*
* @hide
*/
+ @SystemApi
public abstract boolean setDefaultBrowserPackageNameAsUser(String packageName,
@UserIdInt int userId);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d8d7abe..ca3011e 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3899,6 +3899,9 @@
a.info.taskAffinity = buildTaskAffinityName(owner.applicationInfo.packageName,
owner.applicationInfo.taskAffinity, str, outError);
+ a.info.splitName =
+ sa.getNonConfigurationString(R.styleable.AndroidManifestActivity_splitName, 0);
+
a.info.flags = 0;
if (sa.getBoolean(
R.styleable.AndroidManifestActivity_multiprocess, false)) {
@@ -4520,6 +4523,9 @@
com.android.internal.R.styleable.AndroidManifestProvider_initOrder,
0);
+ p.info.splitName =
+ sa.getNonConfigurationString(R.styleable.AndroidManifestProvider_splitName, 0);
+
p.info.flags = 0;
if (sa.getBoolean(
@@ -4816,6 +4822,9 @@
s.info.permission = str.length() > 0 ? str.toString().intern() : null;
}
+ s.info.splitName =
+ sa.getNonConfigurationString(R.styleable.AndroidManifestService_splitName, 0);
+
s.info.flags = 0;
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestService_stopWithTask,
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index ad11307..c3185a7 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -40,6 +40,7 @@
import android.annotation.XmlRes;
import android.content.pm.ActivityInfo;
import android.graphics.Movie;
+import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable.ConstantState;
import android.graphics.drawable.DrawableInflater;
@@ -333,7 +334,35 @@
return res;
}
throw new NotFoundException("String resource ID #0x"
- + Integer.toHexString(id));
+ + Integer.toHexString(id));
+ }
+
+ /**
+ * Return the Typeface value associated with a particular resource ID.
+ * {@more}
+ *
+ * @param id The desired resource identifier, as generated by the aapt
+ * tool. This integer encodes the package, type, and resource
+ * entry. The value 0 is an invalid identifier.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return Typeface The Typeface data associated with the resource.
+ */
+ @NonNull public Typeface getFont(@StringRes int id) throws NotFoundException {
+ final TypedValue value = obtainTempTypedValue();
+ try {
+ final ResourcesImpl impl = mResourcesImpl;
+ impl.getValue(id, value, true);
+ Typeface typeface = impl.loadFont(value, id);
+ if (typeface != null) {
+ return typeface;
+ }
+ } finally {
+ releaseTempTypedValue(value);
+ }
+ throw new NotFoundException("Font resource ID #0x"
+ + Integer.toHexString(id));
}
/**
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index eb010e4..05892e0 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -31,6 +31,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
import android.content.res.Resources.NotFoundException;
+import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.icu.text.PluralRules;
@@ -47,6 +48,7 @@
import android.view.Display;
import android.view.DisplayAdjustments;
+import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Locale;
@@ -740,6 +742,36 @@
}
/**
+ * Loads a font from XML or resources stream.
+ */
+ @Nullable
+ public Typeface loadFont(TypedValue value, int id) {
+ if (value.string == null) {
+ throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
+ + Integer.toHexString(id) + ") is not a Font: " + value);
+ }
+
+ final String file = value.string.toString();
+
+ if (DEBUG_LOAD) {
+ Log.v(TAG, "Loading font for cookie " + value.assetCookie + ": " + file);
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
+ try {
+ if (file.endsWith(".xml")) {
+ // TODO handle xml type font definitions
+ } else {
+ return Typeface.createFromResources(
+ mAssets, value.string.toString(), value.assetCookie);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ return null;
+ }
+
+ /**
* Given the value and id, we can get the XML filename as in value.data, based on that, we
* first try to load CSL from the cache. If not found, try to get from the constant state.
* Last, parse the XML and generate the CSL.
diff --git a/core/java/android/hardware/HardwareBuffer.aidl b/core/java/android/hardware/HardwareBuffer.aidl
new file mode 100644
index 0000000..5bdd966
--- /dev/null
+++ b/core/java/android/hardware/HardwareBuffer.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware;
+
+parcelable HardwareBuffer;
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
new file mode 100644
index 0000000..fffb1d7
--- /dev/null
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import dalvik.annotation.optimization.FastNative;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * HardwareBuffer wraps a native <code>AHardwareBuffer</code> object, which is a low-level object
+ * representing a memory buffer accessible by various hardware units. HardwareBuffer allows sharing
+ * buffers across different application processes. In particular, HardwareBuffers may be mappable
+ * to memory accessibly to various hardware systems, such as the GPU, a sensor or context hub, or
+ * other auxiliary processing units.
+ *
+ * For more information, see the NDK documentation for <code>AHardwareBuffer</code>.
+ */
+public final class HardwareBuffer implements Parcelable {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({RGBA_8888, RGBA_FP16, RGBX_8888, RGB_888, RGB_565})
+ public @interface Format {};
+
+ /** Format: 8 bits each red, green, blue, alpha */
+ public static final int RGBA_8888 = 1;
+ /** Format: 8 bits each red, green, blue, alpha, alpha is always 0xFF */
+ public static final int RGBX_8888 = 2;
+ /** Format: 8 bits each red, green, blue, no alpha */
+ public static final int RGB_888 = 3;
+ /** Format: 5 bits each red and blue, 6 bits green, no alpha */
+ public static final int RGB_565 = 4;
+ /** Format: 16 bits each red, green, blue, alpha */
+ public static final int RGBA_FP16 = 5;
+
+ // Note: do not rename, this field is used by native code
+ private long mNativeObject;
+
+ // Invoked on destruction
+ private Runnable mCleaner;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {USAGE0_CPU_READ, USAGE0_CPU_READ_OFTEN, USAGE0_CPU_WRITE,
+ USAGE0_CPU_WRITE_OFTEN, USAGE0_GPU_SAMPLED_IMAGE, USAGE0_GPU_COLOR_OUTPUT,
+ USAGE0_GPU_STORAGE_IMAGE, USAGE0_GPU_CUBEMAP, USAGE0_GPU_DATA_BUFFER,
+ USAGE0_PROTECTED_CONTENT, USAGE0_SENSOR_DIRECT_DATA, USAGE0_VIDEO_ENCODE})
+ public @interface Usage0 {};
+
+ /** Usage0: the buffer will sometimes be read by the CPU */
+ public static final long USAGE0_CPU_READ = (1 << 1);
+ /** Usage0: the buffer will often be read by the CPU*/
+ public static final long USAGE0_CPU_READ_OFTEN = (1 << 2 | USAGE0_CPU_READ);
+ /** Usage0: the buffer will sometimes be written to by the CPU */
+ public static final long USAGE0_CPU_WRITE = (1 << 5);
+ /** Usage0: the buffer will often be written to by the CPU */
+ public static final long USAGE0_CPU_WRITE_OFTEN = (1 << 6 | USAGE0_CPU_WRITE);
+ /** Usage0: the buffer will be read from by the GPU */
+ public static final long USAGE0_GPU_SAMPLED_IMAGE = (1 << 10);
+ /** Usage0: the buffer will be written to by the GPU */
+ public static final long USAGE0_GPU_COLOR_OUTPUT = (1 << 11);
+ /** Usage0: the buffer will be read from and written to by the GPU */
+ public static final long USAGE0_GPU_STORAGE_IMAGE = (USAGE0_GPU_SAMPLED_IMAGE |
+ USAGE0_GPU_COLOR_OUTPUT);
+ /** Usage0: the buffer will be used as a cubemap texture */
+ public static final long USAGE0_GPU_CUBEMAP = (1 << 13);
+ /** Usage0: the buffer will be used as a shader storage or uniform buffer object*/
+ public static final long USAGE0_GPU_DATA_BUFFER = (1 << 14);
+ /** Usage0: the buffer must not be used outside of a protected hardware path */
+ public static final long USAGE0_PROTECTED_CONTENT = (1 << 18);
+ /** Usage0: the buffer will be used for sensor direct data */
+ public static final long USAGE0_SENSOR_DIRECT_DATA = (1 << 29);
+ /** Usage0: the buffer will be read by a hardware video encoder */
+ public static final long USAGE0_VIDEO_ENCODE = (1 << 21);
+
+ // The approximate size of a native AHardwareBuffer object.
+ private static final long NATIVE_HARDWARE_BUFFER_SIZE = 232;
+ /**
+ * Creates a new <code>HardwareBuffer</code> instance.
+ *
+ * <p>Calling this method will throw an <code>IllegalStateException</code> if
+ * format is not a supported Format type.</p>
+ *
+ * @param width The width in pixels of the buffer
+ * @param height The height in pixels of the buffer
+ * @param format The format of each pixel, one of {@link #RGBA_8888}, {@link #RGBA_FP16},
+ * {@link #RGBX_8888}, {@link #RGB_565}, {@link #RGB_888}
+ * @param layers The number of layers in the buffer
+ * @param usage Flags describing how the buffer will be used, one of
+ * {@link #USAGE0_CPU_READ}, {@link #USAGE0_CPU_READ_OFTEN}, {@link #USAGE0_CPU_WRITE},
+ * {@link #USAGE0_CPU_WRITE_OFTEN}, {@link #USAGE0_GPU_SAMPLED_IMAGE},
+ * {@link #USAGE0_GPU_COLOR_OUTPUT},{@link #USAGE0_GPU_STORAGE_IMAGE},
+ * {@link #USAGE0_GPU_CUBEMAP}, {@link #USAGE0_GPU_DATA_BUFFER},
+ * {@link #USAGE0_PROTECTED_CONTENT}, {@link #USAGE0_SENSOR_DIRECT_DATA},
+ * {@link #USAGE0_VIDEO_ENCODE}
+ *
+ * @return A <code>HardwareBuffer</code> instance if successful, or throws an
+ * IllegalArgumentException if the dimensions passed are invalid (either zero, negative, or
+ * too large to allocate), if the format is not supported, if the requested number of layers
+ * is less than one or not supported, or if the passed usage flags are not a supported set.
+ */
+ @NonNull
+ public static HardwareBuffer create(int width, int height, @Format int format, int layers,
+ @Usage0 long usage) {
+ if (!HardwareBuffer.isSupportedFormat(format)) {
+ throw new IllegalArgumentException("Invalid pixel format " + format);
+ }
+ if (width <= 0) {
+ throw new IllegalArgumentException("Invalid width " + width);
+ }
+ if (height <= 0) {
+ throw new IllegalArgumentException("Invalid height " + height);
+ }
+ if (layers <= 0) {
+ throw new IllegalArgumentException("Invalid layer count " + layers);
+ }
+ long nativeObject = nCreateHardwareBuffer(width, height, format, layers, usage);
+ if (nativeObject == 0) {
+ throw new IllegalArgumentException("Unable to create a HardwareBuffer, either the " +
+ "dimensions passed were too large, too many image layers were requested, " +
+ "or an invalid set of usage flags was passed");
+ }
+ return new HardwareBuffer(nativeObject);
+ }
+
+ /**
+ * Private use only. See {@link #create(int, int, int, int, int, long, long)}. May also be
+ * called from JNI using an already allocated native <code>HardwareBuffer</code>.
+ */
+ private HardwareBuffer(long nativeObject) {
+ mNativeObject = nativeObject;
+
+ long nativeSize = NATIVE_HARDWARE_BUFFER_SIZE;
+ NativeAllocationRegistry registry = new NativeAllocationRegistry(
+ HardwareBuffer.class.getClassLoader(), nGetNativeFinalizer(), nativeSize);
+ mCleaner = registry.registerNativeAllocation(this, mNativeObject);
+ }
+
+ /**
+ * Returns the width of this buffer in pixels.
+ */
+ public int getWidth() {
+ if (mNativeObject == 0) {
+ throw new IllegalStateException("This HardwareBuffer has been destroyed and its width "
+ + "cannot be obtained.");
+ }
+ return nGetWidth(mNativeObject);
+ }
+
+ /**
+ * Returns the height of this buffer in pixels.
+ */
+ public int getHeight() {
+ if (mNativeObject == 0) {
+ throw new IllegalStateException("This HardwareBuffer has been destroyed and its height "
+ + "cannot be obtained.");
+ }
+ return nGetHeight(mNativeObject);
+ }
+
+ /**
+ * Returns the format of this buffer, one of {@link #RGBA_8888}, {@link #RGBA_FP16},
+ * {@link #RGBX_8888}, {@link #RGB_565}, or {@link #RGB_888}.
+ */
+ public int getFormat() {
+ if (mNativeObject == 0) {
+ throw new IllegalStateException("This HardwareBuffer has been destroyed and its format "
+ + "cannot be obtained.");
+ }
+ return nGetFormat(mNativeObject);
+ }
+
+ /**
+ * Returns the number of layers in this buffer.
+ */
+ public int getLayers() {
+ if (mNativeObject == 0) {
+ throw new IllegalStateException("This HardwareBuffer has been destroyed and its layer "
+ + "count cannot be obtained.");
+ }
+ return nGetLayers(mNativeObject);
+ }
+
+ /**
+ * Returns the usage flags of the usage hints set on this buffer.
+ */
+ public long getUsage() {
+ if (mNativeObject == 0) {
+ throw new IllegalStateException("This HardwareBuffer has been destroyed and its usage "
+ + "cannot be obtained.");
+ }
+ return nGetUsage(mNativeObject);
+ }
+
+ /**
+ * Destroys this buffer immediately. Calling this method frees up any
+ * underlying native resources. After calling this method, this buffer
+ * must not be used in any way.
+ *
+ * @see #isDestroyed()
+ */
+ public void destroy() {
+ if (mNativeObject != 0) {
+ mNativeObject = 0;
+ mCleaner.run();
+ mCleaner = null;
+ }
+ }
+
+ /**
+ * Indicates whether this buffer has been destroyed. A destroyed buffer
+ * cannot be used in any way: the buffer cannot be written to a parcel, etc.
+ *
+ * @return True if this <code>HardwareBuffer</code> is in a destroyed state,
+ * false otherwise.
+ *
+ * @see #destroy()
+ */
+ public boolean isDestroyed() {
+ return mNativeObject == 0;
+ }
+
+ @Override
+ public int describeContents() {
+ return Parcelable.CONTENTS_FILE_DESCRIPTOR;
+ }
+
+ /**
+ * Flatten this object in to a Parcel.
+ *
+ * <p>Calling this method will throw an <code>IllegalStateException</code> if
+ * {@link #destroy()} has been previously called.</p>
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mNativeObject == 0) {
+ throw new IllegalStateException("This HardwareBuffer has been destroyed and cannot be "
+ + "written to a parcel.");
+ }
+ nWriteHardwareBufferToParcel(mNativeObject, dest);
+ }
+
+ public static final Parcelable.Creator<HardwareBuffer> CREATOR =
+ new Parcelable.Creator<HardwareBuffer>() {
+ public HardwareBuffer createFromParcel(Parcel in) {
+ long nativeObject = nReadHardwareBufferFromParcel(in);
+ if (nativeObject != 0) {
+ return new HardwareBuffer(nativeObject);
+ }
+ return null;
+ }
+
+ public HardwareBuffer[] newArray(int size) {
+ return new HardwareBuffer[size];
+ }
+ };
+
+ /**
+ * Validates whether a particular format is supported by HardwareBuffer.
+ *
+ * @param format The format to validate.
+ *
+ * @return True if <code>format</code> is a supported format. false otherwise.
+ * See {@link #create(int, int, int, int, int, long, long)}.a
+ */
+ private static boolean isSupportedFormat(@Format int format) {
+ switch(format) {
+ case RGBA_8888:
+ case RGBA_FP16:
+ case RGBX_8888:
+ case RGB_565:
+ case RGB_888:
+ return true;
+ }
+ return false;
+ }
+
+ private static native long nCreateHardwareBuffer(int width, int height, int format, int layers,
+ long usage);
+ private static native long nGetNativeFinalizer();
+ private static native void nWriteHardwareBufferToParcel(long nativeObject, Parcel dest);
+ private static native long nReadHardwareBufferFromParcel(Parcel in);
+ @FastNative
+ private static native int nGetWidth(long nativeObject);
+ @FastNative
+ private static native int nGetHeight(long nativeObject);
+ @FastNative
+ private static native int nGetFormat(long nativeObject);
+ @FastNative
+ private static native int nGetLayers(long nativeObject);
+ @FastNative
+ private static native long nGetUsage(long nativeObject);
+}
diff --git a/core/java/com/android/internal/logging/LogBuilder.java b/core/java/android/metrics/LogMaker.java
similarity index 76%
rename from core/java/com/android/internal/logging/LogBuilder.java
rename to core/java/android/metrics/LogMaker.java
index 7eda3da..0aef532 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 @@
* @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 @@
out[i * 2] = entries.keyAt(i);
out[i * 2 + 1] = entries.valueAt(i);
}
+ int size = out.toString().getBytes().length;
+ if (size > MAX_SERIALIZED_SIZE) {
+ Log.i(TAG, "Log line too long, did not emit: " + size + " bytes.");
+ throw new RuntimeException();
+ }
return out;
}
public void deserialize(Object[] items) {
int i = 0;
- while(i < items.length) {
+ while (i < items.length) {
Object key = items[i++];
Object value = i < items.length ? items[i++] : null;
if (key instanceof Integer) {
entries.put((Integer) key, value);
} else {
- Log.i(TAG, "Invalid key " + key.toString());
+ Log.i(TAG, "Invalid key " + key.toString());
}
}
}
diff --git a/core/java/com/android/internal/logging/MetricsReader.java b/core/java/android/metrics/MetricsReader.java
similarity index 92%
rename from core/java/com/android/internal/logging/MetricsReader.java
rename to core/java/android/metrics/MetricsReader.java
index c4fc963..079c2c9 100644
--- a/core/java/com/android/internal/logging/MetricsReader.java
+++ b/core/java/android/metrics/MetricsReader.java
@@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.internal.logging;
+package android.metrics;
+
+import android.annotation.SystemApi;
import com.android.internal.logging.legacy.LegacyConversionLogger;
import com.android.internal.logging.legacy.EventLogCollector;
@@ -22,10 +24,12 @@
/**
* Read platform logs.
+ * @hide
*/
+@SystemApi
public class MetricsReader {
private EventLogCollector mReader;
- private Queue<LogBuilder> mEventQueue;
+ private Queue<LogMaker> mEventQueue;
private long mLastEventMs;
private long mCheckpointMs;
@@ -57,7 +61,7 @@
}
/* Next entry in the current log session. */
- public LogBuilder next() {
+ public LogMaker next() {
return mEventQueue == null ? null : mEventQueue.remove();
}
diff --git a/core/java/android/net/INetworkScoreCache.aidl b/core/java/android/net/INetworkScoreCache.aidl
index 35601ce..1da7d67 100644
--- a/core/java/android/net/INetworkScoreCache.aidl
+++ b/core/java/android/net/INetworkScoreCache.aidl
@@ -34,7 +34,7 @@
* the current scores for each network for debugging purposes.
* @hide
*/
-interface INetworkScoreCache
+oneway interface INetworkScoreCache
{
void updateScores(in List<ScoredNetwork> networks);
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 20de370..e4cdbce 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -44,7 +44,6 @@
// 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 b03c907..35a266b 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -291,4 +291,6 @@
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 c6ff476..626d6f4 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -24,27 +24,32 @@
import android.app.ActivityThread;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
-import android.os.ProxyFileDescriptorCallback;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.ProxyFileDescriptorCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.AppFuseMount;
@@ -60,6 +65,7 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -1396,6 +1402,222 @@
}
}
+ /**
+ * Return quota size in bytes for cached data belonging to the calling app.
+ * <p>
+ * If your app goes above this quota, your cached files will be some of the
+ * first to be deleted when additional disk space is needed. Conversely, if
+ * your app stays under this quota, your cached files will be some of the
+ * last to be deleted when additional disk space is needed.
+ * <p>
+ * This quota may change over time depending on how frequently the user
+ * interacts with your app, and depending on how much disk space is used.
+ * <p>
+ * Cached data tracked by this method always includes
+ * {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}, and
+ * it also includes {@link Context#getExternalCacheDir()} if the primary
+ * shared/external storage is hosted on the same storage device as your
+ * private data.
+ * <p class="note">
+ * Note: if your app uses the {@code android:sharedUserId} manifest feature,
+ * then cached data for all packages in your shared UID is tracked together
+ * as a single unit.
+ * </p>
+ *
+ * @see #getCacheQuotaBytes()
+ * @see #getCacheSizeBytes()
+ * @see #getExternalCacheQuotaBytes()
+ * @see #getExternalCacheSizeBytes()
+ */
+ public long getCacheQuotaBytes() {
+ try {
+ final ApplicationInfo app = mContext.getApplicationInfo();
+ return mStorageManager.getCacheQuotaBytes(app.volumeUuid, app.uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return total size in bytes of cached data belonging to the calling app.
+ * <p>
+ * Cached data tracked by this method always includes
+ * {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}, and
+ * it also includes {@link Context#getExternalCacheDir()} if the primary
+ * shared/external storage is hosted on the same storage device as your
+ * private data.
+ * <p class="note">
+ * Note: if your app uses the {@code android:sharedUserId} manifest feature,
+ * then cached data for all packages in your shared UID is tracked together
+ * as a single unit.
+ * </p>
+ *
+ * @see #getCacheQuotaBytes()
+ * @see #getCacheSizeBytes()
+ * @see #getExternalCacheQuotaBytes()
+ * @see #getExternalCacheSizeBytes()
+ */
+ public long getCacheSizeBytes() {
+ try {
+ final ApplicationInfo app = mContext.getApplicationInfo();
+ return mStorageManager.getCacheSizeBytes(app.volumeUuid, app.uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return quota size in bytes for cached data on primary shared/external
+ * storage belonging to the calling app.
+ * <p>
+ * If primary shared/external storage is hosted on the same storage device
+ * as your private data, this method will return -1, since all data stored
+ * under {@link Context#getExternalCacheDir()} will be counted under
+ * {@link #getCacheQuotaBytes()}.
+ * <p class="note">
+ * Note: if your app uses the {@code android:sharedUserId} manifest feature,
+ * then cached data for all packages in your shared UID is tracked together
+ * as a single unit.
+ * </p>
+ */
+ public long getExternalCacheQuotaBytes() {
+ final ApplicationInfo app = mContext.getApplicationInfo();
+ final String primaryUuid = getPrimaryStorageUuid();
+ if (Objects.equals(app.volumeUuid, primaryUuid)) {
+ return -1;
+ }
+ try {
+ return mStorageManager.getCacheQuotaBytes(primaryUuid, app.uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return total size in bytes of cached data on primary shared/external
+ * storage belonging to the calling app.
+ * <p>
+ * If primary shared/external storage is hosted on the same storage device
+ * as your private data, this method will return -1, since all data stored
+ * under {@link Context#getExternalCacheDir()} will be counted under
+ * {@link #getCacheQuotaBytes()}.
+ * <p class="note">
+ * Note: if your app uses the {@code android:sharedUserId} manifest feature,
+ * then cached data for all packages in your shared UID is tracked together
+ * as a single unit.
+ * </p>
+ */
+ public long getExternalCacheSizeBytes() {
+ final ApplicationInfo app = mContext.getApplicationInfo();
+ final String primaryUuid = getPrimaryStorageUuid();
+ if (Objects.equals(app.volumeUuid, primaryUuid)) {
+ return -1;
+ }
+ try {
+ return mStorageManager.getCacheSizeBytes(primaryUuid, app.uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static final String XATTR_ATOMIC = "user.atomic";
+ private static final String XATTR_TOMBSTONE = "user.tombstone";
+
+ /** {@hide} */
+ private static void setCacheBehavior(File path, String name, boolean enabled)
+ throws IOException {
+ if (!path.isDirectory()) {
+ throw new IOException("Cache behavior can only be set on directories");
+ }
+ if (enabled) {
+ try {
+ Os.setxattr(path.getAbsolutePath(), name,
+ "1".getBytes(StandardCharsets.UTF_8), 0);
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ } else {
+ try {
+ Os.removexattr(path.getAbsolutePath(), name);
+ } catch (ErrnoException e) {
+ if (e.errno != OsConstants.ENODATA) {
+ throw e.rethrowAsIOException();
+ }
+ }
+ }
+ }
+
+ /** {@hide} */
+ private static boolean isCacheBehavior(File path, String name) throws IOException {
+ try {
+ Os.getxattr(path.getAbsolutePath(), name);
+ return true;
+ } catch (ErrnoException e) {
+ if (e.errno != OsConstants.ENODATA) {
+ throw e.rethrowAsIOException();
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Enable or disable special cache behavior that treats this directory and
+ * its contents as an atomic unit.
+ * <p>
+ * When enabled and this directory is considered for automatic deletion by
+ * the OS, all contained files will either be deleted together, or not at
+ * all. This is useful when you have a directory that contains several
+ * related metadata files that depend on each other, such as movie file and
+ * a subtitle file.
+ * <p>
+ * When enabled, the <em>newest</em> {@link File#lastModified()} value of
+ * any contained files is considered the modified time of the entire
+ * directory.
+ * <p>
+ * This behavior can only be set on a directory, and it applies recursively
+ * to all contained files and directories.
+ */
+ public void setCacheBehaviorAtomic(File path, boolean atomic) throws IOException {
+ setCacheBehavior(path, XATTR_ATOMIC, atomic);
+ }
+
+ /**
+ * Read the current value set by
+ * {@link #setCacheBehaviorAtomic(File, boolean)}.
+ */
+ public boolean isCacheBehaviorAtomic(File path) throws IOException {
+ return isCacheBehavior(path, XATTR_ATOMIC);
+ }
+
+ /**
+ * Enable or disable special cache behavior that leaves deleted cache files
+ * intact as tombstones.
+ * <p>
+ * When enabled and a file contained in this directory is automatically
+ * deleted by the OS, the file will be truncated to have a length of 0 bytes
+ * instead of being fully deleted. This is useful if you need to distinguish
+ * between a file that was deleted versus one that never existed.
+ * <p>
+ * This behavior can only be set on a directory, and it applies recursively
+ * to all contained files and directories.
+ * <p class="note">
+ * Note: this behavior is ignored completely if the user explicitly requests
+ * that all cached data be cleared.
+ * </p>
+ */
+ public void setCacheBehaviorTombstone(File path, boolean tombstone) throws IOException {
+ setCacheBehavior(path, XATTR_TOMBSTONE, tombstone);
+ }
+
+ /**
+ * Read the current value set by
+ * {@link #setCacheBehaviorTombstone(File, boolean)}.
+ */
+ public boolean isCacheBehaviorTombstone(File path) throws IOException {
+ return isCacheBehavior(path, XATTR_TOMBSTONE);
+ }
+
private final Object mFuseAppLoopLock = new Object();
@GuardedBy("mFuseAppLoopLock")
diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java
new file mode 100644
index 0000000..90e710f
--- /dev/null
+++ b/core/java/android/provider/FontsContract.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.provider;
+
+import android.app.ActivityThread;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.graphics.fonts.FontRequest;
+import android.graphics.fonts.FontResult;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+
+/**
+ * Utility class to deal with Font ContentProviders.
+ */
+public class FontsContract {
+ private static final String TAG = "FontsContract";
+
+ /**
+ * Defines the constants used in a response from a Font Provider. The cursor returned from the
+ * query should have the ID column populated with the content uri ID for the resulting font.
+ * This should point to a real file or shared memory, as the client will mmap the given file
+ * descriptor. Pipes, sockets and other non-mmap-able file descriptors will fail to load in the
+ * client application.
+ */
+ public static final class Columns implements BaseColumns {
+ /**
+ * Constant used to request data from a font provider. The cursor returned from the query
+ * should have this column populated with an int for the ttc index for the resulting font.
+ */
+ public static final String TTC_INDEX = "font_ttc_index";
+ /**
+ * Constant used to request data from a font provider. The cursor returned from the query
+ * may populate this column with the font variation settings String information for the
+ * font.
+ */
+ public static final String VARIATION_SETTINGS = "font_variation_settings";
+ /**
+ * Constant used to request data from a font provider. The cursor returned from the query
+ * should have this column populated with the int style for the resulting font. This should
+ * be one of {@link android.graphics.Typeface#NORMAL},
+ * {@link android.graphics.Typeface#BOLD}, {@link android.graphics.Typeface#ITALIC} or
+ * {@link android.graphics.Typeface#BOLD_ITALIC}
+ */
+ public static final String STYLE = "font_style";
+ }
+
+ /**
+ * Constant used to identify the List of {@link ParcelFileDescriptor} item in the Bundle
+ * returned to the ResultReceiver in getFont.
+ * @hide
+ */
+ public static final String PARCEL_FONT_RESULTS = "font_results";
+
+ /** @hide */
+ public static final int RESULT_CODE_OK = 0;
+ /** @hide */
+ public static final int RESULT_CODE_FONT_NOT_FOUND = 1;
+ /** @hide */
+ public static final int RESULT_CODE_PROVIDER_NOT_FOUND = 2;
+
+ private static final int THREAD_RENEWAL_THRESHOLD_MS = 10000;
+
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private Handler mHandler;
+ @GuardedBy("mLock")
+ private HandlerThread mThread;
+
+ /** @hide */
+ public FontsContract() {
+ // TODO: investigate if the system context is the best option here. ApplicationContext or
+ // the one passed by developer?
+ // TODO: Looks like ActivityThread.currentActivityThread() can return null. Check when it
+ // returns null and check if we need to handle null case.
+ mContext = ActivityThread.currentActivityThread().getSystemContext();
+ mPackageManager = mContext.getPackageManager();
+ }
+
+ // We use a background thread to post the content resolving work for all requests on. This
+ // thread should be quit/stopped after all requests are done.
+ private final Runnable mReplaceDispatcherThreadRunnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ if (mThread != null) {
+ mThread.quitSafely();
+ mThread = null;
+ mHandler = null;
+ }
+ }
+ }
+ };
+
+ /**
+ * @hide
+ */
+ public void getFont(FontRequest request, ResultReceiver receiver) {
+ synchronized (mLock) {
+ if (mHandler == null) {
+ mThread = new HandlerThread("fonts", Process.THREAD_PRIORITY_BACKGROUND);
+ mThread.start();
+ mHandler = new Handler(mThread.getLooper());
+ }
+ mHandler.post(() -> {
+ String providerAuthority = request.getProviderAuthority();
+ // TODO: Implement cert checking for non-system apps
+ ProviderInfo providerInfo = mPackageManager.resolveContentProvider(
+ providerAuthority, PackageManager.MATCH_SYSTEM_ONLY);
+ if (providerInfo == null) {
+ receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
+ return;
+ }
+ Bundle result = getFontFromProvider(request, receiver, providerInfo);
+ if (result == null) {
+ receiver.send(RESULT_CODE_FONT_NOT_FOUND, null);
+ return;
+ }
+ receiver.send(RESULT_CODE_OK, result);
+ });
+ mHandler.removeCallbacks(mReplaceDispatcherThreadRunnable);
+ mHandler.postDelayed(mReplaceDispatcherThreadRunnable, THREAD_RENEWAL_THRESHOLD_MS);
+ }
+ }
+
+ private Bundle getFontFromProvider(FontRequest request, ResultReceiver receiver,
+ ProviderInfo providerInfo) {
+ ArrayList<FontResult> result = null;
+ Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(providerInfo.authority)
+ .build();
+ try (Cursor cursor = mContext.getContentResolver().query(uri, new String[] { Columns._ID,
+ Columns.TTC_INDEX, Columns.VARIATION_SETTINGS, Columns.STYLE },
+ "query = ?", new String[] { request.getQuery() }, null);) {
+ // TODO: Should we restrict the amount of fonts that can be returned?
+ // TODO: Write documentation explaining that all results should be from the same family.
+ if (cursor != null && cursor.getCount() > 0) {
+ result = new ArrayList<>();
+ final int idColumnIndex = cursor.getColumnIndex(Columns._ID);
+ final int ttcIndexColumnIndex = cursor.getColumnIndex(Columns.TTC_INDEX);
+ final int vsColumnIndex = cursor.getColumnIndex(Columns.VARIATION_SETTINGS);
+ final int styleColumnIndex = cursor.getColumnIndex(Columns.STYLE);
+ while (cursor.moveToNext()) {
+ long id = cursor.getLong(idColumnIndex);
+ Uri fileUri = ContentUris.withAppendedId(uri, id);
+ try {
+ ParcelFileDescriptor pfd =
+ mContext.getContentResolver().openFileDescriptor(fileUri, "r");
+ final int ttcIndex = cursor.getInt(ttcIndexColumnIndex);
+ final String variationSettings = cursor.getString(vsColumnIndex);
+ final int style = cursor.getInt(styleColumnIndex);
+ result.add(new FontResult(pfd, ttcIndex, variationSettings, style));
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "FileNotFoundException raised when interacting with content "
+ + "provider " + providerInfo.authority, e);
+ }
+ }
+ }
+ }
+ if (result != null && !result.isEmpty()) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList(PARCEL_FONT_RESULTS, result);
+ return bundle;
+ }
+ return null;
+ }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5dcc96a..e3da337 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1334,6 +1334,19 @@
= "android.settings.VR_LISTENER_SETTINGS";
/**
+ * Activity Action: Show Picture-in-picture settings.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PICTURE_IN_PICTURE_SETTINGS
+ = "android.settings.PICTURE_IN_PICTURE_SETTINGS";
+
+ /**
* Activity Action: Show Storage Manager settings.
* <p>
* Input: Nothing.
@@ -7601,6 +7614,14 @@
"hdmi_control_auto_device_off_enabled";
/**
+ * The interval in milliseconds at which location requests will be throttled when they are
+ * coming from the background.
+ * @hide
+ */
+ public static final String LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS =
+ "location_background_throttle_interval_ms";
+
+ /**
* Whether TV will switch to MHL port when a mobile device is plugged in.
* (0 = false, 1 = true)
* @hide
@@ -10007,6 +10028,17 @@
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 a4b6807..e48a0d0 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -319,7 +319,7 @@
*
* @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 694837e..d930689 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1166,11 +1166,12 @@
// System specified group key.
private String mOverrideGroupKey;
// Notification assistant channel override.
- private NotificationChannel mOverrideChannel;
+ private NotificationChannel mChannel;
// Notification assistant people override.
private ArrayList<String> mOverridePeople;
// Notification assistant snooze criteria.
private ArrayList<SnoozeCriterion> mSnoozeCriteria;
+ private boolean mShowBadge;
public Ranking() {}
@@ -1200,7 +1201,7 @@
}
/**
- * Returns the user specificed visibility for the package that posted
+ * Returns the user specified visibility for the package that posted
* this notification, or
* {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if
* no such preference has been expressed.
@@ -1233,7 +1234,7 @@
* Returns the importance of the notification, which dictates its
* modes of presentation, see: {@link NotificationManager#IMPORTANCE_DEFAULT}, etc.
*
- * @return the rank of the notification
+ * @return the importance of the notification
*/
public @NotificationManager.Importance int getImportance() {
return mImportance;
@@ -1258,12 +1259,11 @@
}
/**
- * If the {@link NotificationAssistantService} has overridden the channel this notification
- * was posted to, then this will not match the channel provided by the posting application
- * and this should be used to determine the interruptiveness of the notification instead.
+ * Returns the notification channel this notification was posted to, which dictates
+ * notification behavior and presentation.
*/
public NotificationChannel getChannel() {
- return mOverrideChannel;
+ return mChannel;
}
/**
@@ -1283,11 +1283,20 @@
return mSnoozeCriteria;
}
+ /**
+ * Returns whether this notification can be displayed as a badge.
+ *
+ * @return true if the notification can be displayed as a badge, false otherwise.
+ */
+ public boolean canShowBadge() {
+ return mShowBadge;
+ }
+
private void populate(String key, int rank, boolean matchesInterruptionFilter,
int visibilityOverride, int suppressedVisualEffects, int importance,
CharSequence explanation, String overrideGroupKey,
- NotificationChannel overrideChannel, ArrayList<String> overridePeople,
- ArrayList<SnoozeCriterion> snoozeCriteria) {
+ NotificationChannel channel, ArrayList<String> overridePeople,
+ ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge) {
mKey = key;
mRank = rank;
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1297,9 +1306,10 @@
mImportance = importance;
mImportanceExplanation = explanation;
mOverrideGroupKey = overrideGroupKey;
- mOverrideChannel = overrideChannel;
+ mChannel = channel;
mOverridePeople = overridePeople;
mSnoozeCriteria = snoozeCriteria;
+ mShowBadge = showBadge;
}
/**
@@ -1343,9 +1353,10 @@
private ArrayMap<String, Integer> mImportance;
private ArrayMap<String, String> mImportanceExplanation;
private ArrayMap<String, String> mOverrideGroupKeys;
- private ArrayMap<String, NotificationChannel> mOverrideChannels;
+ private ArrayMap<String, NotificationChannel> mChannels;
private ArrayMap<String, ArrayList<String>> mOverridePeople;
private ArrayMap<String, ArrayList<SnoozeCriterion>> mSnoozeCriteria;
+ private ArrayMap<String, Boolean> mShowBadge;
private RankingMap(NotificationRankingUpdate rankingUpdate) {
mRankingUpdate = rankingUpdate;
@@ -1373,7 +1384,8 @@
outRanking.populate(key, rank, !isIntercepted(key),
getVisibilityOverride(key), getSuppressedVisualEffects(key),
getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key),
- getOverrideChannel(key), getOverridePeople(key), getSnoozeCriteria(key));
+ getChannel(key), getOverridePeople(key), getSnoozeCriteria(key),
+ getShowBadge(key));
return rank >= 0;
}
@@ -1453,13 +1465,13 @@
return mOverrideGroupKeys.get(key);
}
- private NotificationChannel getOverrideChannel(String key) {
+ private NotificationChannel getChannel(String key) {
synchronized (this) {
- if (mOverrideChannels == null) {
- buildOverrideChannelsLocked();
+ if (mChannels == null) {
+ buildChannelsLocked();
}
}
- return mOverrideChannels.get(key);
+ return mChannels.get(key);
}
private ArrayList<String> getOverridePeople(String key) {
@@ -1480,6 +1492,16 @@
return mSnoozeCriteria.get(key);
}
+ private boolean getShowBadge(String key) {
+ synchronized (this) {
+ if (mShowBadge == null) {
+ buildShowBadgeLocked();
+ }
+ }
+ Boolean showBadge = mShowBadge.get(key);
+ return showBadge == null ? false : showBadge.booleanValue();
+ }
+
// Locked by 'this'
private void buildRanksLocked() {
String[] orderedKeys = mRankingUpdate.getOrderedKeys();
@@ -1544,11 +1566,11 @@
}
// Locked by 'this'
- private void buildOverrideChannelsLocked() {
- Bundle overrideChannels = mRankingUpdate.getOverrideChannels();
- mOverrideChannels = new ArrayMap<>(overrideChannels.size());
- for (String key : overrideChannels.keySet()) {
- mOverrideChannels.put(key, overrideChannels.getParcelable(key));
+ private void buildChannelsLocked() {
+ Bundle channels = mRankingUpdate.getChannels();
+ mChannels = new ArrayMap<>(channels.size());
+ for (String key : channels.keySet()) {
+ mChannels.put(key, channels.getParcelable(key));
}
}
@@ -1570,6 +1592,15 @@
}
}
+ // Locked by 'this'
+ private void buildShowBadgeLocked() {
+ Bundle showBadge = mRankingUpdate.getShowBadge();
+ mShowBadge = new ArrayMap<>(showBadge.size());
+ for (String key : showBadge.keySet()) {
+ mShowBadge.put(key, showBadge.getBoolean(key));
+ }
+ }
+
// ----------- Parcelable
@Override
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index a2cdeff..326b212 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -31,14 +31,16 @@
private final int[] mImportance;
private final Bundle mImportanceExplanation;
private final Bundle mOverrideGroupKeys;
- private final Bundle mOverrideChannels;
+ private final Bundle mChannels;
private final Bundle mOverridePeople;
private final Bundle mSnoozeCriteria;
+ private final Bundle mShowBadge;
public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
Bundle visibilityOverrides, Bundle suppressedVisualEffects,
int[] importance, Bundle explanation, Bundle overrideGroupKeys,
- Bundle overrideChannels, Bundle overridePeople, Bundle snoozeCriteria) {
+ Bundle channels, Bundle overridePeople, Bundle snoozeCriteria,
+ Bundle showBadge) {
mKeys = keys;
mInterceptedKeys = interceptedKeys;
mVisibilityOverrides = visibilityOverrides;
@@ -46,9 +48,10 @@
mImportance = importance;
mImportanceExplanation = explanation;
mOverrideGroupKeys = overrideGroupKeys;
- mOverrideChannels = overrideChannels;
+ mChannels = channels;
mOverridePeople = overridePeople;
mSnoozeCriteria = snoozeCriteria;
+ mShowBadge = showBadge;
}
public NotificationRankingUpdate(Parcel in) {
@@ -60,9 +63,10 @@
in.readIntArray(mImportance);
mImportanceExplanation = in.readBundle();
mOverrideGroupKeys = in.readBundle();
- mOverrideChannels = in.readBundle();
+ mChannels = in.readBundle();
mOverridePeople = in.readBundle();
mSnoozeCriteria = in.readBundle();
+ mShowBadge = in.readBundle();
}
@Override
@@ -79,9 +83,10 @@
out.writeIntArray(mImportance);
out.writeBundle(mImportanceExplanation);
out.writeBundle(mOverrideGroupKeys);
- out.writeBundle(mOverrideChannels);
+ out.writeBundle(mChannels);
out.writeBundle(mOverridePeople);
out.writeBundle(mSnoozeCriteria);
+ out.writeBundle(mShowBadge);
}
public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -123,8 +128,8 @@
return mOverrideGroupKeys;
}
- public Bundle getOverrideChannels() {
- return mOverrideChannels;
+ public Bundle getChannels() {
+ return mChannels;
}
public Bundle getOverridePeople() {
@@ -134,4 +139,8 @@
public Bundle getSnoozeCriteria() {
return mSnoozeCriteria;
}
+
+ public Bundle getShowBadge() {
+ return mShowBadge;
+ }
}
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 6276af3..85baf4e 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -43,21 +43,18 @@
private final Notification notification;
private final UserHandle user;
private final long postTime;
- private final NotificationChannel channel;
private Context mContext; // used for inflation & icon expansion
/** @hide */
- public StatusBarNotification(String pkg, String opPkg, NotificationChannel channel, int id,
+ public StatusBarNotification(String pkg, String opPkg, int id,
String tag, int uid, int initialPid, Notification notification, UserHandle user,
String overrideGroupKey, long postTime) {
if (pkg == null) throw new NullPointerException();
if (notification == null) throw new NullPointerException();
- if (channel == null) throw new IllegalArgumentException();
this.pkg = pkg;
this.opPkg = opPkg;
- this.channel = channel;
this.id = id;
this.tag = tag;
this.uid = uid;
@@ -88,7 +85,6 @@
this.postTime = postTime;
this.key = key();
this.groupKey = groupKey();
- this.channel = null;
}
public StatusBarNotification(Parcel in) {
@@ -112,7 +108,6 @@
}
this.key = key();
this.groupKey = groupKey();
- this.channel = NotificationChannel.CREATOR.createFromParcel(in);
}
private String key() {
@@ -182,7 +177,6 @@
} else {
out.writeInt(0);
}
- this.channel.writeToParcel(out, flags);
}
public int describeContents() {
@@ -209,14 +203,14 @@
public StatusBarNotification cloneLight() {
final Notification no = new Notification();
this.notification.cloneInto(no, false); // light copy
- return new StatusBarNotification(this.pkg, this.opPkg, this.channel,
+ return new StatusBarNotification(this.pkg, this.opPkg,
this.id, this.tag, this.uid, this.initialPid,
no, this.user, this.overrideGroupKey, this.postTime);
}
@Override
public StatusBarNotification clone() {
- return new StatusBarNotification(this.pkg, this.opPkg, this.channel,
+ return new StatusBarNotification(this.pkg, this.opPkg,
this.id, this.tag, this.uid, this.initialPid,
this.notification.clone(), this.user, this.overrideGroupKey, this.postTime);
}
@@ -336,13 +330,6 @@
}
/**
- * Returns the channel this notification was posted to.
- */
- public NotificationChannel getNotificationChannel() {
- return channel;
- }
-
- /**
* @hide
*/
public Context getPackageContext(Context context) {
diff --git a/core/java/android/speech/tts/BlockingAudioTrack.java b/core/java/android/speech/tts/BlockingAudioTrack.java
index 9920ea1..be5851c 100644
--- a/core/java/android/speech/tts/BlockingAudioTrack.java
+++ b/core/java/android/speech/tts/BlockingAudioTrack.java
@@ -164,7 +164,7 @@
// all data from the audioTrack has been sent to the mixer, so
// it's safe to release at this point.
if (DBG) Log.d(TAG, "Releasing audio track [" + track.hashCode() + "]");
- synchronized(mAudioTrackLock) {
+ synchronized (mAudioTrackLock) {
mAudioTrack = null;
}
track.release();
@@ -340,4 +340,25 @@
return value < min ? min : (value < max ? value : max);
}
+ /**
+ * @see
+ * AudioTrack#setPlaybackPositionUpdateListener(AudioTrack.OnPlaybackPositionUpdateListener).
+ */
+ public void setPlaybackPositionUpdateListener(
+ AudioTrack.OnPlaybackPositionUpdateListener listener) {
+ synchronized (mAudioTrackLock) {
+ if (mAudioTrack != null) {
+ mAudioTrack.setPlaybackPositionUpdateListener(listener);
+ }
+ }
+ }
+
+ /** @see AudioTrack#setNotificationMarkerPosition(int). */
+ public void setNotificationMarkerPosition(int frames) {
+ synchronized (mAudioTrackLock) {
+ if (mAudioTrack != null) {
+ mAudioTrack.setNotificationMarkerPosition(frames);
+ }
+ }
+ }
}
diff --git a/core/java/android/speech/tts/ITextToSpeechCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
index 4e3acf6a..edb6e48 100644
--- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
@@ -83,4 +83,19 @@
* callback.
*/
void onAudioAvailable(String utteranceId, in byte[] audio);
+
+ /**
+ * Tells the client that the engine is about to speak the specified range of the utterance.
+ *
+ * <p>
+ * Only called if the engine supplies timing information by calling
+ * {@link SynthesisCallback#rangeStart(int, int, int)} and only when the request is played back
+ * by the service, not when using {@link android.speech.tts.TextToSpeech#synthesizeToFile}.
+ * </p>
+ *
+ * @param utteranceId Unique id identifying the synthesis request.
+ * @param start The start character index of the range in the utterance text.
+ * @param end The end character index of the range (exclusive) in the utterance text.
+ */
+ void onUtteranceRangeStart(String utteranceId, int start, int end);
}
diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
index 778aa86..9e24b09 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
@@ -271,4 +271,12 @@
mStatusCode = errorCode;
}
}
+
+ public void rangeStart(int markerInFrames, int start, int end) {
+ if (mItem == null) {
+ Log.e(TAG, "mItem is null");
+ return;
+ }
+ mItem.rangeStart(markerInFrames, start, end);
+ }
}
diff --git a/core/java/android/speech/tts/SynthesisCallback.java b/core/java/android/speech/tts/SynthesisCallback.java
index 2fd8499..8b74ed7 100644
--- a/core/java/android/speech/tts/SynthesisCallback.java
+++ b/core/java/android/speech/tts/SynthesisCallback.java
@@ -142,4 +142,26 @@
* <p>Useful for checking if a fallback from network request is possible.
*/
boolean hasFinished();
+
+ /**
+ * The service may call this method to provide timing information about the spoken text.
+ *
+ * <p>Calling this method means that at the given audio frame, the given range of the input is
+ * about to be spoken. If this method is called the client will receive a callback on the
+ * listener ({@link UtteranceProgressListener#onUtteranceRangeStart}) at the moment that frame
+ * has been reached by the playback head.
+ *
+ * <p>The markerInFrames is a frame index into the audio for this synthesis request, i.e. into
+ * the concatenation of the audio bytes sent to audioAvailable for this synthesis request. The
+ * definition of a frame depends on the format given by {@link #start}. See {@link AudioFormat}
+ * for more information.
+ *
+ * <p>This method should only be called on the synthesis thread, while in {@link
+ * TextToSpeechService#onSynthesizeText}.
+ *
+ * @param markerInFrames The position in frames in the audio where this range is spoken.
+ * @param start The start index of the range in the input text.
+ * @param end The end index (exclusive) of the range in the input text.
+ */
+ default void rangeStart(int markerInFrames, int start, int end) {}
}
diff --git a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
index 7423933..cb5f220 100644
--- a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
+++ b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
@@ -17,18 +17,21 @@
import android.speech.tts.TextToSpeechService.AudioOutputParams;
import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
+import android.media.AudioTrack;
import android.util.Log;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.ConcurrentLinkedQueue;
/**
- * Manages the playback of a list of byte arrays representing audio data
- * that are queued by the engine to an audio track.
+ * Manages the playback of a list of byte arrays representing audio data that are queued by the
+ * engine to an audio track.
*/
-final class SynthesisPlaybackQueueItem extends PlaybackQueueItem {
+final class SynthesisPlaybackQueueItem extends PlaybackQueueItem
+ implements AudioTrack.OnPlaybackPositionUpdateListener {
private static final String TAG = "TTS.SynthQueueItem";
private static final boolean DBG = false;
@@ -63,6 +66,10 @@
private final BlockingAudioTrack mAudioTrack;
private final AbstractEventLogger mLogger;
+ // Stores a queue of markers. When the marker in front is reached the client is informed and we
+ // wait for the next one.
+ private ConcurrentLinkedQueue<ProgressMarker> markerList = new ConcurrentLinkedQueue<>();
+
SynthesisPlaybackQueueItem(AudioOutputParams audioParams, int sampleRate,
int audioFormat, int channelCount, UtteranceProgressDispatcher dispatcher,
Object callerIdentity, AbstractEventLogger logger) {
@@ -89,6 +96,8 @@
return;
}
+ mAudioTrack.setPlaybackPositionUpdateListener(this);
+
try {
byte[] buffer = null;
@@ -172,6 +181,55 @@
}
}
+ /** Convenience class for passing around TTS markers. */
+ private class ProgressMarker {
+ // The index in frames of this marker.
+ public final int frames;
+ // The start index in the text of the utterance.
+ public final int start;
+ // The end index (exclusive) in the text of the utterance.
+ public final int end;
+
+ public ProgressMarker(int frames, int start, int end) {
+ this.frames = frames;
+ this.start = start;
+ this.end = end;
+ }
+ }
+
+ /** Set a callback for the first marker in the queue. */
+ void updateMarker() {
+ ProgressMarker marker = markerList.peek();
+ if (marker != null) {
+ // Zero is used to disable the marker. The documentation recommends to use a non-zero
+ // position near zero such as 1.
+ int markerInFrames = marker.frames == 0 ? 1 : marker.frames;
+ mAudioTrack.setNotificationMarkerPosition(markerInFrames);
+ }
+ }
+
+ /** Informs us that at markerInFrames, the range between start and end is about to be spoken. */
+ void rangeStart(int markerInFrames, int start, int end) {
+ markerList.add(new ProgressMarker(markerInFrames, start, end));
+ updateMarker();
+ }
+
+ @Override
+ public void onMarkerReached(AudioTrack track) {
+ ProgressMarker marker = markerList.poll();
+ if (marker == null) {
+ Log.e(TAG, "onMarkerReached reached called but no marker in queue");
+ return;
+ }
+ // Inform the client.
+ getDispatcher().dispatchOnUtteranceRangeStart(marker.start, marker.end);
+ // Listen for the next marker.
+ // It's ok if this marker is in the past, in that case onMarkerReached will be called again.
+ updateMarker();
+ }
+
+ @Override
+ public void onPeriodicNotification(AudioTrack track) {}
void put(byte[] buffer) throws InterruptedException {
try {
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 24cad95..9a157b7 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -2103,55 +2103,69 @@
private boolean mEstablished;
- private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
- public void onStop(String utteranceId, boolean isStarted) throws RemoteException {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onStop(utteranceId, isStarted);
- }
- };
+ private final ITextToSpeechCallback.Stub mCallback =
+ new ITextToSpeechCallback.Stub() {
+ public void onStop(String utteranceId, boolean isStarted)
+ throws RemoteException {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onStop(utteranceId, isStarted);
+ }
+ };
- @Override
- public void onSuccess(String utteranceId) {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onDone(utteranceId);
- }
- }
+ @Override
+ public void onSuccess(String utteranceId) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onDone(utteranceId);
+ }
+ }
- @Override
- public void onError(String utteranceId, int errorCode) {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onError(utteranceId);
- }
- }
+ @Override
+ public void onError(String utteranceId, int errorCode) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onError(utteranceId);
+ }
+ }
- @Override
- public void onStart(String utteranceId) {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onStart(utteranceId);
- }
- }
+ @Override
+ public void onStart(String utteranceId) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onStart(utteranceId);
+ }
+ }
- @Override
- public void onBeginSynthesis(String utteranceId, int sampleRateInHz, int audioFormat,
- int channelCount) {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onBeginSynthesis(utteranceId, sampleRateInHz, audioFormat, channelCount);
- }
- }
+ @Override
+ public void onBeginSynthesis(
+ String utteranceId,
+ int sampleRateInHz,
+ int audioFormat,
+ int channelCount) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onBeginSynthesis(
+ utteranceId, sampleRateInHz, audioFormat, channelCount);
+ }
+ }
- @Override
- public void onAudioAvailable(String utteranceId, byte[] audio) {
- UtteranceProgressListener listener = mUtteranceProgressListener;
- if (listener != null) {
- listener.onAudioAvailable(utteranceId, audio);
- }
- }
- };
+ @Override
+ public void onAudioAvailable(String utteranceId, byte[] audio) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onAudioAvailable(utteranceId, audio);
+ }
+ }
+
+ @Override
+ public void onUtteranceRangeStart(String utteranceId, int start, int end) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onUtteranceRangeStart(utteranceId, start, end);
+ }
+ }
+ };
private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
private final ComponentName mName;
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 55da52b..80d3c8a 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -663,6 +663,8 @@
void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount);
void dispatchOnAudioAvailable(byte[] audio);
+
+ public void dispatchOnUtteranceRangeStart(int start, int end);
}
/** Set of parameters affecting audio output. */
@@ -882,6 +884,15 @@
}
}
+ @Override
+ public void dispatchOnUtteranceRangeStart(int start, int end) {
+ final String utteranceId = getUtteranceId();
+ if (utteranceId != null) {
+ mCallbacks.dispatchOnUtteranceRangeStart(
+ getCallerIdentity(), utteranceId, start, end);
+ }
+ }
+
abstract public String getUtteranceId();
String getStringParam(Bundle params, String key, String defaultValue) {
@@ -1559,6 +1570,17 @@
}
}
+ public void dispatchOnUtteranceRangeStart(
+ Object callerIdentity, String utteranceId, int start, int end) {
+ ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
+ if (cb == null) return;
+ try {
+ cb.onUtteranceRangeStart(utteranceId, start, end);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback dispatchOnUtteranceRangeStart(String, int, int) failed: " + e);
+ }
+ }
+
@Override
public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
IBinder caller = (IBinder) cookie;
diff --git a/core/java/android/speech/tts/UtteranceProgressListener.java b/core/java/android/speech/tts/UtteranceProgressListener.java
index 72a5228..0ee3769 100644
--- a/core/java/android/speech/tts/UtteranceProgressListener.java
+++ b/core/java/android/speech/tts/UtteranceProgressListener.java
@@ -122,8 +122,24 @@
}
/**
- * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new
- * progress listener.
+ * This is called when the TTS service is about to speak the specified range of the utterance
+ * with the given utteranceId.
+ *
+ * <p>This method is called when the audio is expected to start playing on the speaker. Note
+ * that this is different from {@link #onAudioAvailable} which is called as soon as the audio is
+ * generated.
+ *
+ * <p>Only called if the engine supplies timing information by calling {@link
+ * SynthesisCallback#rangeStart(int, int, int)}.
+ *
+ * @param utteranceId Unique id identifying the synthesis request.
+ * @param start The start index of the range in the utterance text.
+ * @param end The end index of the range (exclusive) in the utterance text.
+ */
+ public void onUtteranceRangeStart(String utteranceId, int start, int end) {}
+
+ /**
+ * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new progress listener.
*
* @hide
*/
diff --git a/core/java/android/text/FontConfig.aidl b/core/java/android/text/FontConfig.aidl
new file mode 100644
index 0000000..17a5ca2
--- /dev/null
+++ b/core/java/android/text/FontConfig.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+/** @hide */
+parcelable FontConfig;
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
new file mode 100644
index 0000000..df694ff
--- /dev/null
+++ b/core/java/android/text/FontConfig.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Font configuration descriptions for System fonts.
+ */
+public final class FontConfig implements Parcelable {
+ private final List<Family> mFamilies = new ArrayList<>();
+ private final List<Alias> mAliases = new ArrayList<>();
+
+ public FontConfig() {
+ }
+
+ public FontConfig(FontConfig config) {
+ for (int i = 0; i < config.mFamilies.size(); i++) {
+ mFamilies.add(new Family(config.mFamilies.get(i)));
+ }
+ mAliases.addAll(config.mAliases);
+ }
+
+ /**
+ * Returns the ordered list of families included in the system fonts.
+ */
+ public List<Family> getFamilies() {
+ return mFamilies;
+ }
+
+ /**
+ * Returns the list of aliases defined for the font families in the system fonts.
+ */
+ public List<Alias> getAliases() {
+ return mAliases;
+ }
+
+ /**
+ * @hide
+ */
+ public FontConfig(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flag) {
+ out.writeInt(mFamilies.size());
+ for (int i = 0; i < mFamilies.size(); i++) {
+ mFamilies.get(i).writeToParcel(out, flag);
+ }
+ out.writeInt(mAliases.size());
+ for (int i = 0; i < mAliases.size(); i++) {
+ mAliases.get(i).writeToParcel(out, flag);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void readFromParcel(Parcel in) {
+ int size = in.readInt();
+ for (int i = 0; i < size; i++) {
+ mFamilies.add(new Family(in));
+ }
+ size = in.readInt();
+ for (int i = 0; i < size; i++) {
+ mAliases.add(new Alias(in));
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<FontConfig> CREATOR = new Parcelable.Creator() {
+ public FontConfig createFromParcel(Parcel in) {
+ return new FontConfig(in);
+ }
+ public FontConfig[] newArray(int size) {
+ return new FontConfig[size];
+ }
+ };
+
+ /**
+ * Class that holds information about a Font axis.
+ */
+ public static final class Axis implements Parcelable {
+ private final int mTag;
+ private final float mStyleValue;
+
+ public Axis(int tag, float styleValue) {
+ this.mTag = tag;
+ this.mStyleValue = styleValue;
+ }
+
+ /**
+ * Returns the variable font axis tag associated to this axis.
+ */
+ public int getTag() {
+ return mTag;
+ }
+
+ /**
+ * Returns the style value associated to the given axis for this font.
+ */
+ public float getStyleValue() {
+ return mStyleValue;
+ }
+
+ /**
+ * @hide
+ */
+ public Axis(Parcel in) {
+ mTag = in.readInt();
+ mStyleValue = in.readFloat();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flag) {
+ out.writeInt(mTag);
+ out.writeFloat(mStyleValue);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<Axis> CREATOR = new Creator<Axis>() {
+ @Override
+ public Axis createFromParcel(Parcel in) {
+ return new Axis(in);
+ }
+
+ @Override
+ public Axis[] newArray(int size) {
+ return new Axis[size];
+ }
+ };
+ }
+
+ /**
+ * Class that holds information about a Font.
+ */
+ public static final class Font implements Parcelable {
+ private final String mFontName;
+ private final int mTtcIndex;
+ private final List<Axis> mAxes;
+ private final int mWeight;
+ private final boolean mIsItalic;
+ private ParcelFileDescriptor mFd;
+
+ public Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic) {
+ mFontName = fontName;
+ mTtcIndex = ttcIndex;
+ mAxes = axes;
+ mWeight = weight;
+ mIsItalic = isItalic;
+ mFd = null;
+ }
+
+ public Font(Font origin) {
+ mFontName = origin.mFontName;
+ mTtcIndex = origin.mTtcIndex;
+ mAxes = new ArrayList<>(origin.mAxes);
+ mWeight = origin.mWeight;
+ mIsItalic = origin.mIsItalic;
+ if (origin.mFd != null) {
+ try {
+ mFd = origin.mFd.dup();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Returns the name associated by the system to this font.
+ */
+ public String getFontName() {
+ return mFontName;
+ }
+
+ /**
+ * Returns the index to be used to access this font when accessing a TTC file.
+ */
+ public int getTtcIndex() {
+ return mTtcIndex;
+ }
+
+ /**
+ * Returns the list of axes associated to this font.
+ */
+ public List<Axis> getAxes() {
+ return mAxes;
+ }
+
+ /**
+ * Returns the weight value for this font.
+ */
+ public int getWeight() {
+ return mWeight;
+ }
+
+ /**
+ * Returns whether this font is italic.
+ */
+ public boolean isItalic() {
+ return mIsItalic;
+ }
+
+ /**
+ * Returns a file descriptor to access the specified font. This should be closed after use.
+ */
+ public ParcelFileDescriptor getFd() {
+ return mFd;
+ }
+
+ /**
+ * @hide
+ */
+ public void setFd(ParcelFileDescriptor fd) {
+ mFd = fd;
+ }
+
+ /**
+ * @hide
+ */
+ public Font(Parcel in) {
+ mFontName = in.readString();
+ mTtcIndex = in.readInt();
+ final int numAxes = in.readInt();
+ mAxes = new ArrayList<>();
+ for (int i = 0; i < numAxes; i++) {
+ mAxes.add(new Axis(in));
+ }
+ mWeight = in.readInt();
+ mIsItalic = in.readInt() == 1;
+ if (in.readInt() == 1) { /* has FD */
+ mFd = ParcelFileDescriptor.CREATOR.createFromParcel(in);
+ } else {
+ mFd = null;
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flag) {
+ out.writeString(mFontName);
+ out.writeInt(mTtcIndex);
+ out.writeInt(mAxes.size());
+ for (int i = 0; i < mAxes.size(); i++) {
+ mAxes.get(i).writeToParcel(out, flag);
+ }
+ out.writeInt(mWeight);
+ out.writeInt(mIsItalic ? 1 : 0);
+ out.writeInt(mFd == null ? 0 : 1);
+ if (mFd != null) {
+ mFd.writeToParcel(out, flag);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<Font> CREATOR = new Creator<Font>() {
+ @Override
+ public Font createFromParcel(Parcel in) {
+ return new Font(in);
+ }
+
+ @Override
+ public Font[] newArray(int size) {
+ return new Font[size];
+ }
+ };
+ }
+
+ /**
+ * Class that holds information about a Font alias.
+ */
+ public static final class Alias implements Parcelable {
+ private final String mName;
+ private final String mToName;
+ private final int mWeight;
+
+ public Alias(String name, String toName, int weight) {
+ this.mName = name;
+ this.mToName = toName;
+ this.mWeight = weight;
+ }
+
+ /**
+ * Returns the new name for the alias.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the existing name to which this alias points to.
+ */
+ public String getToName() {
+ return mToName;
+ }
+
+ /**
+ * Returns the weight associated with this alias.
+ */
+ public int getWeight() {
+ return mWeight;
+ }
+
+ /**
+ * @hide
+ */
+ public Alias(Parcel in) {
+ mName = in.readString();
+ mToName = in.readString();
+ mWeight = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flag) {
+ out.writeString(mName);
+ out.writeString(mToName);
+ out.writeInt(mWeight);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<Alias> CREATOR = new Creator<Alias>() {
+ @Override
+ public Alias createFromParcel(Parcel in) {
+ return new Alias(in);
+ }
+
+ @Override
+ public Alias[] newArray(int size) {
+ return new Alias[size];
+ }
+ };
+ }
+
+ /**
+ * Class that holds information about a Font family.
+ */
+ public static final class Family implements Parcelable {
+ private final String mName;
+ private final List<Font> mFonts;
+ private final String mLanguage;
+ private final String mVariant;
+
+ public Family(String name, List<Font> fonts, String language, String variant) {
+ this.mName = name;
+ this.mFonts = fonts;
+ this.mLanguage = language;
+ this.mVariant = variant;
+ }
+
+ public Family(Family origin) {
+ this.mName = origin.mName;
+ this.mLanguage = origin.mLanguage;
+ this.mVariant = origin.mVariant;
+ this.mFonts = new ArrayList<>();
+ for (int i = 0; i < origin.mFonts.size(); i++) {
+ mFonts.add(new Font(origin.mFonts.get(i)));
+ }
+ }
+
+ /**
+ * Returns the name given by the system to this font family.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the list of fonts included in this family.
+ */
+ public List<Font> getFonts() {
+ return mFonts;
+ }
+
+ /**
+ * Returns the language for this family. May be null.
+ */
+ public String getLanguage() {
+ return mLanguage;
+ }
+
+ /**
+ * Returns the font variant for this family, e.g. "elegant" or "compact". May be null.
+ */
+ public String getVariant() {
+ return mVariant;
+ }
+
+ /**
+ * @hide
+ */
+ public Family(Parcel in) {
+ mName = in.readString();
+ final int size = in.readInt();
+ mFonts = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ mFonts.add(new Font(in));
+ }
+ mLanguage = in.readString();
+ mVariant = in.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flag) {
+ out.writeString(mName);
+ out.writeInt(mFonts.size());
+ for (int i = 0; i < mFonts.size(); i++) {
+ mFonts.get(i).writeToParcel(out, flag);
+ }
+ out.writeString(mLanguage);
+ out.writeString(mVariant);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<Family> CREATOR = new Creator<Family>() {
+ @Override
+ public Family createFromParcel(Parcel in) {
+ return new Family(in);
+ }
+
+ @Override
+ public Family[] newArray(int size) {
+ return new Family[size];
+ }
+ };
+ }
+}
diff --git a/core/java/android/text/FontManager.java b/core/java/android/text/FontManager.java
new file mode 100644
index 0000000..b61cbf3
--- /dev/null
+++ b/core/java/android/text/FontManager.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.os.RemoteException;
+
+import com.android.internal.font.IFontManager;
+
+/**
+ * Interact with the Font service.
+ */
+public final class FontManager {
+ private static final String TAG = "FontManager";
+
+ private final IFontManager mService;
+
+ /**
+ * @hide
+ */
+ public FontManager(IFontManager service) {
+ mService = service;
+ }
+
+ /**
+ * Retrieve the system fonts data. This loads the fonts.xml data if needed and loads all system
+ * fonts in to memory, providing file descriptors for them.
+ */
+ public FontConfig getSystemFonts() {
+ try {
+ return mService.getSystemFonts();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/text/LangId.java b/core/java/android/text/LangId.java
new file mode 100644
index 0000000..ed6e909
--- /dev/null
+++ b/core/java/android/text/LangId.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.text;
+
+/**
+ * Java wrapper for LangId native library interface.
+ * This class is used to detect languages in text.
+ * @hide
+ */
+public final class LangId {
+ // TODO: Move this to android.view.textclassifier and make it package-private.
+ // We'll have to update the native library code to do this.
+
+ static {
+ System.loadLibrary("smart-selection_jni");
+ }
+
+ private final long mModelPtr;
+
+ /**
+ * Creates a new instance of LangId predictor, using the provided model image.
+ */
+ public LangId(int fd) {
+ mModelPtr = nativeNew(fd);
+ }
+
+ /**
+ * Detects the language for given text.
+ */
+ public String findLanguage(String text) {
+ return nativeFindLanguage(mModelPtr, text);
+ }
+
+ /**
+ * Frees up the allocated memory.
+ */
+ public void close() {
+ nativeClose(mModelPtr);
+ }
+
+ private static native long nativeNew(int fd);
+
+ private static native String nativeFindLanguage(long context, String text);
+
+ private static native void nativeClose(long context);
+}
+
diff --git a/core/java/android/text/SmartSelection.java b/core/java/android/text/SmartSelection.java
new file mode 100644
index 0000000..97ef514
--- /dev/null
+++ b/core/java/android/text/SmartSelection.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+/**
+ * Java wrapper for SmartSelection native library interface.
+ * This library is used for detecting entities in text.
+ * @hide
+ */
+public final class SmartSelection {
+ // TODO: Move this to android.view.textclassifier and make it package-private.
+ // We'll have to update the native library code to do this.
+
+ static {
+ System.loadLibrary("smart-selection_jni");
+ }
+
+ private final long mCtx;
+
+ /**
+ * Creates a new instance of SmartSelect predictor, using the provided model image,
+ * given as a file descriptor.
+ */
+ public SmartSelection(int fd) {
+ mCtx = nativeNew(fd);
+ }
+
+ /**
+ * Given a string context and current selection, computes the SmartSelection suggestion.
+ *
+ * The begin and end are character indices into the context UTF8 string. selectionBegin is the
+ * character index where the selection begins, and selectionEnd is the index of one character
+ * past the selection span.
+ *
+ * The return value is an array of two ints: suggested selection beginning and end, with the
+ * same semantics as the input selectionBeginning and selectionEnd.
+ */
+ public int[] suggest(String context, int selectionBegin, int selectionEnd) {
+ return nativeSuggest(mCtx, context, selectionBegin, selectionEnd);
+ }
+
+ /**
+ * Given a string context and current selection, classifies the type of the selected text.
+ *
+ * The begin and end params are character indices in the context string.
+ *
+ * Returns the type of the selection, e.g. "email", "address", "phone".
+ */
+ public String classifyText(String context, int selectionBegin, int selectionEnd) {
+ return nativeClassifyText(mCtx, context, selectionBegin, selectionEnd);
+ }
+
+ /**
+ * Frees up the allocated memory.
+ */
+ public void close() {
+ nativeClose(mCtx);
+ }
+
+ private static native long nativeNew(int fd);
+
+ private static native int[] nativeSuggest(
+ long context, String text, int selectionBegin, int selectionEnd);
+
+ private static native String nativeClassifyText(
+ long context, String text, int selectionBegin, int selectionEnd);
+
+ private static native void nativeClose(long context);
+}
+
diff --git a/core/java/android/text/TextAssistant.java b/core/java/android/text/TextAssistant.java
deleted file mode 100644
index b044981..0000000
--- a/core/java/android/text/TextAssistant.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-/**
- * Interface for providing text assistant features.
- */
-public interface TextAssistant {
-
- /**
- * NO_OP TextAssistant. This will act as the default TextAssistant until we implement a
- * TextClassificationManager.
- * @hide
- */
- TextAssistant NO_OP = new TextAssistant() {
-
- private final TextSelection mTextSelection = new TextSelection();
-
- @Override
- public TextSelection suggestSelection(
- CharSequence text, int selectionStartIndex, int selectionEndIndex) {
- mTextSelection.mStartIndex = selectionStartIndex;
- mTextSelection.mEndIndex = selectionEndIndex;
- return mTextSelection;
- }
-
- @Override
- public void addLinks(Spannable text, int linkMask) {}
- };
-
- /**
- * Returns suggested text selection indices, recognized types and their associated confidence
- * scores. The selections are ordered from highest to lowest scoring.
- */
- TextSelection suggestSelection(
- CharSequence text, int selectionStartIndex, int selectionEndIndex);
-
- /**
- * Adds assistance clickable spans to the provided text.
- */
- void addLinks(Spannable text, int linkMask);
-}
diff --git a/core/java/android/text/TextClassification.java b/core/java/android/text/TextClassification.java
deleted file mode 100644
index bb226da..0000000
--- a/core/java/android/text/TextClassification.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * Information about entities that a specific piece of text is classified as.
- */
-public class TextClassification {
-
- /** @hide */
- public static final TextClassification NO_OP = new TextClassification();
-
- private Map<String, Float> mTypeConfidence = Collections.unmodifiableMap(Collections.EMPTY_MAP);
-
- /**
- * Returns a map of text classification types to their respective confidence scores.
- * The scores range from 0 (low confidence) to 1 (high confidence). The items are ordered from
- * high scoring items to low scoring items.
- */
- public Map<String, Float> getTypeConfidence() {
- return mTypeConfidence;
- }
-}
diff --git a/core/java/android/text/TextClassificationManager.java b/core/java/android/text/TextClassificationManager.java
deleted file mode 100644
index d4548f0..0000000
--- a/core/java/android/text/TextClassificationManager.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-import android.annotation.NonNull;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Interface to the text classification service.
- * This class uses machine learning techniques to infer things about text.
- * Unless otherwise stated, methods of this class are blocking operations and should most likely not
- * be called on the UI thread.
- *
- * <p> You do not instantiate this class directly; instead, retrieve it through
- * {@link android.content.Context#getSystemService}.
- *
- * The TextClassificationManager serves as the default TextAssistant if none has been set.
- * @see android.app.Activity#setTextAssistant(TextAssistant).
- */
-public final class TextClassificationManager implements TextAssistant {
- // TODO: Consider not making this class implement TextAssistant.
-
- /** @hide */
- public TextClassificationManager() {}
-
- /**
- * Returns information containing languages that were detected in the provided text.
- * This is a blocking operation and should most likely not be called on the UI thread.
- */
- public List<TextLanguage> detectLanguages(@NonNull CharSequence text) {
- // TODO: Implement this using the cld3 library.
- return Collections.emptyList();
- }
-
- @Override
- public TextSelection suggestSelection(
- @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex) {
- // TODO: Implement.
- return TextAssistant.NO_OP.suggestSelection(text, selectionStartIndex, selectionEndIndex);
- }
-
- @Override
- public void addLinks(@NonNull Spannable text, int linkMask) {
- // TODO: Implement.
- }
-}
diff --git a/core/java/android/text/TextLanguage.java b/core/java/android/text/TextLanguage.java
deleted file mode 100644
index eb834f1..0000000
--- a/core/java/android/text/TextLanguage.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-import android.annotation.NonNull;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * Specifies detected languages for a section of text indicated by a start and end index.
- */
-public final class TextLanguage {
-
- private final int mStartIndex;
- private final int mEndIndex;
- private final Map<String, Float> mLanguageConfidence;
-
- /**
- * Initializes a TextLanguage object.
- *
- * @param startIndex the start index of the detected languages in the text provided to generate
- * this object.
- * @param endIndex the end index of the detected languages in the text provided to generate this
- * object.
- * @param languageConfidence a map of detected language to confidence score. The language string
- * is a BCP-47 language tag.
- * @throws NullPointerException if languageConfidence is null or contains a null key or value.
- */
- public TextLanguage(int startIndex, int endIndex,
- @NonNull Map<String, Float> languageConfidence) {
- mStartIndex = startIndex;
- mEndIndex = endIndex;
-
- Map<String, Float> map = new LinkedHashMap<>();
- Preconditions.checkNotNull(languageConfidence).entrySet().stream()
- .sorted(Map.Entry.comparingByValue())
- .forEach(entry -> map.put(
- Preconditions.checkNotNull(entry.getKey()),
- Preconditions.checkNotNull(entry.getValue())));
- mLanguageConfidence = Collections.unmodifiableMap(map);
- }
-
- /**
- * Returns the start index of the detected languages in the text provided to generate this
- * object.
- */
- public int getStartIndex() {
- return mStartIndex;
- }
-
- /**
- * Returns the end index of the detected languages in the text provided to generate this object.
- */
- public int getEndIndex() {
- return mEndIndex;
- }
-
- /**
- * Returns an unmodifiable map of detected language to confidence score. The map entries are
- * ordered from high confidence score (1) to low confidence score (0). The language string is a
- * BCP-47 language tag.
- */
- @NonNull
- public Map<String, Float> getLanguageConfidence() {
- return mLanguageConfidence;
- }
-}
diff --git a/core/java/android/text/TextSelection.java b/core/java/android/text/TextSelection.java
deleted file mode 100644
index 9400458..0000000
--- a/core/java/android/text/TextSelection.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-/**
- * Text selection information.
- */
-public class TextSelection {
-
- /** @hide */
- int mStartIndex;
- /** @hide */
- int mEndIndex;
-
- private TextClassification mTextClassification = TextClassification.NO_OP;
-
- /**
- * Returns the start index of the text selection.
- */
- public int getSelectionStartIndex() {
- return mStartIndex;
- }
-
- /**
- * Returns the end index of the text selection.
- */
- public int getSelectionEndIndex() {
- return mEndIndex;
- }
-
- /**
- * Returns information about what the text selection is classified as.
- */
- public TextClassification getTextClassification() {
- return mTextClassification;
- }
-}
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index 79d16fb..92c70bd 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -16,6 +16,8 @@
package android.util;
+import android.annotation.SystemApi;
+
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
@@ -253,6 +255,19 @@
throws IOException;
/**
+ * Read events from the log, filtered by type, blocking until logs are about to be overwritten.
+ * @param tags to search for
+ * @param timestamp timestamp allow logs before this time to be overwritten.
+ * @param output container to add events into
+ * @throws IOException if something goes wrong reading events
+ * @hide
+ */
+ @SystemApi
+ public static native void readEventsOnWrapping(int[] tags, long timestamp,
+ Collection<Event> output)
+ throws IOException;
+
+ /**
* Get the name associated with an event type tag code.
* @param tag code to look up
* @return the name of the tag, or null if no tag has that number
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index b37ea8e..105cc47 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -292,7 +292,7 @@
public static final int STATE_VR = 5;
/* The color mode constants defined below must be kept in sync with the ones in
- * system/graphics.h */
+ * system/core/include/system/graphics-base.h */
/**
* Display color mode: The current color mode is unknown or invalid.
@@ -306,11 +306,24 @@
*/
public static final int COLOR_MODE_DEFAULT = 0;
- /**
- * Display color mode: SRGB
- * @hide
- */
+ /** @hide */
+ public static final int COLOR_MODE_BT601_625 = 1;
+ /** @hide */
+ public static final int COLOR_MODE_BT601_625_UNADJUSTED = 2;
+ /** @hide */
+ public static final int COLOR_MODE_BT601_525 = 3;
+ /** @hide */
+ public static final int COLOR_MODE_BT601_525_UNADJUSTED = 4;
+ /** @hide */
+ public static final int COLOR_MODE_BT709 = 5;
+ /** @hide */
+ public static final int COLOR_MODE_DCI_P3 = 6;
+ /** @hide */
public static final int COLOR_MODE_SRGB = 7;
+ /** @hide */
+ public static final int COLOR_MODE_ADOBE_RGB = 8;
+ /** @hide */
+ public static final int COLOR_MODE_DISPLAY_P3 = 9;
/**
* Internal method to create a display.
@@ -745,6 +758,8 @@
/**
* Returns the display's HDR capabilities.
+ *
+ * @see #isHdr()
*/
public HdrCapabilities getHdrCapabilities() {
synchronized (this) {
@@ -754,6 +769,35 @@
}
/**
+ * 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 a07a7ef..41a13cf 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -16,16 +16,12 @@
package android.view;
-import static android.view.View.KEYBOARD_NAVIGATION_GROUP_CLUSTER;
-import static android.view.View.KEYBOARD_NAVIGATION_GROUP_SECTION;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Rect;
import android.util.ArrayMap;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.view.View.KeyboardNavigationGroupType;
import java.util.ArrayList;
import java.util.Collections;
@@ -110,31 +106,28 @@
}
/**
- * Find the root of the next keyboard navigation group after the current one. The group type can
- * be either a cluster or a section.
- * @param groupType Type of the keyboard navigation group
+ * Find the root of the next keyboard navigation cluster after the current one.
* @param root The view tree to look inside. Cannot be null
- * @param currentGroup The starting point of the search. Null means the default group
+ * @param currentCluster The starting point of the search. Null means the default cluster
* @param direction Direction to look
- * @return The next group, or null if none exists
+ * @return The next cluster, or null if none exists
*/
- public View findNextKeyboardNavigationGroup(
- @KeyboardNavigationGroupType int groupType,
+ public View findNextKeyboardNavigationCluster(
@NonNull View root,
- @Nullable View currentGroup,
+ @Nullable View currentCluster,
int direction) {
View next = null;
- final ArrayList<View> groups = mTempList;
+ final ArrayList<View> clusters = mTempList;
try {
- groups.clear();
- root.addKeyboardNavigationGroups(groupType, groups, direction);
- if (!groups.isEmpty()) {
- next = findNextKeyboardNavigationGroup(
- groupType, root, currentGroup, groups, direction);
+ clusters.clear();
+ root.addKeyboardNavigationClusters(clusters, direction);
+ if (!clusters.isEmpty()) {
+ next = findNextKeyboardNavigationCluster(
+ root, currentCluster, clusters, direction);
}
} finally {
- groups.clear();
+ clusters.clear();
}
return next;
}
@@ -207,25 +200,22 @@
}
}
- private View findNextKeyboardNavigationGroup(
- @KeyboardNavigationGroupType int groupType,
+ private View findNextKeyboardNavigationCluster(
View root,
- View currentGroup,
- List<View> groups,
+ View currentCluster,
+ List<View> clusters,
int direction) {
- final int count = groups.size();
+ final int count = clusters.size();
switch (direction) {
case View.FOCUS_FORWARD:
case View.FOCUS_DOWN:
case View.FOCUS_RIGHT:
- return getNextKeyboardNavigationGroup(
- groupType, root, currentGroup, groups, count);
+ return getNextKeyboardNavigationCluster(root, currentCluster, clusters, count);
case View.FOCUS_BACKWARD:
case View.FOCUS_UP:
case View.FOCUS_LEFT:
- return getPreviousKeyboardNavigationGroup(
- groupType, root, currentGroup, groups, count);
+ return getPreviousKeyboardNavigationCluster(root, currentCluster, clusters, count);
default:
throw new IllegalArgumentException("Unknown direction: " + direction);
}
@@ -331,70 +321,50 @@
return null;
}
- private static View getNextKeyboardNavigationGroup(
- @KeyboardNavigationGroupType int groupType,
+ private static View getNextKeyboardNavigationCluster(
View root,
- View currentGroup,
- List<View> groups,
+ View currentCluster,
+ List<View> clusters,
int count) {
- if (currentGroup == null) {
- // The current group is the default one.
- // The next group after the default one is the first one.
- // Note that the caller guarantees that 'group' is not empty.
- return groups.get(0);
+ if (currentCluster == null) {
+ // The current cluster is the default one.
+ // The next cluster after the default one is the first one.
+ // Note that the caller guarantees that 'clusters' is not empty.
+ return clusters.get(0);
}
- final int position = groups.lastIndexOf(currentGroup);
+ final int position = clusters.lastIndexOf(currentCluster);
if (position >= 0 && position + 1 < count) {
- // Return the next non-default group if we can find it.
- return groups.get(position + 1);
+ // Return the next non-default cluster if we can find it.
+ return clusters.get(position + 1);
}
- switch (groupType) {
- case KEYBOARD_NAVIGATION_GROUP_CLUSTER:
- // The current cluster is the last one. The next one is the default one, i.e. the
- // root.
- return root;
- case KEYBOARD_NAVIGATION_GROUP_SECTION:
- // There is no "default section", hence returning the first one.
- return groups.get(0);
- default:
- throw new IllegalArgumentException(
- "Unknown keyboard navigation group type: " + groupType);
- }
+ // The current cluster is the last one. The next one is the default one, i.e. the
+ // root.
+ return root;
}
- private static View getPreviousKeyboardNavigationGroup(
- @KeyboardNavigationGroupType int groupType,
+ private static View getPreviousKeyboardNavigationCluster(
View root,
- View currentGroup,
- List<View> groups,
+ View currentCluster,
+ List<View> clusters,
int count) {
- if (currentGroup == null) {
- // The current group is the default one.
- // The previous group before the default one is the last one.
- // Note that the caller guarantees that 'groups' is not empty.
- return groups.get(count - 1);
+ if (currentCluster == null) {
+ // The current cluster is the default one.
+ // The previous cluster before the default one is the last one.
+ // Note that the caller guarantees that 'clusters' is not empty.
+ return clusters.get(count - 1);
}
- final int position = groups.indexOf(currentGroup);
+ final int position = clusters.indexOf(currentCluster);
if (position > 0) {
- // Return the previous non-default group if we can find it.
- return groups.get(position - 1);
+ // Return the previous non-default cluster if we can find it.
+ return clusters.get(position - 1);
}
- switch (groupType) {
- case KEYBOARD_NAVIGATION_GROUP_CLUSTER:
- // The current cluster is the first one. The previous one is the default one, i.e.
- // the root.
- return root;
- case KEYBOARD_NAVIGATION_GROUP_SECTION:
- // There is no "default section", hence returning the last one.
- return groups.get(count - 1);
- default:
- throw new IllegalArgumentException(
- "Unknown keyboard navigation group type: " + groupType);
- }
+ // The current cluster is the first one. The previous one is the default one, i.e.
+ // the root.
+ return root;
}
/**
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index b0826a8..f3ebcb4 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -360,7 +360,6 @@
void destroy() {
mInitialized = false;
updateEnabledState(null);
- mRootNode.discardDisplayList();
nDestroy(mNativeProxy, mRootNode.mNativeRenderNode);
}
@@ -492,12 +491,20 @@
*/
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 26e311c..3bd67c7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -857,22 +857,39 @@
*/
static boolean sCascadedDragDrop;
- /**
- * This view does not want keystrokes. Use with TAKES_FOCUS_MASK when
- * calling setFlags.
- */
- private static final int NOT_FOCUSABLE = 0x00000000;
+ /** @hide */
+ @IntDef({NOT_FOCUSABLE, FOCUSABLE, FOCUSABLE_AUTO})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Focusable {}
/**
- * This view wants keystrokes. Use with TAKES_FOCUS_MASK when calling
- * setFlags.
+ * This view does not want keystrokes.
+ * <p>
+ * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code
+ * android:focusable}.
*/
- private static final int FOCUSABLE = 0x00000001;
+ public static final int NOT_FOCUSABLE = 0x00000000;
+
+ /**
+ * This view wants keystrokes.
+ * <p>
+ * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code
+ * android:focusable}.
+ */
+ public static final int FOCUSABLE = 0x00000001;
+
+ /**
+ * This view determines focusability automatically. This is the default.
+ * <p>
+ * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code
+ * android:focusable}.
+ */
+ public static final int FOCUSABLE_AUTO = 0x00000010;
/**
* Mask for use with setFlags indicating bits used for focus.
*/
- private static final int FOCUSABLE_MASK = 0x00000001;
+ private static final int FOCUSABLE_MASK = 0x00000011;
/**
* This view will adjust its padding to fit sytem windows (e.g. status bar)
@@ -1252,14 +1269,6 @@
@Retention(RetentionPolicy.SOURCE)
public @interface FocusRealDirection {} // Like @FocusDirection, but without forward/backward
- /** @hide */
- @IntDef({
- KEYBOARD_NAVIGATION_GROUP_CLUSTER,
- KEYBOARD_NAVIGATION_GROUP_SECTION
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface KeyboardNavigationGroupType {}
-
/**
* Use with {@link #focusSearch(int)}. Move focus to the previous selectable
* item.
@@ -1293,18 +1302,6 @@
public static final int FOCUS_DOWN = 0x00000082;
/**
- * Use with {@link #keyboardNavigationGroupSearch(int, View, int)}. Search for a keyboard
- * navigation cluster.
- */
- public static final int KEYBOARD_NAVIGATION_GROUP_CLUSTER = 1;
-
- /**
- * Use with {@link #keyboardNavigationGroupSearch(int, View, int)}. Search for a keyboard
- * navigation section.
- */
- public static final int KEYBOARD_NAVIGATION_GROUP_SECTION = 2;
-
- /**
* Bits of {@link #getMeasuredWidthAndState()} and
* {@link #getMeasuredWidthAndState()} that provide the actual measured size.
*/
@@ -2500,7 +2497,7 @@
* 1 PFLAG3_SCROLL_INDICATOR_END
* 1 PFLAG3_ASSIST_BLOCKED
* 1 PFLAG3_CLUSTER
- * 1 PFLAG3_SECTION
+ * x * NO LONGER NEEDED, SHOULD BE REUSED *
* 1 PFLAG3_FINGER_DOWN
* 1 PFLAG3_FOCUSED_BY_DEFAULT
* xxxx * NO LONGER NEEDED, SHOULD BE REUSED *
@@ -2710,14 +2707,6 @@
private static final int PFLAG3_CLUSTER = 0x8000;
/**
- * Flag indicating that the view is a root of a keyboard navigation section.
- *
- * @see #isKeyboardNavigationSection()
- * @see #setKeyboardNavigationSection(boolean)
- */
- private static final int PFLAG3_SECTION = 0x10000;
-
- /**
* Indicates that the user is currently touching the screen.
* Currently used for the tooltip positioning only.
*/
@@ -3807,11 +3796,6 @@
*/
int mNextClusterForwardId = View.NO_ID;
- /**
- * User-specified next keyboard navigation section.
- */
- int mNextSectionForwardId = View.NO_ID;
-
private CheckForLongPress mPendingCheckForLongPress;
private CheckForTap mPendingCheckForTap = null;
private PerformClick mPerformClick;
@@ -4169,7 +4153,7 @@
public View(Context context) {
mContext = context;
mResources = context != null ? context.getResources() : null;
- mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
+ mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | FOCUSABLE_AUTO;
// Set some flags defaults
mPrivateFlags2 =
(LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) |
@@ -4355,6 +4339,10 @@
final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+ // Set default values.
+ viewFlagValues |= FOCUSABLE_AUTO;
+ viewFlagMasks |= FOCUSABLE_AUTO;
+
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
@@ -4467,8 +4455,8 @@
}
break;
case com.android.internal.R.styleable.View_focusable:
- if (a.getBoolean(attr, false)) {
- viewFlagValues |= FOCUSABLE;
+ viewFlagValues = (viewFlagValues & ~FOCUSABLE_MASK) | getFocusableAttribute(a);
+ if ((viewFlagValues & FOCUSABLE_AUTO) == 0) {
viewFlagMasks |= FOCUSABLE_MASK;
}
break;
@@ -4622,9 +4610,6 @@
case R.styleable.View_nextClusterForward:
mNextClusterForwardId = a.getResourceId(attr, View.NO_ID);
break;
- case R.styleable.View_nextSectionForward:
- mNextSectionForwardId = a.getResourceId(attr, View.NO_ID);
- break;
case R.styleable.View_minWidth:
mMinWidth = a.getDimensionPixelSize(attr, 0);
break;
@@ -4769,11 +4754,6 @@
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 @@
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 @@
}
/**
- * 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 @@
/**
* 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 @@
/**
- * 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 @@
}
/**
- * 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 @@
* 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
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 @@
}
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 @@
}
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 @@
// 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 @@
}
private void resetDisplayList() {
- mRenderNode.discardDisplayList();
+ if (mRenderNode.isValid()) {
+ mRenderNode.discardDisplayList();
+ }
- if (mBackgroundRenderNode != null) {
+ if (mBackgroundRenderNode != null && mBackgroundRenderNode.isValid()) {
mBackgroundRenderNode.discardDisplayList();
}
}
@@ -18160,7 +18113,7 @@
private static String printFlags(int flags) {
String output = "";
int numFlags = 0;
- if ((flags & FOCUSABLE_MASK) == FOCUSABLE) {
+ if ((flags & FOCUSABLE) == FOCUSABLE) {
output += "TAKES_FOCUS";
numFlags++;
}
@@ -24664,16 +24617,6 @@
}
/**
- * 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 @@
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 @@
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 d252d75..ba73c5f 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -915,13 +915,10 @@
*/
@Override
public View focusSearch(View focused, int direction) {
- if (isRootNamespace()
- || isKeyboardNavigationCluster()
- && (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD)) {
+ if (isRootNamespace()) {
// root namespace means we should consider ourselves the top of the
// tree for focus searching; otherwise we could be focus searching
// into other tabs. see LocalActivityManager and TabHost for more info.
- // Cluster's root works same way for the forward and backward navigation.
return FocusFinder.getInstance().findNextFocus(this, focused, direction);
} else if (mParent != null) {
return mParent.focusSearch(focused, direction);
@@ -1136,12 +1133,6 @@
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
- if (isKeyboardNavigationCluster()
- && (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD) && !hasFocus()) {
- // A cluster cannot be focus-entered from outside using forward/backward navigation.
- return;
- }
-
final int focusableCount = views.size();
final int descendantFocusability = getDescendantFocusability();
@@ -1175,11 +1166,10 @@
}
@Override
- public void addKeyboardNavigationGroups(
- @KeyboardNavigationGroupType int groupType, Collection<View> views, int direction) {
+ public void addKeyboardNavigationClusters(Collection<View> views, int direction) {
final int focusableCount = views.size();
- super.addKeyboardNavigationGroups(groupType, views, direction);
+ super.addKeyboardNavigationClusters(views, direction);
if (focusableCount != views.size()) {
// No need to look for groups inside a group.
@@ -1195,14 +1185,8 @@
for (int i = 0; i < count; i++) {
final View child = children[i];
- if (groupType == KEYBOARD_NAVIGATION_GROUP_SECTION
- && child.isKeyboardNavigationCluster()) {
- // When the current cluster is the default cluster, and we are searching for
- // sections, ignore sections inside non-default clusters.
- continue;
- }
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
- child.addKeyboardNavigationGroups(groupType, views, direction);
+ child.addKeyboardNavigationClusters(views, direction);
}
}
}
@@ -3072,8 +3056,7 @@
final View[] children = mChildren;
for (int i = index; i != end; i += increment) {
View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
- && !child.isKeyboardNavigationCluster()) {
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
if (child.requestFocus(direction, previouslyFocusedRect)) {
return true;
}
@@ -3450,16 +3433,6 @@
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 c9277ca..79b05cd 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -18,7 +18,6 @@
import android.graphics.Rect;
import android.os.Bundle;
-import android.view.View.KeyboardNavigationGroupType;
import android.view.accessibility.AccessibilityEvent;
/**
@@ -148,20 +147,17 @@
public View focusSearch(View v, int direction);
/**
- * Find the nearest keyboard navigation group in the specified direction. The group type can be
- * either a cluster or a section.
- * This does not actually give focus to that group.
+ * Find the nearest keyboard navigation cluster in the specified direction.
+ * This does not actually give focus to that cluster.
*
- * @param groupType Type of the keyboard navigation group
- * @param currentGroup The starting point of the search. Null means the current group is not
- * found yet
+ * @param currentCluster The starting point of the search. Null means the current cluster is not
+ * found yet
* @param direction Direction to look
*
- * @return The nearest keyboard navigation group in the specified direction, or null if none
+ * @return The nearest keyboard navigation cluster in the specified direction, or null if none
* can be found
*/
- View keyboardNavigationGroupSearch(
- @KeyboardNavigationGroupType int groupType, View currentGroup, int direction);
+ View keyboardNavigationClusterSearch(View currentCluster, int direction);
/**
* Change the z order of the child so it's on top of all other children.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c0f2c37..3cbe82e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,8 +16,6 @@
package android.view;
-import static android.view.View.KEYBOARD_NAVIGATION_GROUP_CLUSTER;
-import static android.view.View.KEYBOARD_NAVIGATION_GROUP_SECTION;
import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
@@ -73,7 +71,6 @@
import android.util.TypedValue;
import android.view.Surface.OutOfResourcesException;
import android.view.View.AttachInfo;
-import android.view.View.KeyboardNavigationGroupType;
import android.view.View.MeasureSpec;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -4397,14 +4394,13 @@
return false;
}
- private boolean performKeyboardGroupNavigation(
- @KeyboardNavigationGroupType int groupType, int direction) {
+ private boolean performKeyboardGroupNavigation(int direction) {
final View focused = mView.findFocus();
- final View group = focused != null
- ? focused.keyboardNavigationGroupSearch(groupType, null, direction)
- : keyboardNavigationGroupSearch(groupType, null, direction);
+ final View cluster = focused != null
+ ? focused.keyboardNavigationClusterSearch(null, direction)
+ : keyboardNavigationClusterSearch(null, direction);
- if (group != null && group.restoreDefaultFocus(View.FOCUS_DOWN)) {
+ if (cluster != null && cluster.restoreDefaultFocus(View.FOCUS_DOWN)) {
return true;
}
@@ -4424,32 +4420,15 @@
}
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 @@
// 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 @@
* {@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 0000000..7aab71f
--- /dev/null
+++ b/core/java/android/view/textclassifier/EntityConfidence.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper object for setting and getting entity scores for classified text.
+ *
+ * @param <T> the entity type.
+ * @hide
+ */
+final class EntityConfidence<T> {
+
+ private final Map<T, Float> mEntityConfidence = new HashMap<>();
+
+ private final Comparator<T> mEntityComparator = (e1, e2) -> {
+ float score1 = mEntityConfidence.get(e1);
+ float score2 = mEntityConfidence.get(e2);
+ if (score1 > score2) {
+ return 1;
+ }
+ if (score1 < score2) {
+ return -1;
+ }
+ return 0;
+ };
+
+ EntityConfidence() {}
+
+ EntityConfidence(@NonNull EntityConfidence<T> source) {
+ Preconditions.checkNotNull(source);
+ mEntityConfidence.putAll(source.mEntityConfidence);
+ }
+
+ /**
+ * Sets an entity type for the classified text and assigns a confidence score.
+ *
+ * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+ * 0 implies the entity does not exist for the classified text.
+ * Values greater than 1 are clamped to 1.
+ */
+ public void setEntityType(
+ @NonNull T type, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+ Preconditions.checkNotNull(type);
+ if (confidenceScore > 0) {
+ mEntityConfidence.put(type, Math.min(1, confidenceScore));
+ } else {
+ mEntityConfidence.remove(type);
+ }
+ }
+
+ /**
+ * Returns an immutable list of entities found in the classified text ordered from
+ * high confidence to low confidence.
+ */
+ @NonNull
+ public List<T> getEntities() {
+ List<T> entities = new ArrayList<>(mEntityConfidence.size());
+ entities.addAll(mEntityConfidence.keySet());
+ entities.sort(mEntityComparator);
+ return Collections.unmodifiableList(entities);
+ }
+
+ /**
+ * Returns the confidence score for the specified entity. The value ranges from
+ * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+ * classified text.
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(T entity) {
+ if (mEntityConfidence.containsKey(entity)) {
+ return mEntityConfidence.get(entity);
+ }
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return mEntityConfidence.toString();
+ }
+}
diff --git a/core/java/android/view/textclassifier/LinksInfo.java b/core/java/android/view/textclassifier/LinksInfo.java
new file mode 100644
index 0000000..3acbdc0
--- /dev/null
+++ b/core/java/android/view/textclassifier/LinksInfo.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.NonNull;
+
+/**
+ * Link information that can be applied to text. See: {@link #apply(CharSequence)}.
+ * Typical implementations of this interface will annotate spannable text with e.g
+ * {@link android.text.style.ClickableSpan}s or other annotations.
+ */
+public interface LinksInfo {
+
+ /**
+ * @hide
+ */
+ LinksInfo NO_OP = text -> false;
+
+ /**
+ * Applies link annotations to the specified text.
+ * These annotations are not guaranteed to be applied. For example, the annotations may not be
+ * applied if the text has changed from what it was when the link spec was generated for it.
+ *
+ * @return Whether or not the link annotations were successfully applied.
+ */
+ boolean apply(@NonNull CharSequence text);
+}
diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
new file mode 100644
index 0000000..4673c50
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.text.LangId;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Interface to the text classification service.
+ *
+ * <p>You do not instantiate this class directly; instead, retrieve it through
+ * {@link android.content.Context#getSystemService}.
+ */
+public final class TextClassificationManager {
+
+ private static final String LOG_TAG = "TextClassificationManager";
+
+ private final Context mContext;
+ // TODO: Implement a way to close the file descriptor.
+ private ParcelFileDescriptor mFd;
+ private TextClassifier mDefault;
+ private LangId mLangId;
+
+ /** @hide */
+ public TextClassificationManager(Context context) {
+ mContext = Preconditions.checkNotNull(context);
+ }
+
+ /**
+ * Returns the default text classifier.
+ */
+ public TextClassifier getDefaultTextClassifier() {
+ if (mDefault == null) {
+ try {
+ mFd = ParcelFileDescriptor.open(
+ new File("/etc/assistant/smart-selection.model"),
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ mDefault = new TextClassifierImpl(mContext, mFd);
+ } catch (FileNotFoundException e) {
+ Log.e(LOG_TAG, "Error accessing 'text classifier selection' model file.", e);
+ mDefault = TextClassifier.NO_OP;
+ }
+ }
+ return mDefault;
+ }
+
+ /**
+ * Returns information containing languages that were detected in the provided text.
+ * This is a blocking operation you should avoid calling it on the UI thread.
+ *
+ * @throws IllegalArgumentException if text is null
+ */
+ public List<TextLanguage> detectLanguages(@NonNull CharSequence text) {
+ Preconditions.checkArgument(text != null);
+ try {
+ if (text.length() > 0) {
+ final String language = getLanguageDetector().findLanguage(text.toString());
+ final Locale locale = new Locale.Builder().setLanguageTag(language).build();
+ return Collections.unmodifiableList(Arrays.asList(
+ new TextLanguage.Builder(0, text.length())
+ .setLanguage(locale, 1.0f /* confidence */)
+ .build()));
+ }
+ } catch (Throwable t) {
+ // Avoid throwing from this method. Log the error.
+ Log.e(LOG_TAG, "Error detecting languages for text. Returning empty result.", t);
+ }
+ // Getting here means something went wrong. Return an empty result.
+ return Collections.emptyList();
+ }
+
+ private LangId getLanguageDetector() {
+ if (mLangId == null) {
+ // TODO: Use a file descriptor as soon as we start to depend on a model file
+ // for language detection.
+ mLangId = new LangId(0);
+ }
+ return mLangId;
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextClassificationResult.java b/core/java/android/view/textclassifier/TextClassificationResult.java
new file mode 100644
index 0000000..6af0efb
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassificationResult.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.view.View.OnClickListener;
+import android.view.textclassifier.TextClassifier.EntityType;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Information for generating a widget to handle classified text.
+ */
+public final class TextClassificationResult {
+
+ /**
+ * @hide
+ */
+ static final TextClassificationResult EMPTY = new TextClassificationResult.Builder().build();
+
+ @NonNull private final String mText;
+ @Nullable private final Drawable mIcon;
+ @Nullable private final String mLabel;
+ @Nullable private final Intent mIntent;
+ @Nullable private final OnClickListener mOnClickListener;
+ @NonNull private final EntityConfidence<String> mEntityConfidence;
+ @NonNull private final List<String> mEntities;
+
+ private TextClassificationResult(
+ @NonNull String text,
+ Drawable icon,
+ String label,
+ Intent intent,
+ OnClickListener onClickListener,
+ @NonNull EntityConfidence<String> entityConfidence) {
+ mText = text;
+ mIcon = icon;
+ mLabel = label;
+ mIntent = intent;
+ mOnClickListener = onClickListener;
+ mEntityConfidence = new EntityConfidence<>(entityConfidence);
+ mEntities = mEntityConfidence.getEntities();
+ }
+
+ /**
+ * Gets the classified text.
+ */
+ @NonNull
+ public String getText() {
+ return mText;
+ }
+
+ /**
+ * Returns the number of entities found in the classified text.
+ */
+ @IntRange(from = 0)
+ public int getEntityCount() {
+ return mEntities.size();
+ }
+
+ /**
+ * Returns the entity at the specified index. Entities are ordered from high confidence
+ * to low confidence.
+ *
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getEntityCount() for the number of entities available.
+ */
+ @NonNull
+ public @EntityType String getEntity(int index) {
+ return mEntities.get(index);
+ }
+
+ /**
+ * Returns the confidence score for the specified entity. The value ranges from
+ * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+ * classified text.
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(@EntityType String entity) {
+ return mEntityConfidence.getConfidenceScore(entity);
+ }
+
+ /**
+ * Returns an icon that may be rendered on a widget used to act on the classified text.
+ */
+ @Nullable
+ public Drawable getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Returns a label that may be rendered on a widget used to act on the classified text.
+ */
+ @Nullable
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * Returns an intent that may be fired to act on the classified text.
+ */
+ @Nullable
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ /**
+ * Returns an OnClickListener that may be triggered to act on the classified text.
+ */
+ @Nullable
+ public OnClickListener getOnClickListener() {
+ return mOnClickListener;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("TextClassificationResult {"
+ + "text=%s, entities=%s, label=%s, intent=%s}",
+ mText, mEntityConfidence, mLabel, mIntent);
+ }
+
+ /**
+ * Creates an OnClickListener that starts an activity with the specified intent.
+ *
+ * @throws IllegalArgumentException if context or intent is null
+ * @hide
+ */
+ @NonNull
+ public static OnClickListener createStartActivityOnClick(
+ @NonNull final Context context, @NonNull final Intent intent) {
+ Preconditions.checkArgument(context != null);
+ Preconditions.checkArgument(intent != null);
+ return v -> context.startActivity(intent);
+ }
+
+ /**
+ * Builder for building {@link TextClassificationResult}s.
+ */
+ public static final class Builder {
+
+ @NonNull private String mText;
+ @Nullable private Drawable mIcon;
+ @Nullable private String mLabel;
+ @Nullable private Intent mIntent;
+ @Nullable private OnClickListener mOnClickListener;
+ @NonNull private final EntityConfidence<String> mEntityConfidence =
+ new EntityConfidence<>();
+
+ /**
+ * Sets the classified text.
+ */
+ public Builder setText(@NonNull String text) {
+ mText = Preconditions.checkNotNull(text);
+ return this;
+ }
+
+ /**
+ * Sets an entity type for the classification result and assigns a confidence score.
+ *
+ * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+ * 0 implies the entity does not exist for the classified text.
+ * Values greater than 1 are clamped to 1.
+ */
+ public Builder setEntityType(
+ @NonNull @EntityType String type,
+ @FloatRange(from = 0.0, to = 1.0)float confidenceScore) {
+ mEntityConfidence.setEntityType(type, confidenceScore);
+ return this;
+ }
+
+ /**
+ * Sets an icon that may be rendered on a widget used to act on the classified text.
+ */
+ public Builder setIcon(@Nullable Drawable icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /**
+ * Sets a label that may be rendered on a widget used to act on the classified text.
+ */
+ public Builder setLabel(@Nullable String label) {
+ mLabel = label;
+ return this;
+ }
+
+ /**
+ * Sets an intent that may be fired to act on the classified text.
+ */
+ public Builder setIntent(@Nullable Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ /**
+ * Sets an OnClickListener that may be triggered to act on the classified text.
+ */
+ public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
+ mOnClickListener = onClickListener;
+ return this;
+ }
+
+ /**
+ * Builds an returns a {@link TextClassificationResult}.
+ */
+ public TextClassificationResult build() {
+ return new TextClassificationResult(
+ mText, mIcon, mLabel, mIntent, mOnClickListener, mEntityConfidence);
+ }
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
new file mode 100644
index 0000000..b84e2ae
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Interface for providing text classification related features.
+ *
+ * <p>Unless otherwise stated, methods of this interface are blocking operations and you should
+ * avoid calling them on the UI thread.
+ */
+public interface TextClassifier {
+
+ String TYPE_OTHER = "other";
+ String TYPE_EMAIL = "email";
+ String TYPE_PHONE = "phone";
+ String TYPE_ADDRESS = "address";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({
+ TYPE_OTHER, TYPE_EMAIL, TYPE_PHONE, TYPE_ADDRESS
+ })
+ @interface EntityType {}
+
+ /**
+ * No-op TextClassifier.
+ * This may be used to turn off TextClassifier features.
+ */
+ TextClassifier NO_OP = new TextClassifier() {
+
+ @Override
+ public TextSelection suggestSelection(
+ CharSequence text, int selectionStartIndex, int selectionEndIndex) {
+ return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
+ }
+
+ @Override
+ public TextClassificationResult getTextClassificationResult(
+ CharSequence text, int startIndex, int endIndex) {
+ return TextClassificationResult.EMPTY;
+ }
+
+ @Override
+ public LinksInfo getLinks(CharSequence text, int linkMask) {
+ return LinksInfo.NO_OP;
+ }
+ };
+
+ /**
+ * Returns suggested text selection indices, recognized types and their associated confidence
+ * scores. The selections are ordered from highest to lowest scoring.
+ *
+ * @param text text providing context for the selected text (which is specified
+ * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
+ * @param selectionStartIndex start index of the selected part of text
+ * @param selectionEndIndex end index of the selected part of text
+ *
+ * @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
+ * selectionEndIndex is greater than text.length() or less than selectionStartIndex
+ */
+ @NonNull
+ TextSelection suggestSelection(
+ @NonNull CharSequence text,
+ @IntRange(from = 0) int selectionStartIndex,
+ @IntRange(from = 0) int selectionEndIndex);
+
+ /**
+ * Returns a {@link TextClassificationResult} object that can be used to generate a widget for
+ * handling the classified text.
+ *
+ * @param text text providing context for the text to classify (which is specified
+ * by the sub sequence starting at startIndex and ending at endIndex)
+ * @param startIndex start index of the text to classify
+ * @param endIndex end index of the text to classify
+ *
+ * @throws IllegalArgumentException if text is null; startIndex is negative;
+ * endIndex is greater than text.length() or less than startIndex
+ */
+ @NonNull
+ TextClassificationResult getTextClassificationResult(
+ @NonNull CharSequence text, int startIndex, int endIndex);
+
+ /**
+ * Returns a {@link LinksInfo} that may be applied to the text to annotate it with links
+ * information.
+ *
+ * @param text the text to generate annotations for
+ * @param linkMask See {@link android.text.util.Linkify} for a list of linkMasks that may be
+ * specified. Subclasses of this interface may specify additional linkMasks
+ *
+ * @throws IllegalArgumentException if text is null
+ */
+ LinksInfo getLinks(@NonNull CharSequence text, int linkMask);
+}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
new file mode 100644
index 0000000..72796cf
--- /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 0000000..d94d163
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextLanguage.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Specifies detected languages for a section of text indicated by a start and end index.
+ */
+public final class TextLanguage {
+
+ private final int mStartIndex;
+ private final int mEndIndex;
+ @NonNull private final EntityConfidence<Locale> mLanguageConfidence;
+ @NonNull private final List<Locale> mLanguages;
+
+ private TextLanguage(
+ int startIndex, int endIndex, @NonNull EntityConfidence<Locale> languageConfidence) {
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ mLanguageConfidence = new EntityConfidence<>(languageConfidence);
+ mLanguages = mLanguageConfidence.getEntities();
+ }
+
+ /**
+ * Returns the start index of the detected languages in the text provided to generate this
+ * object.
+ */
+ public int getStartIndex() {
+ return mStartIndex;
+ }
+
+ /**
+ * Returns the end index of the detected languages in the text provided to generate this object.
+ */
+ public int getEndIndex() {
+ return mEndIndex;
+ }
+
+ /**
+ * Returns the number of languages found in the classified text.
+ */
+ @IntRange(from = 0)
+ public int getLanguageCount() {
+ return mLanguages.size();
+ }
+
+ /**
+ * Returns the language locale at the specified index.
+ * Language locales are ordered from high confidence to low confidence.
+ *
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getLanguageCount() for the number of language locales available.
+ */
+ @NonNull
+ public Locale getLanguage(int index) {
+ return mLanguages.get(index);
+ }
+
+ /**
+ * Returns the confidence score for the specified language. The value ranges from
+ * 0 (low confidence) to 1 (high confidence). 0 indicates that the language was
+ * not found for the classified text.
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(@Nullable Locale language) {
+ return mLanguageConfidence.getConfidenceScore(language);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("TextLanguage {%d, %d, %s}",
+ mStartIndex, mEndIndex, mLanguageConfidence);
+ }
+
+ /**
+ * Builder to build {@link TextLanguage} objects.
+ */
+ public static final class Builder {
+
+ private final int mStartIndex;
+ private final int mEndIndex;
+ @NonNull private final EntityConfidence<Locale> mLanguageConfidence =
+ new EntityConfidence<>();
+
+ /**
+ * Creates a builder to build {@link TextLanguage} objects.
+ *
+ * @param startIndex the start index of the detected languages in the text provided
+ * to generate the result
+ * @param endIndex the end index of the detected languages in the text provided
+ * to generate the result. Must be greater than startIndex
+ */
+ public Builder(@IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex) {
+ Preconditions.checkArgument(startIndex >= 0);
+ Preconditions.checkArgument(endIndex > startIndex);
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ }
+
+ /**
+ * Sets a language locale with the associated confidence score.
+ */
+ public Builder setLanguage(
+ @NonNull Locale locale, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+ mLanguageConfidence.setEntityType(locale, confidenceScore);
+ return this;
+ }
+
+ /**
+ * Builds and returns a {@link TextLanguage}.
+ */
+ public TextLanguage build() {
+ return new TextLanguage(mStartIndex, mEndIndex, mLanguageConfidence);
+ }
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
new file mode 100644
index 0000000..3172c13
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.view.textclassifier.TextClassifier.EntityType;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Information about where text selection should be.
+ */
+public final class TextSelection {
+
+ private final int mStartIndex;
+ private final int mEndIndex;
+ @NonNull private final EntityConfidence<String> mEntityConfidence;
+ @NonNull private final List<String> mEntities;
+
+ private TextSelection(
+ int startIndex, int endIndex, @NonNull EntityConfidence<String> entityConfidence) {
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ mEntityConfidence = new EntityConfidence<>(entityConfidence);
+ mEntities = mEntityConfidence.getEntities();
+ }
+
+ /**
+ * Returns the start index of the text selection.
+ */
+ public int getSelectionStartIndex() {
+ return mStartIndex;
+ }
+
+ /**
+ * Returns the end index of the text selection.
+ */
+ public int getSelectionEndIndex() {
+ return mEndIndex;
+ }
+
+ /**
+ * Returns the number of entities found in the classified text.
+ */
+ @IntRange(from = 0)
+ public int getEntityCount() {
+ return mEntities.size();
+ }
+
+ /**
+ * Returns the entity at the specified index. Entities are ordered from high confidence
+ * to low confidence.
+ *
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getEntityCount() for the number of entities available.
+ */
+ @NonNull
+ public @EntityType String getEntity(int index) {
+ return mEntities.get(index);
+ }
+
+ /**
+ * Returns the confidence score for the specified entity. The value ranges from
+ * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+ * classified text.
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(@EntityType String entity) {
+ return mEntityConfidence.getConfidenceScore(entity);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("TextSelection {%d, %d, %s}",
+ mStartIndex, mEndIndex, mEntityConfidence);
+ }
+
+ /**
+ * Builder used to build {@link TextSelection} objects.
+ */
+ public static final class Builder {
+
+ private final int mStartIndex;
+ private final int mEndIndex;
+ @NonNull private final EntityConfidence<String> mEntityConfidence =
+ new EntityConfidence<>();
+
+ /**
+ * Creates a builder used to build {@link TextSelection} objects.
+ *
+ * @param startIndex the start index of the text selection.
+ * @param endIndex the end index of the text selection. Must be greater than startIndex
+ */
+ public Builder(@IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex) {
+ Preconditions.checkArgument(startIndex >= 0);
+ Preconditions.checkArgument(endIndex > startIndex);
+ mStartIndex = startIndex;
+ mEndIndex = endIndex;
+ }
+
+ /**
+ * Sets an entity type for the classified text and assigns a confidence score.
+ *
+ * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+ * 0 implies the entity does not exist for the classified text.
+ * Values greater than 1 are clamped to 1.
+ */
+ public Builder setEntityType(
+ @NonNull @EntityType String type,
+ @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+ mEntityConfidence.setEntityType(type, confidenceScore);
+ return this;
+ }
+
+ /**
+ * Builds and returns {@link TextSelection} object.
+ */
+ public TextSelection build() {
+ return new TextSelection(mStartIndex, mEndIndex, mEntityConfidence);
+ }
+ }
+}
diff --git a/core/java/android/webkit/RenderProcessGoneDetail.java b/core/java/android/webkit/RenderProcessGoneDetail.java
index 77d8596..1c79399 100644
--- a/core/java/android/webkit/RenderProcessGoneDetail.java
+++ b/core/java/android/webkit/RenderProcessGoneDetail.java
@@ -32,4 +32,15 @@
* system.
**/
public abstract boolean didCrash();
+
+ /**
+ * Returns the renderer priority that was set at the time that the
+ * renderer exited. This may be greater than the priority that
+ * any individual {@link WebView} requested using
+ * {@link WebView#setRendererPriorityPolicy}.
+ *
+ * @return the priority of the renderer at exit.
+ **/
+ @WebView.RendererPriority
+ public abstract int rendererPriorityAtExit();
}
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
new file mode 100644
index 0000000..404bcf4
--- /dev/null
+++ b/core/java/android/webkit/UserPackage.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.webkit;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class for storing a (user,PackageInfo) mapping.
+ * @hide
+ */
+public class UserPackage {
+ private final UserInfo mUserInfo;
+ private final PackageInfo mPackageInfo;
+
+ public UserPackage(UserInfo user, PackageInfo packageInfo) {
+ this.mUserInfo = user;
+ this.mPackageInfo = packageInfo;
+ }
+
+ /**
+ * Returns a list of (User,PackageInfo) pairs corresponding to the PackageInfos for all
+ * device users for the package named {@param packageName}.
+ */
+ public static List<UserPackage> getPackageInfosAllUsers(Context context,
+ String packageName, int packageFlags) {
+ List<UserInfo> users = getAllUsers(context);
+ List<UserPackage> userPackages = new ArrayList<UserPackage>(users.size());
+ for (UserInfo user : users) {
+ PackageInfo packageInfo = null;
+ try {
+ packageInfo = context.getPackageManager().getPackageInfoAsUser(
+ packageName, packageFlags, user.id);
+ } catch (NameNotFoundException e) {
+ }
+ userPackages.add(new UserPackage(user, packageInfo));
+ }
+ return userPackages;
+ }
+
+ /**
+ * Returns whether the given package is enabled.
+ * This state can be changed by the user from Settings->Apps
+ */
+ public boolean isEnabledPackage() {
+ if (mPackageInfo == null) return false;
+ return mPackageInfo.applicationInfo.enabled;
+ }
+
+ /**
+ * Return true if the package is installed and not hidden
+ */
+ public boolean isInstalledPackage() {
+ if (mPackageInfo == null) return false;
+ return (((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0)
+ && ((mPackageInfo.applicationInfo.privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0));
+ }
+
+ public UserInfo getUserInfo() {
+ return mUserInfo;
+ }
+
+ public PackageInfo getPackageInfo() {
+ return mPackageInfo;
+ }
+
+
+ private static List<UserInfo> getAllUsers(Context context) {
+ UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ return userManager.getUsers(false);
+ }
+
+}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index f98c099..1e7cddf 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.Widget;
@@ -60,6 +61,8 @@
import java.io.BufferedWriter;
import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Map;
/**
@@ -2151,6 +2154,94 @@
return mProvider.findHierarchyView(className, hashCode);
}
+ /** @hide */
+ @IntDef({
+ RENDERER_PRIORITY_WAIVED,
+ RENDERER_PRIORITY_BOUND,
+ RENDERER_PRIORITY_IMPORTANT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RendererPriority {}
+
+ /**
+ * The renderer associated with this WebView is bound with
+ * {@link Context#BIND_WAIVE_PRIORITY}. At this priority level
+ * {@link WebView} renderers will be strong targets for out of memory
+ * killing.
+ *
+ * Use with {@link #setRendererPriorityPolicy}.
+ */
+ public static final int RENDERER_PRIORITY_WAIVED = 0;
+ /**
+ * The renderer associated with this WebView is bound with
+ * the default priority for services.
+ *
+ * Use with {@link #setRendererPriorityPolicy}.
+ */
+ public static final int RENDERER_PRIORITY_BOUND = 1;
+ /**
+ * The renderer associated with this WebView is bound with
+ * {@link Context#BIND_IMPORTANT}.
+ *
+ * Use with {@link #setRendererPriorityPolicy}.
+ */
+ public static final int RENDERER_PRIORITY_IMPORTANT = 2;
+
+ /**
+ * Set the renderer priority policy for this {@link WebView}. The
+ * priority policy will be used to determine whether an out of
+ * process renderer should be considered to be a target for OOM
+ * killing.
+ *
+ * Because a renderer can be associated with more than one
+ * WebView, the final priority it is computed as the maximum of
+ * any attached WebViews. When a WebView is destroyed it will
+ * cease to be considerered when calculating the renderer
+ * priority. Once no WebViews remain associated with the renderer,
+ * the priority of the renderer will be reduced to
+ * {@link #RENDERER_PRIORITY_WAIVED}.
+ *
+ * The default policy is to set the priority to
+ * {@link #RENDERER_PRIORITY_IMPORTANT} regardless of visibility,
+ * and this should not be changed unless the caller also handles
+ * renderer crashes with
+ * {@link WebViewClient#onRenderProcessGone}. Any other setting
+ * will result in WebView renderers being killed by the system
+ * more aggressively than the application.
+ *
+ * @param rendererRequestedPriority the minimum priority at which
+ * this WebView desires the renderer process to be bound.
+ * @param waivedWhenNotVisible if true, this flag specifies that
+ * when this WebView is not visible, it will be treated as
+ * if it had requested a priority of
+ * {@link #RENDERER_PRIORITY_WAIVED}.
+ */
+ public void setRendererPriorityPolicy(
+ @RendererPriority int rendererRequestedPriority,
+ boolean waivedWhenNotVisible) {
+ mProvider.setRendererPriorityPolicy(rendererRequestedPriority, waivedWhenNotVisible);
+ }
+
+ /**
+ * Get the requested renderer priority for this WebView.
+ *
+ * @return the requested renderer priority policy.
+ */
+ @RendererPriority
+ public int getRendererRequestedPriority() {
+ return mProvider.getRendererRequestedPriority();
+ }
+
+ /**
+ * Return whether this WebView requests a priority of
+ * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
+ *
+ * @return whether this WebView requests a priority of
+ * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
+ */
+ public boolean getRendererPriorityWaivedWhenNotVisible() {
+ return mProvider.getRendererPriorityWaivedWhenNotVisible();
+ }
//-------------------------------------------------------------------------
// Interface for WebView providers
//-------------------------------------------------------------------------
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index 2cdff79..92d0d71 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -26,6 +26,7 @@
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.graphics.Canvas;
+import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.Trace;
import android.util.SparseArray;
@@ -206,4 +207,15 @@
appInfo.getBaseResourcePath(), newAssetPath);
}
}
+
+ /**
+ * Returns whether WebView should run in multiprocess mode.
+ */
+ public boolean isMultiProcessEnabled() {
+ try {
+ return WebViewFactory.getUpdateService().isMultiProcessEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index dd1b0d2..ffc18b1 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -269,6 +269,12 @@
public View findHierarchyView(String className, int hashCode);
+ public void setRendererPriorityPolicy(int rendererRequestedPriority, boolean waivedWhenNotVisible);
+
+ public int getRendererRequestedPriority();
+
+ public boolean getRendererPriorityWaivedWhenNotVisible();
+
//-------------------------------------------------------------------------
// Provider internal methods
//-------------------------------------------------------------------------
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index af5c842..faf0379 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -42,6 +42,8 @@
* <p>See the <a href="{@docRoot}guide/topics/ui/controls/text.html">Text Fields</a>
* guide.</p>
* <p>
+ * This widget does not support auto-sizing text.
+ * <p>
* <b>XML attributes</b>
* <p>
* See {@link android.R.styleable#EditText EditText Attributes},
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 2fc8ec9..f7f9a81 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -60,8 +60,6 @@
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.StaticLayout;
-import android.text.TextClassification;
-import android.text.TextSelection;
import android.text.TextUtils;
import android.text.method.KeyListener;
import android.text.method.MetaKeyKeyListener;
@@ -108,6 +106,8 @@
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.view.textclassifier.TextClassificationResult;
+import android.view.textclassifier.TextSelection;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.TextView.Drawables;
import android.widget.TextView.OnEditorActionListener;
@@ -149,7 +149,7 @@
private static final String UNDO_OWNER_TAG = "Editor";
// Ordering constants used to place the Action Mode or context menu items in their menu.
- // Reserve 1 for the app that the ASSIST logic suggests as the best app to handle the selection.
+ private static final int MENU_ITEM_ORDER_ASSIST = 1;
private static final int MENU_ITEM_ORDER_UNDO = 2;
private static final int MENU_ITEM_ORDER_REDO = 3;
private static final int MENU_ITEM_ORDER_SHARE = 4;
@@ -238,6 +238,8 @@
private boolean mPreserveSelection;
private boolean mRestartActionModeOnNextRefresh;
+ private TextClassificationResult mTextClassificationResult;
+
boolean mIsBeingLongClicked;
private SuggestionsPopupWindow mSuggestionsPopupWindow;
@@ -1889,7 +1891,7 @@
mInsertionPointCursorController.invalidateHandle();
}
if (mTextActionMode != null) {
- mTextActionMode.invalidate();
+ invalidateActionMode(getTextClassifierInfo(false));
}
}
@@ -1982,12 +1984,12 @@
if (mRestartActionModeOnNextRefresh) {
// To avoid distraction, newly start action mode only when selection action
// mode is being restarted.
- startSelectionActionMode();
+ startSelectionActionMode(getTextClassifierInfo(true));
}
} else if (selectionController == null || !selectionController.isActive()) {
// Insertion action mode is active. Avoid dismissing the selection.
stopTextActionModeWithPreservingSelection();
- startSelectionActionMode();
+ startSelectionActionMode(getTextClassifierInfo(true));
} else {
mTextActionMode.invalidateContentRect();
}
@@ -2031,7 +2033,8 @@
*
* @return true if the selection mode was actually started.
*/
- boolean startSelectionActionMode() {
+ boolean startSelectionActionMode(@Nullable TextClassificationResult textClassificationResult) {
+ mTextClassificationResult = textClassificationResult;
boolean selectionStarted = startSelectionActionModeInternal();
if (selectionStarted) {
getSelectionController().show();
@@ -2040,6 +2043,40 @@
return selectionStarted;
}
+ private boolean startSelectionActionModeWithTextAssistant() {
+ return startSelectionActionMode(getTextClassifierInfo(true));
+ }
+
+ private void invalidateActionMode(TextClassificationResult textClassificationResult) {
+ mTextClassificationResult = textClassificationResult;
+ mTextActionMode.invalidate();
+ }
+
+ // TODO: Make this a non-blocking call.
+ private TextClassificationResult getTextClassifierInfo(boolean updateSelection) {
+ // TODO: Trim the text so that only text necessary to provide context of the selected
+ // text is sent to the assistant.
+ final int trimStartIndex = 0;
+ final int trimEndIndex = mTextView.getText().length();
+ CharSequence trimmedText =
+ mTextView.getText().subSequence(trimStartIndex, trimEndIndex);
+ int startIndex = mTextView.getSelectionStart() - trimStartIndex;
+ int endIndex = mTextView.getSelectionEnd() - trimStartIndex;
+
+ if (updateSelection) {
+ TextSelection textSelection = mTextView.getTextClassifier()
+ .suggestSelection(trimmedText, startIndex, endIndex);
+ startIndex = Math.max(0, textSelection.getSelectionStartIndex() + trimStartIndex);
+ endIndex = Math.min(mTextView.getText().length(),
+ textSelection.getSelectionEndIndex() + trimStartIndex);
+ Selection.setSelection((Spannable) mTextView.getText(), startIndex, endIndex);
+ return getTextClassifierInfo(false);
+ }
+
+ return mTextView.getTextClassifier()
+ .getTextClassificationResult(trimmedText, startIndex, endIndex);
+ }
+
/**
* If the TextView allows text selection, selects the current word when no existing selection
* was available and starts a drag.
@@ -2086,7 +2123,7 @@
}
if (mTextActionMode != null) {
// Text action mode is already started
- mTextActionMode.invalidate();
+ invalidateActionMode(getTextClassifierInfo(false));
return false;
}
@@ -3744,8 +3781,7 @@
private final Path mSelectionPath = new Path();
private final RectF mSelectionBounds = new RectF();
private final boolean mHasSelection;
-
- private int mHandleHeight;
+ private final int mHandleHeight;
public TextActionModeCallback(boolean hasSelection) {
mHasSelection = hasSelection;
@@ -3765,18 +3801,19 @@
if (insertionController != null) {
insertionController.getHandle();
mHandleHeight = mSelectHandleCenter.getMinimumHeight();
+ } else {
+ mHandleHeight = 0;
}
}
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- TextClassification textClassification = updateSelectionWithTextAssistant();
-
mode.setTitle(null);
mode.setSubtitle(null);
mode.setTitleOptionalHint(true);
- populateMenuWithItems(menu, textClassification);
+ populateMenuWithItems(menu);
+ updateAssistMenuItem(menu, mTextClassificationResult);
Callback customCallback = getCustomCallback();
if (customCallback != null) {
@@ -3802,30 +3839,13 @@
}
}
- private TextClassification updateSelectionWithTextAssistant() {
- // Trim the text so that only text necessary to provide context of the selected text is
- // sent to the assistant.
- CharSequence trimmedText = mTextView.getText();
- int textLength = mTextView.getText().length();
- int trimStartIndex = 0;
- int startIndex = mTextView.getSelectionStart() - trimStartIndex;
- int endIndex = mTextView.getSelectionEnd() - trimStartIndex;
- TextSelection textSelection = mTextView.getTextAssistant()
- .suggestSelection(trimmedText, startIndex, endIndex);
- Selection.setSelection(
- (Spannable) mTextView.getText(),
- Math.max(0, textSelection.getSelectionStartIndex() + trimStartIndex),
- Math.min(textLength, textSelection.getSelectionEndIndex() + trimStartIndex));
- return textSelection.getTextClassification();
- }
-
private Callback getCustomCallback() {
return mHasSelection
? mCustomSelectionActionModeCallback
: mCustomInsertionActionModeCallback;
}
- private void populateMenuWithItems(Menu menu, TextClassification textClassification) {
+ private void populateMenuWithItems(Menu menu) {
if (mTextView.canCut()) {
menu.add(Menu.NONE, TextView.ID_CUT, MENU_ITEM_ORDER_CUT,
com.android.internal.R.string.cut)
@@ -3855,13 +3875,13 @@
updateSelectAllItem(menu);
updateReplaceItem(menu);
- updateAssistMenuItem(menu, textClassification);
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
updateSelectAllItem(menu);
updateReplaceItem(menu);
+ updateAssistMenuItem(menu, mTextClassificationResult);
Callback customCallback = getCustomCallback();
if (customCallback != null) {
@@ -3894,10 +3914,16 @@
}
}
- private void updateAssistMenuItem(Menu menu, TextClassification textClassification) {
- // TODO: Find the best app available to handle the selected text based on information in
- // the TextClassification object.
- // Add app icon + intent to trigger app to the menu.
+ private void updateAssistMenuItem(
+ Menu menu, TextClassificationResult textClassificationResult) {
+ menu.removeItem(TextView.ID_ASSIST);
+ if (textClassificationResult != null
+ && textClassificationResult.getIcon() != null
+ && textClassificationResult.getOnClickListener() != null) {
+ menu.add(Menu.NONE, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST, null)
+ .setIcon(textClassificationResult.getIcon())
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+ }
}
@Override
@@ -3909,6 +3935,10 @@
if (customCallback != null && customCallback.onActionItemClicked(mode, item)) {
return true;
}
+ if (TextView.ID_ASSIST == item.getItemId() && mTextClassificationResult != null) {
+ mTextClassificationResult.getOnClickListener().onClick(mTextView);
+ stopTextActionMode();
+ }
return mTextView.onTextContextMenuItem(item.getItemId());
}
@@ -3916,6 +3946,7 @@
public void onDestroyActionMode(ActionMode mode) {
// Clear mTextActionMode not to recursively destroy action mode by clearing selection.
mTextActionMode = null;
+ mTextClassificationResult = null;
Callback customCallback = getCustomCallback();
if (customCallback != null) {
customCallback.onDestroyActionMode(mode);
@@ -4733,7 +4764,7 @@
}
positionAtCursorOffset(offset, false);
if (mTextActionMode != null) {
- mTextActionMode.invalidate();
+ invalidateActionMode(getTextClassifierInfo(false));
}
}
@@ -4817,7 +4848,7 @@
}
updateDrawable();
if (mTextActionMode != null) {
- mTextActionMode.invalidate();
+ invalidateActionMode(getTextClassifierInfo(false));
}
}
@@ -5465,7 +5496,8 @@
resetDragAcceleratorState();
if (mTextView.hasSelection()) {
- startSelectionActionMode();
+ // TODO: Do not invoke the text assistant if this was a drag selection.
+ startSelectionActionMode(getTextClassifierInfo(true));
}
break;
}
diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java
index 3b7fe86..70b70bc 100644
--- a/core/java/android/widget/RatingBar.java
+++ b/core/java/android/widget/RatingBar.java
@@ -150,7 +150,11 @@
*/
public void setIsIndicator(boolean isIndicator) {
mIsUserSeekable = !isIndicator;
- setFocusable(!isIndicator);
+ if (isIndicator) {
+ setFocusable(FOCUSABLE_AUTO);
+ } else {
+ setFocusable(FOCUSABLE);
+ }
}
/**
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 9139361..3822138 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -357,9 +357,9 @@
setInputType(inputType);
}
- boolean focusable = true;
- focusable = a.getBoolean(R.styleable.SearchView_focusable, focusable);
- setFocusable(focusable);
+ if (getFocusable() == FOCUSABLE_AUTO) {
+ setFocusable(FOCUSABLE);
+ }
a.recycle();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 08b18a4..a11ece6 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -22,6 +22,7 @@
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.FloatRange;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
@@ -76,8 +77,6 @@
import android.text.Spanned;
import android.text.SpannedString;
import android.text.StaticLayout;
-import android.text.TextAssistant;
-import android.text.TextClassificationManager;
import android.text.TextDirectionHeuristic;
import android.text.TextDirectionHeuristics;
import android.text.TextPaint;
@@ -145,6 +144,8 @@
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
import android.view.textservice.SpellCheckerSubtype;
import android.view.textservice.TextServicesManager;
import android.widget.RemoteViews.RemoteView;
@@ -158,6 +159,8 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Locale;
@@ -252,6 +255,10 @@
* @attr ref android.R.styleable#TextView_fontFeatureSettings
* @attr ref android.R.styleable#TextView_breakStrategy
* @attr ref android.R.styleable#TextView_hyphenationFrequency
+ * @attr ref android.R.styleable#TextView_autoSizeText
+ * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
+ * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
+ * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
*/
@RemoteView
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
@@ -345,6 +352,8 @@
private boolean mPreDrawRegistered;
private boolean mPreDrawListenerDetached;
+ private TextClassifier mTextClassifier;
+
// A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
// that if a user is holding down a movement key to traverse text, we shouldn't also traverse
// the view hierarchy. On the other hand, if the user is using the movement key to traverse
@@ -670,17 +679,31 @@
*/
private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
- // The TextView does not auto-size text.
- public static final int AUTO_SIZE_TYPE_NONE = 0;
+ // The TextView does not auto-size text (default).
+ public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
// The TextView performs uniform horizontal and vertical text size scaling to fit within the
// container.
- public static final int AUTO_SIZE_TYPE_XY = 1;
- // Auto-size type.
- private int mAutoSizeType = AUTO_SIZE_TYPE_NONE;
- // Specify if auto-size is needed.
- private boolean mNeedsAutoSize = false;
+ public static final int AUTO_SIZE_TEXT_TYPE_XY = 1;
+ /** @hide */
+ @IntDef({AUTO_SIZE_TEXT_TYPE_NONE, AUTO_SIZE_TEXT_TYPE_XY})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AutoSizeTextType {}
+ // Default minimum size for auto-sizing text in scaled pixels. {@see #setAutoSizeMinTextSize}.
+ private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
+ // Default maximum size for auto-sizing text in scaled pixels. {@see #setAutoSizeMaxTextSize}.
+ private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
// Default value for the step size in pixels.
private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
+ // Auto-size text type.
+ private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
+ // Specify if auto-size text is needed.
+ private boolean mNeedsAutoSizeText = false;
+ // Step size for auto-sizing in pixels.
+ private int mAutoSizeStepGranularityInPx = 0;
+ // Minimum text size for auto-sizing in pixels.
+ private int mAutoSizeMinTextSizeInPx = 0;
+ // Maximum text size for auto-sizing in pixels.
+ private int mAutoSizeMaxTextSizeInPx = 0;
// Contains the sorted set of desired text sizes in pixels to pick from when auto-sizing text.
private int[] mAutoSizeTextSizesInPx;
@@ -887,13 +910,6 @@
CharSequence hint = null;
boolean password = false;
int inputType = EditorInfo.TYPE_NULL;
- int autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
- int autoSizeMinTextSize = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics());
- int autoSizeMaxTextSize = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_SP, 112, getResources().getDisplayMetrics());
-
-
a = theme.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
@@ -1249,20 +1265,19 @@
break;
case com.android.internal.R.styleable.TextView_autoSizeText:
- mAutoSizeType = a.getInt(attr, AUTO_SIZE_TYPE_NONE);
+ mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
break;
case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
- autoSizeStepGranularityInPx = a.getDimensionPixelSize(
- attr, DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
+ mAutoSizeStepGranularityInPx = a.getDimensionPixelSize(attr, 0);
break;
case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
- autoSizeMinTextSize = a.getDimensionPixelSize(attr, autoSizeMinTextSize);
+ mAutoSizeMinTextSizeInPx = a.getDimensionPixelSize(attr, 0);
break;
case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
- autoSizeMaxTextSize = a.getDimensionPixelSize(attr, autoSizeMaxTextSize);
+ mAutoSizeMaxTextSizeInPx = a.getDimensionPixelSize(attr, 0);
break;
}
}
@@ -1500,26 +1515,22 @@
if (hint != null) setHint(hint);
/*
- * Views are not normally focusable unless specified to be.
+ * Views are not normally clickable unless specified to be.
* However, TextViews that have input or movement methods *are*
- * focusable by default.
+ * clickable by default. By setting clickable here, we implicitly set focusable as well
+ * if not overridden by the developer.
*/
a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
-
- boolean focusable = mMovement != null || getKeyListener() != null;
- boolean clickable = focusable || isClickable();
- boolean longClickable = focusable || isLongClickable();
+ boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
+ boolean clickable = canInputOrMove || isClickable();
+ boolean longClickable = canInputOrMove || isLongClickable();
n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
- case com.android.internal.R.styleable.View_focusable:
- focusable = a.getBoolean(attr, focusable);
- break;
-
case com.android.internal.R.styleable.View_clickable:
clickable = a.getBoolean(attr, clickable);
break;
@@ -1531,7 +1542,6 @@
}
a.recycle();
- setFocusable(focusable);
setClickable(clickable);
setLongClickable(longClickable);
@@ -1542,39 +1552,204 @@
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
- // Setup auto-size.
+ setupAutoSizeTextXY();
+ }
+
+ /**
+ * Specify whether this widget should automatically scale the text to try to perfectly fit
+ * within the layout bounds by taking into account the auto-size configuration.
+ *
+ * @param autoSizeTextType the type of auto-size. Must be one of
+ * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
+ * {@link TextView#AUTO_SIZE_TEXT_TYPE_XY}
+ *
+ * @attr ref android.R.styleable#TextView_autoSizeText
+ *
+ * @see #getAutoSizeTextType()
+ */
+ public void setAutoSizeTextType(@AutoSizeTextType int autoSizeTextType) {
if (supportsAutoSizeText()) {
- switch (mAutoSizeType) {
- case AUTO_SIZE_TYPE_NONE:
- // Nothing to do.
+ switch (autoSizeTextType) {
+ case AUTO_SIZE_TEXT_TYPE_NONE:
+ if (mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE) {
+ // Clear all auto-size configuration
+ mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
+ mAutoSizeMinTextSizeInPx = 0;
+ mAutoSizeMaxTextSizeInPx = 0;
+ mAutoSizeStepGranularityInPx = 0;
+ mAutoSizeTextSizesInPx = null;
+ mNeedsAutoSizeText = false;
+ }
break;
- case AUTO_SIZE_TYPE_XY:
- if (autoSizeMaxTextSize <= autoSizeMinTextSize) {
- throw new IllegalStateException("Maximum text size is less then minimum "
- + "text size");
+ case AUTO_SIZE_TEXT_TYPE_XY:
+ if (mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_XY) {
+ mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_XY;
+ setupAutoSizeTextXY();
}
-
- if (autoSizeStepGranularityInPx <= 0) {
- throw new IllegalStateException("Unexpected zero or negative value for auto"
- + " size step granularity in pixels");
- }
-
- final int autoSizeValuesLength = (autoSizeMaxTextSize - autoSizeMinTextSize)
- / autoSizeStepGranularityInPx;
- mAutoSizeTextSizesInPx = new int[autoSizeValuesLength];
- int sizeToAdd = autoSizeMinTextSize;
- for (int i = 0; i < autoSizeValuesLength; i++) {
- mAutoSizeTextSizesInPx[i] = sizeToAdd;
- sizeToAdd += autoSizeStepGranularityInPx;
- }
-
- mNeedsAutoSize = true;
break;
default:
throw new IllegalArgumentException(
- "Unknown autoSizeText type: " + mAutoSizeType);
+ "Unknown auto-size text type: " + autoSizeTextType);
+ }
+ }
+ }
+
+ /**
+ * Returns the type of auto-size set for this widget.
+ *
+ * @return an {@code int} corresponding to one of the auto-size types:
+ * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
+ * {@link TextView#AUTO_SIZE_TEXT_TYPE_XY}
+ *
+ * @attr ref android.R.styleable#TextView_autoSizeText
+ *
+ * @see #setAutoSizeTextType(int)
+ */
+ @AutoSizeTextType
+ public int getAutoSizeTextType() {
+ return mAutoSizeTextType;
+ }
+
+ /**
+ * Sets the auto-size step granularity. It is used in conjunction with auto-size minimum
+ * and maximum text size in order to build the set of text sizes the system uses to choose
+ * from when auto-sizing.
+ *
+ * @param unit the desired dimension unit. See {@link TypedValue} for the possible
+ * dimension units
+ * @param size the desired size in the given units
+ *
+ * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
+ *
+ * @see #getAutoSizeStepGranularity()
+ * @see #setAutoSizeMinTextSize(int, float)
+ * @see #setAutoSizeMaxTextSize(int, float)
+ */
+ public void setAutoSizeStepGranularity(int unit, float size) {
+ if (supportsAutoSizeText()) {
+ mAutoSizeStepGranularityInPx = (int) TypedValue.applyDimension(
+ unit, size, getResources().getDisplayMetrics());
+ setupAutoSizeTextXY();
+ }
+ }
+
+ /**
+ * @return the current auto-size step granularity in pixels.
+ *
+ * @see #setAutoSizeStepGranularity(int, float)
+ */
+ public int getAutoSizeStepGranularity() {
+ return mAutoSizeStepGranularityInPx;
+ }
+
+ /**
+ * Sets the minimum text size to be used in conjunction with auto-size maximum text size and
+ * auto-size step granularity in order to build the set of text sizes the system uses to choose
+ * from when auto-sizing.
+ *
+ * @param unit the desired dimension unit. See {@link TypedValue} for the possible
+ * dimension units
+ * @param size the desired size in the given units
+ *
+ * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
+ *
+ * @see #getAutoSizeMinTextSize()
+ * @see #setAutoSizeMaxTextSize(int, float)
+ * @see #setAutoSizeStepGranularity(int, float)
+ */
+ public void setAutoSizeMinTextSize(int unit, float size) {
+ if (supportsAutoSizeText()) {
+ mAutoSizeMinTextSizeInPx = (int) TypedValue.applyDimension(
+ unit, size, getResources().getDisplayMetrics());
+ setupAutoSizeTextXY();
+ }
+ }
+
+ /**
+ * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
+ * if auto-size has not been configured this function returns {@code 0}.
+ *
+ * @see #setAutoSizeMinTextSize(int, float)
+ */
+ public int getAutoSizeMinTextSize() {
+ return mAutoSizeMinTextSizeInPx;
+ }
+
+ /**
+ * Sets the maximum text size to be used in conjunction with auto-size minimum text size and
+ * auto-size step granularity in order to build the set of text sizes the system uses to choose
+ * from when auto-sizing.
+ *
+ * @param unit the desired dimension unit. See {@link TypedValue} for the possible
+ * dimension units
+ * @param size the desired size in the given units
+ *
+ * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
+ *
+ * @see #getAutoSizeMaxTextSize()
+ * @see #setAutoSizeMinTextSize(int, float)
+ * @see #setAutoSizeStepGranularity(int, float)
+ */
+ public void setAutoSizeMaxTextSize(int unit, float size) {
+ if (supportsAutoSizeText()) {
+ mAutoSizeMaxTextSizeInPx = (int) TypedValue.applyDimension(
+ unit, size, getResources().getDisplayMetrics());
+ setupAutoSizeTextXY();
+ }
+ }
+
+ /**
+ * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
+ * if auto-size has not been configured this function returns {@code 0}.
+ *
+ * @see #setAutoSizeMaxTextSize(int, float)
+ */
+ public int getAutoSizeMaxTextSize() {
+ return mAutoSizeMaxTextSizeInPx;
+ }
+
+ private void setupAutoSizeTextXY() {
+ if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_XY) {
+ // Set valid defaults.
+ if (mAutoSizeMinTextSizeInPx <= 0) {
+ mAutoSizeMinTextSizeInPx = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_SP,
+ DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
+ getResources().getDisplayMetrics());
}
+ if (mAutoSizeMaxTextSizeInPx <= 0) {
+ mAutoSizeMaxTextSizeInPx = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_SP,
+ DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
+ getResources().getDisplayMetrics());
+ }
+
+ if (mAutoSizeStepGranularityInPx <= 0) {
+ mAutoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
+ }
+
+ // Validate.
+ if (mAutoSizeMaxTextSizeInPx <= mAutoSizeMinTextSizeInPx) {
+ throw new IllegalStateException("Maximum auto-size text size ("
+ + mAutoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
+ + "text size (" + mAutoSizeMinTextSizeInPx + "px)");
+ }
+
+ // Calculate sizes to choose from based on the current auto-size configuration.
+ final int autoSizeValuesLength = (int) Math.ceil(
+ (mAutoSizeMaxTextSizeInPx - mAutoSizeMinTextSizeInPx)
+ / mAutoSizeStepGranularityInPx);
+
+ mAutoSizeTextSizesInPx = new int[autoSizeValuesLength];
+ int sizeToAdd = mAutoSizeMinTextSizeInPx;
+ for (int i = 0; i < autoSizeValuesLength; i++) {
+ mAutoSizeTextSizesInPx[i] = sizeToAdd;
+ sizeToAdd += mAutoSizeStepGranularityInPx;
+ }
+
+ mNeedsAutoSizeText = true;
+ autoSizeText();
}
}
@@ -1975,11 +2150,11 @@
private void fixFocusableAndClickableSettings() {
if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
- setFocusable(true);
+ setFocusable(FOCUSABLE);
setClickable(true);
setLongClickable(true);
} else {
- setFocusable(false);
+ setFocusable(FOCUSABLE_AUTO);
setClickable(false);
setLongClickable(false);
}
@@ -3030,9 +3205,6 @@
/**
* @return the size (in pixels) of the default text size in this TextView.
- *
- * <p>Note: if this TextView has mAutoSizeType set to {@link TextView#AUTO_SIZE_TYPE_XY} than
- * this function returns the maximum text size for auto-sizing.
*/
@ViewDebug.ExportedProperty(category = "text")
public float getTextSize() {
@@ -3077,7 +3249,7 @@
}
/**
- * Set the default text size to a given unit and value. See {@link
+ * Set the default text size to a given unit and value. See {@link
* TypedValue} for the possible dimension units.
*
* <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
@@ -3113,7 +3285,7 @@
if (mLayout != null) {
// Do not auto-size right after setting the text size.
- mNeedsAutoSize = false;
+ mNeedsAutoSizeText = false;
nullLayouts();
requestLayout();
invalidate();
@@ -5949,7 +6121,7 @@
mEditor.mTextIsSelectable = selectable;
setFocusableInTouchMode(selectable);
- setFocusable(selectable);
+ setFocusable(FOCUSABLE_AUTO);
setClickable(selectable);
setLongClickable(selectable);
@@ -7563,13 +7735,13 @@
}
if (isAutoSizeEnabled()) {
- if (mNeedsAutoSize) {
+ if (mNeedsAutoSizeText) {
// Call auto-size after the width and height have been calculated.
autoSizeText();
}
// Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
// after the next measuring round should set this to false.
- mNeedsAutoSize = true;
+ mNeedsAutoSizeText = true;
}
setMeasuredDimension(width, height);
@@ -9328,7 +9500,7 @@
* auto-size.
*/
private boolean isAutoSizeEnabled() {
- return supportsAutoSizeText() && mAutoSizeType != AUTO_SIZE_TYPE_NONE;
+ return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
}
/**
@@ -9715,7 +9887,7 @@
Selection.setSelection((Spannable) text, start, end);
// Make sure selection mode is engaged.
if (mEditor != null) {
- mEditor.startSelectionActionMode();
+ mEditor.startSelectionActionMode(null);
}
return true;
}
@@ -9859,6 +10031,7 @@
static final int ID_SHARE = android.R.id.shareText;
static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
static final int ID_REPLACE = android.R.id.replaceText;
+ static final int ID_ASSIST = android.R.id.textAssist;
/**
* Called when a context menu option for the text view is selected. Currently
@@ -10083,33 +10256,30 @@
return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
}
- private TextAssistant mTextAssistant;
-
/**
- * Sets the {@link TextAssistant} for this TextView.
- * If null, this TextView uses the default TextAssistant which comes from the Activity.
+ * Sets the {@link TextClassifier} for this TextView.
*/
- public void setTextAssistant(TextAssistant textAssistant) {
- mTextAssistant = textAssistant;
+ public void setTextClassifier(@Nullable TextClassifier textClassifier) {
+ mTextClassifier = textClassifier;
}
/**
- * Returns the {@link TextAssistant} used by this TextView.
- * If no TextAssistant is set, it'll use the one from this TextView's {@link Activity} or
- * {@link Context}. If no TextAssistant is found, it'll use a no-op TextAssistant.
+ * Returns the {@link TextClassifier} used by this TextView.
+ * If no TextClassifier has been set, this TextView uses the default set by the
+ * {@link TextClassificationManager}.
*/
- public TextAssistant getTextAssistant() {
- if (mTextAssistant != null) {
- return mTextAssistant;
+ @NonNull
+ public TextClassifier getTextClassifier() {
+ if (mTextClassifier == null) {
+ TextClassificationManager tcm =
+ mContext.getSystemService(TextClassificationManager.class);
+ if (tcm != null) {
+ mTextClassifier = tcm.getDefaultTextClassifier();
+ } else {
+ mTextClassifier = TextClassifier.NO_OP;
+ }
}
- if (mContext instanceof Activity) {
- mTextAssistant = ((Activity) mContext).getTextAssistant();
- } else {
- // The context of this TextView should be an Activity. If it is not and no
- // text assistant has been set, return the TextClassificationManager.
- mTextAssistant = mContext.getSystemService(TextClassificationManager.class);
- }
- return mTextAssistant;
+ return mTextClassifier;
}
/**
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index d734d17..ab1d9b9 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -749,10 +749,7 @@
}
} else {
try {
- AppGlobals.getPackageManager().setLastChosenActivity(intent,
- intent.resolveType(getContentResolver()),
- PackageManager.MATCH_DEFAULT_ONLY,
- filter, bestMatch, intent.getComponent());
+ mAdapter.mResolverListController.setLastChosen(intent, filter, bestMatch);
} catch (RemoteException re) {
Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
}
@@ -1312,10 +1309,7 @@
protected boolean rebuildList() {
List<ResolvedComponentInfo> currentResolveList = null;
try {
- final Intent primaryIntent = getTargetIntent();
- mLastChosen = AppGlobals.getPackageManager().getLastChosenActivity(
- primaryIntent, primaryIntent.resolveTypeIfNeeded(getContentResolver()),
- PackageManager.MATCH_DEFAULT_ONLY);
+ mLastChosen = mResolverListController.getLastChosen();
} catch (RemoteException re) {
Log.d(TAG, "Error calling getLastChosenActivity\n" + re);
}
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index f88f6f9..00faf65 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -19,13 +19,16 @@
import android.annotation.WorkerThread;
import android.app.ActivityManager;
+import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -66,6 +69,22 @@
}
@VisibleForTesting
+ ResolveInfo getLastChosen() throws RemoteException {
+ return AppGlobals.getPackageManager().getLastChosenActivity(
+ mTargetIntent, mTargetIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ PackageManager.MATCH_DEFAULT_ONLY);
+ }
+
+ @VisibleForTesting
+ void setLastChosen(Intent intent, IntentFilter filter, int match)
+ throws RemoteException {
+ AppGlobals.getPackageManager().setLastChosenActivity(intent,
+ intent.resolveType(mContext.getContentResolver()),
+ PackageManager.MATCH_DEFAULT_ONLY,
+ filter, match, intent.getComponent());
+ }
+
+ @VisibleForTesting
public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntent(
boolean shouldGetResolvedFilter,
boolean shouldGetActivityMetadata,
diff --git a/core/java/com/android/internal/font/IFontManager.aidl b/core/java/com/android/internal/font/IFontManager.aidl
new file mode 100644
index 0000000..52a6262
--- /dev/null
+++ b/core/java/com/android/internal/font/IFontManager.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.font;
+
+import android.text.FontConfig;
+
+/**
+ * Interface to the font manager.
+ * @hide
+ */
+interface IFontManager {
+ FontConfig getSystemFonts();
+}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
index 8d11783..a94b161 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -95,39 +95,32 @@
}
}
+ private static int compareNullableCharSequences(@Nullable CharSequence c1,
+ @Nullable CharSequence c2) {
+ // For historical reasons, an empty text needs to put at the last.
+ final boolean empty1 = TextUtils.isEmpty(c1);
+ final boolean empty2 = TextUtils.isEmpty(c2);
+ if (empty1 || empty2) {
+ return (empty1 ? 1 : 0) - (empty2 ? 1 : 0);
+ }
+ return c1.toString().compareTo(c2.toString());
+ }
+
@Override
public int compareTo(ImeSubtypeListItem other) {
- if (TextUtils.isEmpty(mImeName)) {
- return 1;
+ int result = compareNullableCharSequences(mImeName, other.mImeName);
+ if (result != 0) {
+ return result;
}
- if (TextUtils.isEmpty(other.mImeName)) {
- return -1;
+ result = compareNullableCharSequences(mSubtypeName, other.mSubtypeName);
+ if (result != 0) {
+ return result;
}
- if (!TextUtils.equals(mImeName, other.mImeName)) {
- return mImeName.toString().compareTo(other.mImeName.toString());
+ result = (mIsSystemLocale ? -1 : 0) - (other.mIsSystemLocale ? -1 : 0);
+ if (result != 0) {
+ return result;
}
- if (TextUtils.equals(mSubtypeName, other.mSubtypeName)) {
- return 0;
- }
- if (mIsSystemLocale) {
- return -1;
- }
- if (other.mIsSystemLocale) {
- return 1;
- }
- if (mIsSystemLanguage) {
- return -1;
- }
- if (other.mIsSystemLanguage) {
- return 1;
- }
- if (TextUtils.isEmpty(mSubtypeName)) {
- return 1;
- }
- if (TextUtils.isEmpty(other.mSubtypeName)) {
- return -1;
- }
- return mSubtypeName.toString().compareTo(other.mSubtypeName.toString());
+ return (mIsSystemLanguage ? -1 : 0) - (other.mIsSystemLanguage ? -1 : 0);
}
@Override
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index 716997f..c08cd72 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -1080,22 +1080,6 @@
return enabledSubtypes;
}
- // At the initial boot, the settings for input methods are not set,
- // so we need to enable IME in that case.
- public void enableAllIMEsIfThereIsNoEnabledIME() {
- if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
- StringBuilder sb = new StringBuilder();
- final int N = mMethodList.size();
- for (int i = 0; i < N; i++) {
- InputMethodInfo imi = mMethodList.get(i);
- Slog.i(TAG, "Adding: " + imi.getId());
- if (i > 0) sb.append(':');
- sb.append(imi.getId());
- }
- putEnabledInputMethodsStr(sb.toString());
- }
- }
-
public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(),
mInputMethodSplitter,
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 16c2719..c8bf302 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -16,6 +16,7 @@
package com.android.internal.logging;
import android.content.Context;
+import android.metrics.LogMaker;
import android.os.Build;
import android.view.View;
@@ -37,7 +38,7 @@
}
EventLogTags.writeSysuiViewVisibility(category, 100);
EventLogTags.writeSysuiMultiAction(
- new LogBuilder(category)
+ new LogMaker(category)
.setType(MetricsEvent.TYPE_OPEN)
.serialize());
}
@@ -48,7 +49,7 @@
}
EventLogTags.writeSysuiViewVisibility(category, 0);
EventLogTags.writeSysuiMultiAction(
- new LogBuilder(category)
+ new LogMaker(category)
.setType(MetricsEvent.TYPE_CLOSE)
.serialize());
}
@@ -70,7 +71,7 @@
public static void action(Context context, int category) {
EventLogTags.writeSysuiAction(category, "");
EventLogTags.writeSysuiMultiAction(
- new LogBuilder(category)
+ new LogMaker(category)
.setType(MetricsEvent.TYPE_ACTION)
.serialize());
}
@@ -78,7 +79,7 @@
public static void action(Context context, int category, int value) {
EventLogTags.writeSysuiAction(category, Integer.toString(value));
EventLogTags.writeSysuiMultiAction(
- new LogBuilder(category)
+ new LogMaker(category)
.setType(MetricsEvent.TYPE_ACTION)
.setSubtype(value)
.serialize());
@@ -87,16 +88,13 @@
public static void action(Context context, int category, boolean value) {
EventLogTags.writeSysuiAction(category, Boolean.toString(value));
EventLogTags.writeSysuiMultiAction(
- new LogBuilder(category)
+ new LogMaker(category)
.setType(MetricsEvent.TYPE_ACTION)
.setSubtype(value ? 1 : 0)
.serialize());
}
- public static void action(LogBuilder content) {
- //EventLog.writeEvent(524292, content.serialize());
- // Below would be the *right* way to do this, using the generated
- // EventLogTags method, but that doesn't work.
+ public static void action(LogMaker content) {
if (content.getType() == MetricsEvent.TYPE_UNKNOWN) {
content.setType(MetricsEvent.TYPE_ACTION);
}
@@ -109,7 +107,7 @@
throw new IllegalArgumentException("Must define metric category");
}
EventLogTags.writeSysuiAction(category, pkg);
- EventLogTags.writeSysuiMultiAction(new LogBuilder(category)
+ EventLogTags.writeSysuiMultiAction(new LogMaker(category)
.setType(MetricsEvent.TYPE_ACTION)
.setPackageName(pkg)
.serialize());
@@ -119,7 +117,7 @@
public static void count(Context context, String name, int value) {
EventLogTags.writeSysuiCount(name, value);
EventLogTags.writeSysuiMultiAction(
- new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
+ new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
.setCounterName(name)
.setCounterValue(value)
.serialize());
@@ -129,7 +127,7 @@
public static void histogram(Context context, String name, int bucket) {
EventLogTags.writeSysuiHistogram(name, bucket);
EventLogTags.writeSysuiMultiAction(
- new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
+ new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
.setCounterName(name)
.setCounterBucket(bucket)
.setCounterValue(1)
diff --git a/core/java/com/android/internal/logging/legacy/EventLogCollector.java b/core/java/com/android/internal/logging/legacy/EventLogCollector.java
index 952ae23..eba7d0f 100644
--- a/core/java/com/android/internal/logging/legacy/EventLogCollector.java
+++ b/core/java/com/android/internal/logging/legacy/EventLogCollector.java
@@ -178,7 +178,7 @@
public void readEvents(int[] tags, Collection<Event> events) throws IOException {
// Testing in Android: the Static Final Class Strikes Back!
ArrayList<EventLog.Event> nativeEvents = new ArrayList<>();
- EventLog.readEvents(tags, nativeEvents);
+ EventLog.readEventsOnWrapping(tags, 0L, nativeEvents);
for (EventLog.Event nativeEvent : nativeEvents) {
Event event = new Event(nativeEvent);
events.add(event);
diff --git a/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java b/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java
index 7381ff0..91e968b 100644
--- a/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java
+++ b/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java
@@ -15,9 +15,7 @@
*/
package com.android.internal.logging.legacy;
-import android.os.Bundle;
-
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.util.HashMap;
@@ -34,20 +32,20 @@
public static final int TYPE_HISTOGRAM = 2;
public static final int TYPE_EVENT = 3;
- private final Queue<LogBuilder> mQueue;
+ private final Queue<LogMaker> mQueue;
private HashMap<String, Boolean> mConfig;
public LegacyConversionLogger() {
mQueue = new LinkedList<>();
}
- public Queue<LogBuilder> getEvents() {
+ public Queue<LogMaker> getEvents() {
return mQueue;
}
@Override
public void increment(String counterName) {
- LogBuilder b = new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
+ LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
.setCounterName(counterName)
.setCounterValue(1);
mQueue.add(b);
@@ -55,7 +53,7 @@
@Override
public void incrementBy(String counterName, int value) {
- LogBuilder b = new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
+ LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
.setCounterName(counterName)
.setCounterValue(value);
mQueue.add(b);
@@ -63,7 +61,7 @@
@Override
public void incrementIntHistogram(String counterName, int bucket) {
- LogBuilder b = new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
+ LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
.setCounterName(counterName)
.setCounterBucket(bucket)
.setCounterValue(1);
@@ -72,7 +70,7 @@
@Override
public void incrementLongHistogram(String counterName, long bucket) {
- LogBuilder b = new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
+ LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
.setCounterName(counterName)
.setCounterBucket(bucket)
.setCounterValue(1);
@@ -80,16 +78,16 @@
}
@Override
- public LogBuilder obtain() {
- return new LogBuilder(MetricsEvent.VIEW_UNKNOWN);
+ public LogMaker obtain() {
+ return new LogMaker(MetricsEvent.VIEW_UNKNOWN);
}
@Override
- public void dispose(LogBuilder proto) {
+ public void dispose(LogMaker proto) {
}
@Override
- public void addEvent(LogBuilder proto) {
+ public void addEvent(LogMaker proto) {
mQueue.add(proto);
}
diff --git a/core/java/com/android/internal/logging/legacy/LockscreenGestureParser.java b/core/java/com/android/internal/logging/legacy/LockscreenGestureParser.java
index 6bede24..df08ee0 100644
--- a/core/java/com/android/internal/logging/legacy/LockscreenGestureParser.java
+++ b/core/java/com/android/internal/logging/legacy/LockscreenGestureParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -62,7 +62,7 @@
category = GESTURE_TYPE_MAP[type];
}
if (category != MetricsEvent.VIEW_UNKNOWN) {
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(category);
proto.setType(MetricsEvent.TYPE_ACTION);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java b/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java
index 67b84e9..79f3eb8 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -47,7 +47,7 @@
if (mKey.parse((String) operands[0])) {
int index = (Integer) operands[1];
parseTimes(operands, 2);
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.NOTIFICATION_ITEM_ACTION);
proto.setType(MetricsEvent.TYPE_ACTION);
proto.setSubtype(index);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java b/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java
index 761197b..9548fb0 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java
@@ -17,8 +17,8 @@
import android.util.Log;
+import android.metrics.LogMaker;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.LogBuilder;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -58,7 +58,7 @@
final boolean blink = ((Integer) operands[3]) == 1;
if (mKey.parse(keyString)) {
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.NOTIFICATION_ALERT);
proto.setType(MetricsEvent.TYPE_OPEN);
proto.setSubtype((buzz ? BUZZ : 0) | (beep ? BEEP : 0) | (blink ? BLINK : 0));
diff --git a/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java b/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java
index 0cab1a8..80eb004 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -78,7 +78,7 @@
if (mKey.parse(keyString)) {
if (intentional) {
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.NOTIFICATION_ITEM);
proto.setType(MetricsEvent.TYPE_DISMISS);
proto.setSubtype(reason);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java b/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java
index eeae0a8..eee4701 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -46,7 +46,7 @@
try {
if (mKey.parse((String) operands[0])) {
parseTimes(operands, 1);
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.NOTIFICATION_ITEM);
proto.setType(MetricsEvent.TYPE_ACTION);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java b/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java
index d44b8b1..84cd999 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -52,7 +52,7 @@
if (!byUser || !expanded) {
return;
}
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.NOTIFICATION_ITEM);
proto.setType(MetricsEvent.TYPE_DETAIL);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java b/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java
index 662295b..a064a2e 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java
@@ -15,7 +15,7 @@
*/
package com.android.internal.logging.legacy;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -33,7 +33,7 @@
@Override
public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.NOTIFICATION_PANEL);
proto.setType(MetricsEvent.TYPE_CLOSE);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java b/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java
index 0566f0b..4d19564e 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -47,7 +47,7 @@
}
}
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.NOTIFICATION_PANEL);
proto.setType(MetricsEvent.TYPE_OPEN);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java b/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java
index 9185b91..2d2cd909 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -53,7 +53,7 @@
}
if (mKey.parse(keyString)) {
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.NOTIFICATION_ITEM);
proto.setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java b/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java
index 3bf0f9d..e9baf9b 100644
--- a/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java
+++ b/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -48,7 +48,7 @@
boolean state = (((Integer) operands[0]).intValue()) == 1;
int why = ((Integer) operands[1]).intValue();
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(MetricsEvent.SCREEN);
proto.setType(state ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/StatusBarStateParser.java b/core/java/com/android/internal/logging/legacy/StatusBarStateParser.java
index 23abec4..226253f 100644
--- a/core/java/com/android/internal/logging/legacy/StatusBarStateParser.java
+++ b/core/java/com/android/internal/logging/legacy/StatusBarStateParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -56,7 +56,7 @@
view = MetricsEvent.BOUNCER;
}
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(view);
proto.setType(type);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/SysuiActionParser.java b/core/java/com/android/internal/logging/legacy/SysuiActionParser.java
index 7f91f92..1148ee5 100644
--- a/core/java/com/android/internal/logging/legacy/SysuiActionParser.java
+++ b/core/java/com/android/internal/logging/legacy/SysuiActionParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -60,7 +60,7 @@
}
if (operands.length > 0) {
int category = ((Integer) operands[0]).intValue();
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(category);
proto.setType(MetricsEvent.TYPE_ACTION);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java b/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java
index f9b2f49..0c77b7a 100644
--- a/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java
+++ b/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java
@@ -17,8 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import android.metrics.LogMaker;
/**
* ...and one parser to rule them all.
@@ -39,7 +38,7 @@
public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
final boolean debug = Util.debug();
try {
- logger.addEvent(new LogBuilder(operands).setTimestamp(eventTimeMs));
+ logger.addEvent(new LogMaker(operands).setTimestamp(eventTimeMs));
} catch (ClassCastException e) {
if (debug) {
Log.e(TAG, "unexpected operand type: ", e);
diff --git a/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java b/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java
index 5d5aec0..1223b8d 100644
--- a/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java
+++ b/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java
@@ -17,7 +17,7 @@
import android.util.Log;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -41,7 +41,7 @@
int category = ((Integer) operands[0]).intValue();
boolean visibility = ((Integer) operands[1]).intValue() != 0;
- LogBuilder proto = logger.obtain();
+ LogMaker proto = logger.obtain();
proto.setCategory(category);
proto.setType(visibility ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE);
proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/TagParser.java b/core/java/com/android/internal/logging/legacy/TagParser.java
index c62d084..3bffdd5 100755
--- a/core/java/com/android/internal/logging/legacy/TagParser.java
+++ b/core/java/com/android/internal/logging/legacy/TagParser.java
@@ -17,8 +17,8 @@
import android.util.Log;
+import android.metrics.LogMaker;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.LogBuilder;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
/**
@@ -87,7 +87,7 @@
}
}
- public void filltimes(LogBuilder proto) {
+ public void filltimes(LogMaker proto) {
if (mSinceCreationMillis != 0) {
proto.addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS,
mSinceCreationMillis);
diff --git a/core/java/com/android/internal/logging/legacy/TronLogger.java b/core/java/com/android/internal/logging/legacy/TronLogger.java
index dabe314..ee9341a 100644
--- a/core/java/com/android/internal/logging/legacy/TronLogger.java
+++ b/core/java/com/android/internal/logging/legacy/TronLogger.java
@@ -15,7 +15,7 @@
*/
package com.android.internal.logging.legacy;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
/**
* An entity that knows how to log events and counters.
@@ -34,12 +34,12 @@
void incrementLongHistogram(String counterName, long bucket);
/** Obtain a SystemUiEvent proto, must release this with dispose() or addEvent(). */
- LogBuilder obtain();
+ LogMaker obtain();
- void dispose(LogBuilder proto);
+ void dispose(LogMaker proto);
/** Submit an event to be logged. Logger will dispose of proto. */
- void addEvent(LogBuilder proto);
+ void addEvent(LogMaker proto);
/** Get a config flag. */
boolean getConfig(String configName);
diff --git a/core/java/com/android/internal/widget/AdapterHelper.java b/core/java/com/android/internal/widget/AdapterHelper.java
new file mode 100644
index 0000000..f47d430
--- /dev/null
+++ b/core/java/com/android/internal/widget/AdapterHelper.java
@@ -0,0 +1,775 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.util.Log;
+import android.util.Pools;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Helper class that can enqueue and process adapter update operations.
+ * <p>
+ * To support animations, RecyclerView presents an older version the Adapter to best represent
+ * previous state of the layout. Sometimes, this is not trivial when items are removed that were
+ * not laid out, in which case, RecyclerView has no way of providing that item's view for
+ * animations.
+ * <p>
+ * AdapterHelper creates an UpdateOp for each adapter data change then pre-processes them. During
+ * pre processing, AdapterHelper finds out which UpdateOps can be deferred to second layout pass
+ * and which cannot. For the UpdateOps that cannot be deferred, AdapterHelper will change them
+ * according to previously deferred operation and dispatch them before the first layout pass. It
+ * also takes care of updating deferred UpdateOps since order of operations is changed by this
+ * process.
+ * <p>
+ * Although operations may be forwarded to LayoutManager in different orders, resulting data set
+ * is guaranteed to be the consistent.
+ */
+class AdapterHelper implements OpReorderer.Callback {
+
+ static final int POSITION_TYPE_INVISIBLE = 0;
+
+ static final int POSITION_TYPE_NEW_OR_LAID_OUT = 1;
+
+ private static final boolean DEBUG = false;
+
+ private static final String TAG = "AHT";
+
+ private Pools.Pool<UpdateOp> mUpdateOpPool = new Pools.SimplePool<UpdateOp>(UpdateOp.POOL_SIZE);
+
+ final ArrayList<UpdateOp> mPendingUpdates = new ArrayList<UpdateOp>();
+
+ final ArrayList<UpdateOp> mPostponedList = new ArrayList<UpdateOp>();
+
+ final Callback mCallback;
+
+ Runnable mOnItemProcessedCallback;
+
+ final boolean mDisableRecycler;
+
+ final OpReorderer mOpReorderer;
+
+ private int mExistingUpdateTypes = 0;
+
+ AdapterHelper(Callback callback) {
+ this(callback, false);
+ }
+
+ AdapterHelper(Callback callback, boolean disableRecycler) {
+ mCallback = callback;
+ mDisableRecycler = disableRecycler;
+ mOpReorderer = new OpReorderer(this);
+ }
+
+ AdapterHelper addUpdateOp(UpdateOp... ops) {
+ Collections.addAll(mPendingUpdates, ops);
+ return this;
+ }
+
+ void reset() {
+ recycleUpdateOpsAndClearList(mPendingUpdates);
+ recycleUpdateOpsAndClearList(mPostponedList);
+ mExistingUpdateTypes = 0;
+ }
+
+ void preProcess() {
+ mOpReorderer.reorderOps(mPendingUpdates);
+ final int count = mPendingUpdates.size();
+ for (int i = 0; i < count; i++) {
+ UpdateOp op = mPendingUpdates.get(i);
+ switch (op.cmd) {
+ case UpdateOp.ADD:
+ applyAdd(op);
+ break;
+ case UpdateOp.REMOVE:
+ applyRemove(op);
+ break;
+ case UpdateOp.UPDATE:
+ applyUpdate(op);
+ break;
+ case UpdateOp.MOVE:
+ applyMove(op);
+ break;
+ }
+ if (mOnItemProcessedCallback != null) {
+ mOnItemProcessedCallback.run();
+ }
+ }
+ mPendingUpdates.clear();
+ }
+
+ void consumePostponedUpdates() {
+ final int count = mPostponedList.size();
+ for (int i = 0; i < count; i++) {
+ mCallback.onDispatchSecondPass(mPostponedList.get(i));
+ }
+ recycleUpdateOpsAndClearList(mPostponedList);
+ mExistingUpdateTypes = 0;
+ }
+
+ private void applyMove(UpdateOp op) {
+ // MOVE ops are pre-processed so at this point, we know that item is still in the adapter.
+ // otherwise, it would be converted into a REMOVE operation
+ postponeAndUpdateViewHolders(op);
+ }
+
+ private void applyRemove(UpdateOp op) {
+ int tmpStart = op.positionStart;
+ int tmpCount = 0;
+ int tmpEnd = op.positionStart + op.itemCount;
+ int type = -1;
+ for (int position = op.positionStart; position < tmpEnd; position++) {
+ boolean typeChanged = false;
+ RecyclerView.ViewHolder vh = mCallback.findViewHolder(position);
+ if (vh != null || canFindInPreLayout(position)) {
+ // If a ViewHolder exists or this is a newly added item, we can defer this update
+ // to post layout stage.
+ // * For existing ViewHolders, we'll fake its existence in the pre-layout phase.
+ // * For items that are added and removed in the same process cycle, they won't
+ // have any effect in pre-layout since their add ops are already deferred to
+ // post-layout pass.
+ if (type == POSITION_TYPE_INVISIBLE) {
+ // Looks like we have other updates that we cannot merge with this one.
+ // Create an UpdateOp and dispatch it to LayoutManager.
+ UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount, null);
+ dispatchAndUpdateViewHolders(newOp);
+ typeChanged = true;
+ }
+ type = POSITION_TYPE_NEW_OR_LAID_OUT;
+ } else {
+ // This update cannot be recovered because we don't have a ViewHolder representing
+ // this position. Instead, post it to LayoutManager immediately
+ if (type == POSITION_TYPE_NEW_OR_LAID_OUT) {
+ // Looks like we have other updates that we cannot merge with this one.
+ // Create UpdateOp op and dispatch it to LayoutManager.
+ UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount, null);
+ postponeAndUpdateViewHolders(newOp);
+ typeChanged = true;
+ }
+ type = POSITION_TYPE_INVISIBLE;
+ }
+ if (typeChanged) {
+ position -= tmpCount; // also equal to tmpStart
+ tmpEnd -= tmpCount;
+ tmpCount = 1;
+ } else {
+ tmpCount++;
+ }
+ }
+ if (tmpCount != op.itemCount) { // all 1 effect
+ recycleUpdateOp(op);
+ op = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount, null);
+ }
+ if (type == POSITION_TYPE_INVISIBLE) {
+ dispatchAndUpdateViewHolders(op);
+ } else {
+ postponeAndUpdateViewHolders(op);
+ }
+ }
+
+ private void applyUpdate(UpdateOp op) {
+ int tmpStart = op.positionStart;
+ int tmpCount = 0;
+ int tmpEnd = op.positionStart + op.itemCount;
+ int type = -1;
+ for (int position = op.positionStart; position < tmpEnd; position++) {
+ RecyclerView.ViewHolder vh = mCallback.findViewHolder(position);
+ if (vh != null || canFindInPreLayout(position)) { // deferred
+ if (type == POSITION_TYPE_INVISIBLE) {
+ UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount,
+ op.payload);
+ dispatchAndUpdateViewHolders(newOp);
+ tmpCount = 0;
+ tmpStart = position;
+ }
+ type = POSITION_TYPE_NEW_OR_LAID_OUT;
+ } else { // applied
+ if (type == POSITION_TYPE_NEW_OR_LAID_OUT) {
+ UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount,
+ op.payload);
+ postponeAndUpdateViewHolders(newOp);
+ tmpCount = 0;
+ tmpStart = position;
+ }
+ type = POSITION_TYPE_INVISIBLE;
+ }
+ tmpCount++;
+ }
+ if (tmpCount != op.itemCount) { // all 1 effect
+ Object payload = op.payload;
+ recycleUpdateOp(op);
+ op = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount, payload);
+ }
+ if (type == POSITION_TYPE_INVISIBLE) {
+ dispatchAndUpdateViewHolders(op);
+ } else {
+ postponeAndUpdateViewHolders(op);
+ }
+ }
+
+ private void dispatchAndUpdateViewHolders(UpdateOp op) {
+ // tricky part.
+ // traverse all postpones and revert their changes on this op if necessary, apply updated
+ // dispatch to them since now they are after this op.
+ if (op.cmd == UpdateOp.ADD || op.cmd == UpdateOp.MOVE) {
+ throw new IllegalArgumentException("should not dispatch add or move for pre layout");
+ }
+ if (DEBUG) {
+ Log.d(TAG, "dispatch (pre)" + op);
+ Log.d(TAG, "postponed state before:");
+ for (UpdateOp updateOp : mPostponedList) {
+ Log.d(TAG, updateOp.toString());
+ }
+ Log.d(TAG, "----");
+ }
+
+ // handle each pos 1 by 1 to ensure continuity. If it breaks, dispatch partial
+ // TODO Since move ops are pushed to end, we should not need this anymore
+ int tmpStart = updatePositionWithPostponed(op.positionStart, op.cmd);
+ if (DEBUG) {
+ Log.d(TAG, "pos:" + op.positionStart + ",updatedPos:" + tmpStart);
+ }
+ int tmpCnt = 1;
+ int offsetPositionForPartial = op.positionStart;
+ final int positionMultiplier;
+ switch (op.cmd) {
+ case UpdateOp.UPDATE:
+ positionMultiplier = 1;
+ break;
+ case UpdateOp.REMOVE:
+ positionMultiplier = 0;
+ break;
+ default:
+ throw new IllegalArgumentException("op should be remove or update." + op);
+ }
+ for (int p = 1; p < op.itemCount; p++) {
+ final int pos = op.positionStart + (positionMultiplier * p);
+ int updatedPos = updatePositionWithPostponed(pos, op.cmd);
+ if (DEBUG) {
+ Log.d(TAG, "pos:" + pos + ",updatedPos:" + updatedPos);
+ }
+ boolean continuous = false;
+ switch (op.cmd) {
+ case UpdateOp.UPDATE:
+ continuous = updatedPos == tmpStart + 1;
+ break;
+ case UpdateOp.REMOVE:
+ continuous = updatedPos == tmpStart;
+ break;
+ }
+ if (continuous) {
+ tmpCnt++;
+ } else {
+ // need to dispatch this separately
+ UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt, op.payload);
+ if (DEBUG) {
+ Log.d(TAG, "need to dispatch separately " + tmp);
+ }
+ dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial);
+ recycleUpdateOp(tmp);
+ if (op.cmd == UpdateOp.UPDATE) {
+ offsetPositionForPartial += tmpCnt;
+ }
+ tmpStart = updatedPos; // need to remove previously dispatched
+ tmpCnt = 1;
+ }
+ }
+ Object payload = op.payload;
+ recycleUpdateOp(op);
+ if (tmpCnt > 0) {
+ UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt, payload);
+ if (DEBUG) {
+ Log.d(TAG, "dispatching:" + tmp);
+ }
+ dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial);
+ recycleUpdateOp(tmp);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "post dispatch");
+ Log.d(TAG, "postponed state after:");
+ for (UpdateOp updateOp : mPostponedList) {
+ Log.d(TAG, updateOp.toString());
+ }
+ Log.d(TAG, "----");
+ }
+ }
+
+ void dispatchFirstPassAndUpdateViewHolders(UpdateOp op, int offsetStart) {
+ mCallback.onDispatchFirstPass(op);
+ switch (op.cmd) {
+ case UpdateOp.REMOVE:
+ mCallback.offsetPositionsForRemovingInvisible(offsetStart, op.itemCount);
+ break;
+ case UpdateOp.UPDATE:
+ mCallback.markViewHoldersUpdated(offsetStart, op.itemCount, op.payload);
+ break;
+ default:
+ throw new IllegalArgumentException("only remove and update ops can be dispatched"
+ + " in first pass");
+ }
+ }
+
+ private int updatePositionWithPostponed(int pos, int cmd) {
+ final int count = mPostponedList.size();
+ for (int i = count - 1; i >= 0; i--) {
+ UpdateOp postponed = mPostponedList.get(i);
+ if (postponed.cmd == UpdateOp.MOVE) {
+ int start, end;
+ if (postponed.positionStart < postponed.itemCount) {
+ start = postponed.positionStart;
+ end = postponed.itemCount;
+ } else {
+ start = postponed.itemCount;
+ end = postponed.positionStart;
+ }
+ if (pos >= start && pos <= end) {
+ //i'm affected
+ if (start == postponed.positionStart) {
+ if (cmd == UpdateOp.ADD) {
+ postponed.itemCount++;
+ } else if (cmd == UpdateOp.REMOVE) {
+ postponed.itemCount--;
+ }
+ // op moved to left, move it right to revert
+ pos++;
+ } else {
+ if (cmd == UpdateOp.ADD) {
+ postponed.positionStart++;
+ } else if (cmd == UpdateOp.REMOVE) {
+ postponed.positionStart--;
+ }
+ // op was moved right, move left to revert
+ pos--;
+ }
+ } else if (pos < postponed.positionStart) {
+ // postponed MV is outside the dispatched OP. if it is before, offset
+ if (cmd == UpdateOp.ADD) {
+ postponed.positionStart++;
+ postponed.itemCount++;
+ } else if (cmd == UpdateOp.REMOVE) {
+ postponed.positionStart--;
+ postponed.itemCount--;
+ }
+ }
+ } else {
+ if (postponed.positionStart <= pos) {
+ if (postponed.cmd == UpdateOp.ADD) {
+ pos -= postponed.itemCount;
+ } else if (postponed.cmd == UpdateOp.REMOVE) {
+ pos += postponed.itemCount;
+ }
+ } else {
+ if (cmd == UpdateOp.ADD) {
+ postponed.positionStart++;
+ } else if (cmd == UpdateOp.REMOVE) {
+ postponed.positionStart--;
+ }
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "dispath (step" + i + ")");
+ Log.d(TAG, "postponed state:" + i + ", pos:" + pos);
+ for (UpdateOp updateOp : mPostponedList) {
+ Log.d(TAG, updateOp.toString());
+ }
+ Log.d(TAG, "----");
+ }
+ }
+ for (int i = mPostponedList.size() - 1; i >= 0; i--) {
+ UpdateOp op = mPostponedList.get(i);
+ if (op.cmd == UpdateOp.MOVE) {
+ if (op.itemCount == op.positionStart || op.itemCount < 0) {
+ mPostponedList.remove(i);
+ recycleUpdateOp(op);
+ }
+ } else if (op.itemCount <= 0) {
+ mPostponedList.remove(i);
+ recycleUpdateOp(op);
+ }
+ }
+ return pos;
+ }
+
+ private boolean canFindInPreLayout(int position) {
+ final int count = mPostponedList.size();
+ for (int i = 0; i < count; i++) {
+ UpdateOp op = mPostponedList.get(i);
+ if (op.cmd == UpdateOp.MOVE) {
+ if (findPositionOffset(op.itemCount, i + 1) == position) {
+ return true;
+ }
+ } else if (op.cmd == UpdateOp.ADD) {
+ // TODO optimize.
+ final int end = op.positionStart + op.itemCount;
+ for (int pos = op.positionStart; pos < end; pos++) {
+ if (findPositionOffset(pos, i + 1) == position) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private void applyAdd(UpdateOp op) {
+ postponeAndUpdateViewHolders(op);
+ }
+
+ private void postponeAndUpdateViewHolders(UpdateOp op) {
+ if (DEBUG) {
+ Log.d(TAG, "postponing " + op);
+ }
+ mPostponedList.add(op);
+ switch (op.cmd) {
+ case UpdateOp.ADD:
+ mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
+ break;
+ case UpdateOp.MOVE:
+ mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
+ break;
+ case UpdateOp.REMOVE:
+ mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart,
+ op.itemCount);
+ break;
+ case UpdateOp.UPDATE:
+ mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown update op type for " + op);
+ }
+ }
+
+ boolean hasPendingUpdates() {
+ return mPendingUpdates.size() > 0;
+ }
+
+ boolean hasAnyUpdateTypes(int updateTypes) {
+ return (mExistingUpdateTypes & updateTypes) != 0;
+ }
+
+ int findPositionOffset(int position) {
+ return findPositionOffset(position, 0);
+ }
+
+ int findPositionOffset(int position, int firstPostponedItem) {
+ int count = mPostponedList.size();
+ for (int i = firstPostponedItem; i < count; ++i) {
+ UpdateOp op = mPostponedList.get(i);
+ if (op.cmd == UpdateOp.MOVE) {
+ if (op.positionStart == position) {
+ position = op.itemCount;
+ } else {
+ if (op.positionStart < position) {
+ position--; // like a remove
+ }
+ if (op.itemCount <= position) {
+ position++; // like an add
+ }
+ }
+ } else if (op.positionStart <= position) {
+ if (op.cmd == UpdateOp.REMOVE) {
+ if (position < op.positionStart + op.itemCount) {
+ return -1;
+ }
+ position -= op.itemCount;
+ } else if (op.cmd == UpdateOp.ADD) {
+ position += op.itemCount;
+ }
+ }
+ }
+ return position;
+ }
+
+ /**
+ * @return True if updates should be processed.
+ */
+ boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ if (itemCount < 1) {
+ return false;
+ }
+ mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
+ mExistingUpdateTypes |= UpdateOp.UPDATE;
+ return mPendingUpdates.size() == 1;
+ }
+
+ /**
+ * @return True if updates should be processed.
+ */
+ boolean onItemRangeInserted(int positionStart, int itemCount) {
+ if (itemCount < 1) {
+ return false;
+ }
+ mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));
+ mExistingUpdateTypes |= UpdateOp.ADD;
+ return mPendingUpdates.size() == 1;
+ }
+
+ /**
+ * @return True if updates should be processed.
+ */
+ boolean onItemRangeRemoved(int positionStart, int itemCount) {
+ if (itemCount < 1) {
+ return false;
+ }
+ mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null));
+ mExistingUpdateTypes |= UpdateOp.REMOVE;
+ return mPendingUpdates.size() == 1;
+ }
+
+ /**
+ * @return True if updates should be processed.
+ */
+ boolean onItemRangeMoved(int from, int to, int itemCount) {
+ if (from == to) {
+ return false; // no-op
+ }
+ if (itemCount != 1) {
+ throw new IllegalArgumentException("Moving more than 1 item is not supported yet");
+ }
+ mPendingUpdates.add(obtainUpdateOp(UpdateOp.MOVE, from, to, null));
+ mExistingUpdateTypes |= UpdateOp.MOVE;
+ return mPendingUpdates.size() == 1;
+ }
+
+ /**
+ * Skips pre-processing and applies all updates in one pass.
+ */
+ void consumeUpdatesInOnePass() {
+ // we still consume postponed updates (if there is) in case there was a pre-process call
+ // w/o a matching consumePostponedUpdates.
+ consumePostponedUpdates();
+ final int count = mPendingUpdates.size();
+ for (int i = 0; i < count; i++) {
+ UpdateOp op = mPendingUpdates.get(i);
+ switch (op.cmd) {
+ case UpdateOp.ADD:
+ mCallback.onDispatchSecondPass(op);
+ mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
+ break;
+ case UpdateOp.REMOVE:
+ mCallback.onDispatchSecondPass(op);
+ mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount);
+ break;
+ case UpdateOp.UPDATE:
+ mCallback.onDispatchSecondPass(op);
+ mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
+ break;
+ case UpdateOp.MOVE:
+ mCallback.onDispatchSecondPass(op);
+ mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
+ break;
+ }
+ if (mOnItemProcessedCallback != null) {
+ mOnItemProcessedCallback.run();
+ }
+ }
+ recycleUpdateOpsAndClearList(mPendingUpdates);
+ mExistingUpdateTypes = 0;
+ }
+
+ public int applyPendingUpdatesToPosition(int position) {
+ final int size = mPendingUpdates.size();
+ for (int i = 0; i < size; i++) {
+ UpdateOp op = mPendingUpdates.get(i);
+ switch (op.cmd) {
+ case UpdateOp.ADD:
+ if (op.positionStart <= position) {
+ position += op.itemCount;
+ }
+ break;
+ case UpdateOp.REMOVE:
+ if (op.positionStart <= position) {
+ final int end = op.positionStart + op.itemCount;
+ if (end > position) {
+ return RecyclerView.NO_POSITION;
+ }
+ position -= op.itemCount;
+ }
+ break;
+ case UpdateOp.MOVE:
+ if (op.positionStart == position) {
+ position = op.itemCount; //position end
+ } else {
+ if (op.positionStart < position) {
+ position -= 1;
+ }
+ if (op.itemCount <= position) {
+ position += 1;
+ }
+ }
+ break;
+ }
+ }
+ return position;
+ }
+
+ boolean hasUpdates() {
+ return !mPostponedList.isEmpty() && !mPendingUpdates.isEmpty();
+ }
+
+ /**
+ * Queued operation to happen when child views are updated.
+ */
+ static class UpdateOp {
+
+ static final int ADD = 1;
+
+ static final int REMOVE = 1 << 1;
+
+ static final int UPDATE = 1 << 2;
+
+ static final int MOVE = 1 << 3;
+
+ static final int POOL_SIZE = 30;
+
+ int cmd;
+
+ int positionStart;
+
+ Object payload;
+
+ // holds the target position if this is a MOVE
+ int itemCount;
+
+ UpdateOp(int cmd, int positionStart, int itemCount, Object payload) {
+ this.cmd = cmd;
+ this.positionStart = positionStart;
+ this.itemCount = itemCount;
+ this.payload = payload;
+ }
+
+ String cmdToString() {
+ switch (cmd) {
+ case ADD:
+ return "add";
+ case REMOVE:
+ return "rm";
+ case UPDATE:
+ return "up";
+ case MOVE:
+ return "mv";
+ }
+ return "??";
+ }
+
+ @Override
+ public String toString() {
+ return Integer.toHexString(System.identityHashCode(this))
+ + "[" + cmdToString() + ",s:" + positionStart + "c:" + itemCount
+ + ",p:" + payload + "]";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ UpdateOp op = (UpdateOp) o;
+
+ if (cmd != op.cmd) {
+ return false;
+ }
+ if (cmd == MOVE && Math.abs(itemCount - positionStart) == 1) {
+ // reverse of this is also true
+ if (itemCount == op.positionStart && positionStart == op.itemCount) {
+ return true;
+ }
+ }
+ if (itemCount != op.itemCount) {
+ return false;
+ }
+ if (positionStart != op.positionStart) {
+ return false;
+ }
+ if (payload != null) {
+ if (!payload.equals(op.payload)) {
+ return false;
+ }
+ } else if (op.payload != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = cmd;
+ result = 31 * result + positionStart;
+ result = 31 * result + itemCount;
+ return result;
+ }
+ }
+
+ @Override
+ public UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount, Object payload) {
+ UpdateOp op = mUpdateOpPool.acquire();
+ if (op == null) {
+ op = new UpdateOp(cmd, positionStart, itemCount, payload);
+ } else {
+ op.cmd = cmd;
+ op.positionStart = positionStart;
+ op.itemCount = itemCount;
+ op.payload = payload;
+ }
+ return op;
+ }
+
+ @Override
+ public void recycleUpdateOp(UpdateOp op) {
+ if (!mDisableRecycler) {
+ op.payload = null;
+ mUpdateOpPool.release(op);
+ }
+ }
+
+ void recycleUpdateOpsAndClearList(List<UpdateOp> ops) {
+ final int count = ops.size();
+ for (int i = 0; i < count; i++) {
+ recycleUpdateOp(ops.get(i));
+ }
+ ops.clear();
+ }
+
+ /**
+ * Contract between AdapterHelper and RecyclerView.
+ */
+ interface Callback {
+
+ RecyclerView.ViewHolder findViewHolder(int position);
+
+ void offsetPositionsForRemovingInvisible(int positionStart, int itemCount);
+
+ void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount);
+
+ void markViewHoldersUpdated(int positionStart, int itemCount, Object payloads);
+
+ void onDispatchFirstPass(UpdateOp updateOp);
+
+ void onDispatchSecondPass(UpdateOp updateOp);
+
+ void offsetPositionsForAdd(int positionStart, int itemCount);
+
+ void offsetPositionsForMove(int from, int to);
+ }
+}
diff --git a/core/java/com/android/internal/widget/ChildHelper.java b/core/java/com/android/internal/widget/ChildHelper.java
new file mode 100644
index 0000000..e9136d0
--- /dev/null
+++ b/core/java/com/android/internal/widget/ChildHelper.java
@@ -0,0 +1,538 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class to manage children.
+ * <p>
+ * It wraps a RecyclerView and adds ability to hide some children. There are two sets of methods
+ * provided by this class. <b>Regular</b> methods are the ones that replicate ViewGroup methods
+ * like getChildAt, getChildCount etc. These methods ignore hidden children.
+ * <p>
+ * When RecyclerView needs direct access to the view group children, it can call unfiltered
+ * methods like get getUnfilteredChildCount or getUnfilteredChildAt.
+ */
+class ChildHelper {
+
+ private static final boolean DEBUG = false;
+
+ private static final String TAG = "ChildrenHelper";
+
+ final Callback mCallback;
+
+ final Bucket mBucket;
+
+ final List<View> mHiddenViews;
+
+ ChildHelper(Callback callback) {
+ mCallback = callback;
+ mBucket = new Bucket();
+ mHiddenViews = new ArrayList<View>();
+ }
+
+ /**
+ * Marks a child view as hidden
+ *
+ * @param child View to hide.
+ */
+ private void hideViewInternal(View child) {
+ mHiddenViews.add(child);
+ mCallback.onEnteredHiddenState(child);
+ }
+
+ /**
+ * Unmarks a child view as hidden.
+ *
+ * @param child View to hide.
+ */
+ private boolean unhideViewInternal(View child) {
+ if (mHiddenViews.remove(child)) {
+ mCallback.onLeftHiddenState(child);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Adds a view to the ViewGroup
+ *
+ * @param child View to add.
+ * @param hidden If set to true, this item will be invisible from regular methods.
+ */
+ void addView(View child, boolean hidden) {
+ addView(child, -1, hidden);
+ }
+
+ /**
+ * Add a view to the ViewGroup at an index
+ *
+ * @param child View to add.
+ * @param index Index of the child from the regular perspective (excluding hidden views).
+ * ChildHelper offsets this index to actual ViewGroup index.
+ * @param hidden If set to true, this item will be invisible from regular methods.
+ */
+ void addView(View child, int index, boolean hidden) {
+ final int offset;
+ if (index < 0) {
+ offset = mCallback.getChildCount();
+ } else {
+ offset = getOffset(index);
+ }
+ mBucket.insert(offset, hidden);
+ if (hidden) {
+ hideViewInternal(child);
+ }
+ mCallback.addView(child, offset);
+ if (DEBUG) {
+ Log.d(TAG, "addViewAt " + index + ",h:" + hidden + ", " + this);
+ }
+ }
+
+ private int getOffset(int index) {
+ if (index < 0) {
+ return -1; //anything below 0 won't work as diff will be undefined.
+ }
+ final int limit = mCallback.getChildCount();
+ int offset = index;
+ while (offset < limit) {
+ final int removedBefore = mBucket.countOnesBefore(offset);
+ final int diff = index - (offset - removedBefore);
+ if (diff == 0) {
+ while (mBucket.get(offset)) { // ensure this offset is not hidden
+ offset++;
+ }
+ return offset;
+ } else {
+ offset += diff;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Removes the provided View from underlying RecyclerView.
+ *
+ * @param view The view to remove.
+ */
+ void removeView(View view) {
+ int index = mCallback.indexOfChild(view);
+ if (index < 0) {
+ return;
+ }
+ if (mBucket.remove(index)) {
+ unhideViewInternal(view);
+ }
+ mCallback.removeViewAt(index);
+ if (DEBUG) {
+ Log.d(TAG, "remove View off:" + index + "," + this);
+ }
+ }
+
+ /**
+ * Removes the view at the provided index from RecyclerView.
+ *
+ * @param index Index of the child from the regular perspective (excluding hidden views).
+ * ChildHelper offsets this index to actual ViewGroup index.
+ */
+ void removeViewAt(int index) {
+ final int offset = getOffset(index);
+ final View view = mCallback.getChildAt(offset);
+ if (view == null) {
+ return;
+ }
+ if (mBucket.remove(offset)) {
+ unhideViewInternal(view);
+ }
+ mCallback.removeViewAt(offset);
+ if (DEBUG) {
+ Log.d(TAG, "removeViewAt " + index + ", off:" + offset + ", " + this);
+ }
+ }
+
+ /**
+ * Returns the child at provided index.
+ *
+ * @param index Index of the child to return in regular perspective.
+ */
+ View getChildAt(int index) {
+ final int offset = getOffset(index);
+ return mCallback.getChildAt(offset);
+ }
+
+ /**
+ * Removes all views from the ViewGroup including the hidden ones.
+ */
+ void removeAllViewsUnfiltered() {
+ mBucket.reset();
+ for (int i = mHiddenViews.size() - 1; i >= 0; i--) {
+ mCallback.onLeftHiddenState(mHiddenViews.get(i));
+ mHiddenViews.remove(i);
+ }
+ mCallback.removeAllViews();
+ if (DEBUG) {
+ Log.d(TAG, "removeAllViewsUnfiltered");
+ }
+ }
+
+ /**
+ * This can be used to find a disappearing view by position.
+ *
+ * @param position The adapter position of the item.
+ * @return A hidden view with a valid ViewHolder that matches the position.
+ */
+ View findHiddenNonRemovedView(int position) {
+ final int count = mHiddenViews.size();
+ for (int i = 0; i < count; i++) {
+ final View view = mHiddenViews.get(i);
+ RecyclerView.ViewHolder holder = mCallback.getChildViewHolder(view);
+ if (holder.getLayoutPosition() == position
+ && !holder.isInvalid()
+ && !holder.isRemoved()) {
+ return view;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Attaches the provided view to the underlying ViewGroup.
+ *
+ * @param child Child to attach.
+ * @param index Index of the child to attach in regular perspective.
+ * @param layoutParams LayoutParams for the child.
+ * @param hidden If set to true, this item will be invisible to the regular methods.
+ */
+ void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams,
+ boolean hidden) {
+ final int offset;
+ if (index < 0) {
+ offset = mCallback.getChildCount();
+ } else {
+ offset = getOffset(index);
+ }
+ mBucket.insert(offset, hidden);
+ if (hidden) {
+ hideViewInternal(child);
+ }
+ mCallback.attachViewToParent(child, offset, layoutParams);
+ if (DEBUG) {
+ Log.d(TAG, "attach view to parent index:" + index + ",off:" + offset + ","
+ + "h:" + hidden + ", " + this);
+ }
+ }
+
+ /**
+ * Returns the number of children that are not hidden.
+ *
+ * @return Number of children that are not hidden.
+ * @see #getChildAt(int)
+ */
+ int getChildCount() {
+ return mCallback.getChildCount() - mHiddenViews.size();
+ }
+
+ /**
+ * Returns the total number of children.
+ *
+ * @return The total number of children including the hidden views.
+ * @see #getUnfilteredChildAt(int)
+ */
+ int getUnfilteredChildCount() {
+ return mCallback.getChildCount();
+ }
+
+ /**
+ * Returns a child by ViewGroup offset. ChildHelper won't offset this index.
+ *
+ * @param index ViewGroup index of the child to return.
+ * @return The view in the provided index.
+ */
+ View getUnfilteredChildAt(int index) {
+ return mCallback.getChildAt(index);
+ }
+
+ /**
+ * Detaches the view at the provided index.
+ *
+ * @param index Index of the child to return in regular perspective.
+ */
+ void detachViewFromParent(int index) {
+ final int offset = getOffset(index);
+ mBucket.remove(offset);
+ mCallback.detachViewFromParent(offset);
+ if (DEBUG) {
+ Log.d(TAG, "detach view from parent " + index + ", off:" + offset);
+ }
+ }
+
+ /**
+ * Returns the index of the child in regular perspective.
+ *
+ * @param child The child whose index will be returned.
+ * @return The regular perspective index of the child or -1 if it does not exists.
+ */
+ int indexOfChild(View child) {
+ final int index = mCallback.indexOfChild(child);
+ if (index == -1) {
+ return -1;
+ }
+ if (mBucket.get(index)) {
+ if (DEBUG) {
+ throw new IllegalArgumentException("cannot get index of a hidden child");
+ } else {
+ return -1;
+ }
+ }
+ // reverse the index
+ return index - mBucket.countOnesBefore(index);
+ }
+
+ /**
+ * Returns whether a View is visible to LayoutManager or not.
+ *
+ * @param view The child view to check. Should be a child of the Callback.
+ * @return True if the View is not visible to LayoutManager
+ */
+ boolean isHidden(View view) {
+ return mHiddenViews.contains(view);
+ }
+
+ /**
+ * Marks a child view as hidden.
+ *
+ * @param view The view to hide.
+ */
+ void hide(View view) {
+ final int offset = mCallback.indexOfChild(view);
+ if (offset < 0) {
+ throw new IllegalArgumentException("view is not a child, cannot hide " + view);
+ }
+ if (DEBUG && mBucket.get(offset)) {
+ throw new RuntimeException("trying to hide same view twice, how come ? " + view);
+ }
+ mBucket.set(offset);
+ hideViewInternal(view);
+ if (DEBUG) {
+ Log.d(TAG, "hiding child " + view + " at offset " + offset + ", " + this);
+ }
+ }
+
+ /**
+ * Moves a child view from hidden list to regular list.
+ * Calling this method should probably be followed by a detach, otherwise, it will suddenly
+ * show up in LayoutManager's children list.
+ *
+ * @param view The hidden View to unhide
+ */
+ void unhide(View view) {
+ final int offset = mCallback.indexOfChild(view);
+ if (offset < 0) {
+ throw new IllegalArgumentException("view is not a child, cannot hide " + view);
+ }
+ if (!mBucket.get(offset)) {
+ throw new RuntimeException("trying to unhide a view that was not hidden" + view);
+ }
+ mBucket.clear(offset);
+ unhideViewInternal(view);
+ }
+
+ @Override
+ public String toString() {
+ return mBucket.toString() + ", hidden list:" + mHiddenViews.size();
+ }
+
+ /**
+ * Removes a view from the ViewGroup if it is hidden.
+ *
+ * @param view The view to remove.
+ * @return True if the View is found and it is hidden. False otherwise.
+ */
+ boolean removeViewIfHidden(View view) {
+ final int index = mCallback.indexOfChild(view);
+ if (index == -1) {
+ if (unhideViewInternal(view) && DEBUG) {
+ throw new IllegalStateException("view is in hidden list but not in view group");
+ }
+ return true;
+ }
+ if (mBucket.get(index)) {
+ mBucket.remove(index);
+ if (!unhideViewInternal(view) && DEBUG) {
+ throw new IllegalStateException(
+ "removed a hidden view but it is not in hidden views list");
+ }
+ mCallback.removeViewAt(index);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Bitset implementation that provides methods to offset indices.
+ */
+ static class Bucket {
+
+ static final int BITS_PER_WORD = Long.SIZE;
+
+ static final long LAST_BIT = 1L << (Long.SIZE - 1);
+
+ long mData = 0;
+
+ Bucket mNext;
+
+ void set(int index) {
+ if (index >= BITS_PER_WORD) {
+ ensureNext();
+ mNext.set(index - BITS_PER_WORD);
+ } else {
+ mData |= 1L << index;
+ }
+ }
+
+ private void ensureNext() {
+ if (mNext == null) {
+ mNext = new Bucket();
+ }
+ }
+
+ void clear(int index) {
+ if (index >= BITS_PER_WORD) {
+ if (mNext != null) {
+ mNext.clear(index - BITS_PER_WORD);
+ }
+ } else {
+ mData &= ~(1L << index);
+ }
+
+ }
+
+ boolean get(int index) {
+ if (index >= BITS_PER_WORD) {
+ ensureNext();
+ return mNext.get(index - BITS_PER_WORD);
+ } else {
+ return (mData & (1L << index)) != 0;
+ }
+ }
+
+ void reset() {
+ mData = 0;
+ if (mNext != null) {
+ mNext.reset();
+ }
+ }
+
+ void insert(int index, boolean value) {
+ if (index >= BITS_PER_WORD) {
+ ensureNext();
+ mNext.insert(index - BITS_PER_WORD, value);
+ } else {
+ final boolean lastBit = (mData & LAST_BIT) != 0;
+ long mask = (1L << index) - 1;
+ final long before = mData & mask;
+ final long after = ((mData & ~mask)) << 1;
+ mData = before | after;
+ if (value) {
+ set(index);
+ } else {
+ clear(index);
+ }
+ if (lastBit || mNext != null) {
+ ensureNext();
+ mNext.insert(0, lastBit);
+ }
+ }
+ }
+
+ boolean remove(int index) {
+ if (index >= BITS_PER_WORD) {
+ ensureNext();
+ return mNext.remove(index - BITS_PER_WORD);
+ } else {
+ long mask = (1L << index);
+ final boolean value = (mData & mask) != 0;
+ mData &= ~mask;
+ mask = mask - 1;
+ final long before = mData & mask;
+ // cannot use >> because it adds one.
+ final long after = Long.rotateRight(mData & ~mask, 1);
+ mData = before | after;
+ if (mNext != null) {
+ if (mNext.get(0)) {
+ set(BITS_PER_WORD - 1);
+ }
+ mNext.remove(0);
+ }
+ return value;
+ }
+ }
+
+ int countOnesBefore(int index) {
+ if (mNext == null) {
+ if (index >= BITS_PER_WORD) {
+ return Long.bitCount(mData);
+ }
+ return Long.bitCount(mData & ((1L << index) - 1));
+ }
+ if (index < BITS_PER_WORD) {
+ return Long.bitCount(mData & ((1L << index) - 1));
+ } else {
+ return mNext.countOnesBefore(index - BITS_PER_WORD) + Long.bitCount(mData);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return mNext == null ? Long.toBinaryString(mData)
+ : mNext.toString() + "xx" + Long.toBinaryString(mData);
+ }
+ }
+
+ interface Callback {
+
+ int getChildCount();
+
+ void addView(View child, int index);
+
+ int indexOfChild(View view);
+
+ void removeViewAt(int index);
+
+ View getChildAt(int offset);
+
+ void removeAllViews();
+
+ RecyclerView.ViewHolder getChildViewHolder(View view);
+
+ void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams);
+
+ void detachViewFromParent(int offset);
+
+ void onEnteredHiddenState(View child);
+
+ void onLeftHiddenState(View child);
+ }
+}
+
diff --git a/core/java/com/android/internal/widget/DefaultItemAnimator.java b/core/java/com/android/internal/widget/DefaultItemAnimator.java
new file mode 100644
index 0000000..92345af
--- /dev/null
+++ b/core/java/com/android/internal/widget/DefaultItemAnimator.java
@@ -0,0 +1,668 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+
+import com.android.internal.widget.RecyclerView.ViewHolder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This implementation of {@link RecyclerView.ItemAnimator} provides basic
+ * animations on remove, add, and move events that happen to the items in
+ * a RecyclerView. RecyclerView uses a DefaultItemAnimator by default.
+ *
+ * @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator)
+ */
+public class DefaultItemAnimator extends SimpleItemAnimator {
+ private static final boolean DEBUG = false;
+
+ private static TimeInterpolator sDefaultInterpolator;
+
+ private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<>();
+ private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<>();
+ private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();
+ private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>();
+
+ ArrayList<ArrayList<ViewHolder>> mAdditionsList = new ArrayList<>();
+ ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>();
+ ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>();
+
+ ArrayList<ViewHolder> mAddAnimations = new ArrayList<>();
+ ArrayList<ViewHolder> mMoveAnimations = new ArrayList<>();
+ ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>();
+ ArrayList<ViewHolder> mChangeAnimations = new ArrayList<>();
+
+ private static class MoveInfo {
+ public ViewHolder holder;
+ public int fromX, fromY, toX, toY;
+
+ MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) {
+ this.holder = holder;
+ this.fromX = fromX;
+ this.fromY = fromY;
+ this.toX = toX;
+ this.toY = toY;
+ }
+ }
+
+ private static class ChangeInfo {
+ public ViewHolder oldHolder, newHolder;
+ public int fromX, fromY, toX, toY;
+ private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) {
+ this.oldHolder = oldHolder;
+ this.newHolder = newHolder;
+ }
+
+ ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder,
+ int fromX, int fromY, int toX, int toY) {
+ this(oldHolder, newHolder);
+ this.fromX = fromX;
+ this.fromY = fromY;
+ this.toX = toX;
+ this.toY = toY;
+ }
+
+ @Override
+ public String toString() {
+ return "ChangeInfo{"
+ + "oldHolder=" + oldHolder
+ + ", newHolder=" + newHolder
+ + ", fromX=" + fromX
+ + ", fromY=" + fromY
+ + ", toX=" + toX
+ + ", toY=" + toY
+ + '}';
+ }
+ }
+
+ @Override
+ public void runPendingAnimations() {
+ boolean removalsPending = !mPendingRemovals.isEmpty();
+ boolean movesPending = !mPendingMoves.isEmpty();
+ boolean changesPending = !mPendingChanges.isEmpty();
+ boolean additionsPending = !mPendingAdditions.isEmpty();
+ if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
+ // nothing to animate
+ return;
+ }
+ // First, remove stuff
+ for (ViewHolder holder : mPendingRemovals) {
+ animateRemoveImpl(holder);
+ }
+ mPendingRemovals.clear();
+ // Next, move stuff
+ if (movesPending) {
+ final ArrayList<MoveInfo> moves = new ArrayList<>();
+ moves.addAll(mPendingMoves);
+ mMovesList.add(moves);
+ mPendingMoves.clear();
+ Runnable mover = new Runnable() {
+ @Override
+ public void run() {
+ for (MoveInfo moveInfo : moves) {
+ animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
+ moveInfo.toX, moveInfo.toY);
+ }
+ moves.clear();
+ mMovesList.remove(moves);
+ }
+ };
+ if (removalsPending) {
+ View view = moves.get(0).holder.itemView;
+ view.postOnAnimationDelayed(mover, getRemoveDuration());
+ } else {
+ mover.run();
+ }
+ }
+ // Next, change stuff, to run in parallel with move animations
+ if (changesPending) {
+ final ArrayList<ChangeInfo> changes = new ArrayList<>();
+ changes.addAll(mPendingChanges);
+ mChangesList.add(changes);
+ mPendingChanges.clear();
+ Runnable changer = new Runnable() {
+ @Override
+ public void run() {
+ for (ChangeInfo change : changes) {
+ animateChangeImpl(change);
+ }
+ changes.clear();
+ mChangesList.remove(changes);
+ }
+ };
+ if (removalsPending) {
+ ViewHolder holder = changes.get(0).oldHolder;
+ holder.itemView.postOnAnimationDelayed(changer, getRemoveDuration());
+ } else {
+ changer.run();
+ }
+ }
+ // Next, add stuff
+ if (additionsPending) {
+ final ArrayList<ViewHolder> additions = new ArrayList<>();
+ additions.addAll(mPendingAdditions);
+ mAdditionsList.add(additions);
+ mPendingAdditions.clear();
+ Runnable adder = new Runnable() {
+ @Override
+ public void run() {
+ for (ViewHolder holder : additions) {
+ animateAddImpl(holder);
+ }
+ additions.clear();
+ mAdditionsList.remove(additions);
+ }
+ };
+ if (removalsPending || movesPending || changesPending) {
+ long removeDuration = removalsPending ? getRemoveDuration() : 0;
+ long moveDuration = movesPending ? getMoveDuration() : 0;
+ long changeDuration = changesPending ? getChangeDuration() : 0;
+ long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
+ View view = additions.get(0).itemView;
+ view.postOnAnimationDelayed(adder, totalDelay);
+ } else {
+ adder.run();
+ }
+ }
+ }
+
+ @Override
+ public boolean animateRemove(final ViewHolder holder) {
+ resetAnimation(holder);
+ mPendingRemovals.add(holder);
+ return true;
+ }
+
+ private void animateRemoveImpl(final ViewHolder holder) {
+ final View view = holder.itemView;
+ final ViewPropertyAnimator animation = view.animate();
+ mRemoveAnimations.add(holder);
+ animation.setDuration(getRemoveDuration()).alpha(0).setListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ dispatchRemoveStarting(holder);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ animation.setListener(null);
+ view.setAlpha(1);
+ dispatchRemoveFinished(holder);
+ mRemoveAnimations.remove(holder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+
+ @Override
+ public boolean animateAdd(final ViewHolder holder) {
+ resetAnimation(holder);
+ holder.itemView.setAlpha(0);
+ mPendingAdditions.add(holder);
+ return true;
+ }
+
+ void animateAddImpl(final ViewHolder holder) {
+ final View view = holder.itemView;
+ final ViewPropertyAnimator animation = view.animate();
+ mAddAnimations.add(holder);
+ animation.alpha(1).setDuration(getAddDuration())
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ dispatchAddStarting(holder);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ view.setAlpha(1);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ animation.setListener(null);
+ dispatchAddFinished(holder);
+ mAddAnimations.remove(holder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+
+ @Override
+ public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
+ int toX, int toY) {
+ final View view = holder.itemView;
+ fromX += holder.itemView.getTranslationX();
+ fromY += holder.itemView.getTranslationY();
+ resetAnimation(holder);
+ int deltaX = toX - fromX;
+ int deltaY = toY - fromY;
+ if (deltaX == 0 && deltaY == 0) {
+ dispatchMoveFinished(holder);
+ return false;
+ }
+ if (deltaX != 0) {
+ view.setTranslationX(-deltaX);
+ }
+ if (deltaY != 0) {
+ view.setTranslationY(-deltaY);
+ }
+ mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
+ return true;
+ }
+
+ void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) {
+ final View view = holder.itemView;
+ final int deltaX = toX - fromX;
+ final int deltaY = toY - fromY;
+ if (deltaX != 0) {
+ view.animate().translationX(0);
+ }
+ if (deltaY != 0) {
+ view.animate().translationY(0);
+ }
+ // TODO: make EndActions end listeners instead, since end actions aren't called when
+ // vpas are canceled (and can't end them. why?)
+ // need listener functionality in VPACompat for this. Ick.
+ final ViewPropertyAnimator animation = view.animate();
+ mMoveAnimations.add(holder);
+ animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ dispatchMoveStarting(holder);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ if (deltaX != 0) {
+ view.setTranslationX(0);
+ }
+ if (deltaY != 0) {
+ view.setTranslationY(0);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ animation.setListener(null);
+ dispatchMoveFinished(holder);
+ mMoveAnimations.remove(holder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+
+ @Override
+ public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
+ int fromX, int fromY, int toX, int toY) {
+ if (oldHolder == newHolder) {
+ // Don't know how to run change animations when the same view holder is re-used.
+ // run a move animation to handle position changes.
+ return animateMove(oldHolder, fromX, fromY, toX, toY);
+ }
+ final float prevTranslationX = oldHolder.itemView.getTranslationX();
+ final float prevTranslationY = oldHolder.itemView.getTranslationY();
+ final float prevAlpha = oldHolder.itemView.getAlpha();
+ resetAnimation(oldHolder);
+ int deltaX = (int) (toX - fromX - prevTranslationX);
+ int deltaY = (int) (toY - fromY - prevTranslationY);
+ // recover prev translation state after ending animation
+ oldHolder.itemView.setTranslationX(prevTranslationX);
+ oldHolder.itemView.setTranslationY(prevTranslationY);
+ oldHolder.itemView.setAlpha(prevAlpha);
+ if (newHolder != null) {
+ // carry over translation values
+ resetAnimation(newHolder);
+ newHolder.itemView.setTranslationX(-deltaX);
+ newHolder.itemView.setTranslationY(-deltaY);
+ newHolder.itemView.setAlpha(0);
+ }
+ mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
+ return true;
+ }
+
+ void animateChangeImpl(final ChangeInfo changeInfo) {
+ final ViewHolder holder = changeInfo.oldHolder;
+ final View view = holder == null ? null : holder.itemView;
+ final ViewHolder newHolder = changeInfo.newHolder;
+ final View newView = newHolder != null ? newHolder.itemView : null;
+ if (view != null) {
+ final ViewPropertyAnimator oldViewAnim = view.animate().setDuration(
+ getChangeDuration());
+ mChangeAnimations.add(changeInfo.oldHolder);
+ oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
+ oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
+ oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ dispatchChangeStarting(changeInfo.oldHolder, true);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ oldViewAnim.setListener(null);
+ view.setAlpha(1);
+ view.setTranslationX(0);
+ view.setTranslationY(0);
+ dispatchChangeFinished(changeInfo.oldHolder, true);
+ mChangeAnimations.remove(changeInfo.oldHolder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+ if (newView != null) {
+ final ViewPropertyAnimator newViewAnimation = newView.animate();
+ mChangeAnimations.add(changeInfo.newHolder);
+ newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration())
+ .alpha(1).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ dispatchChangeStarting(changeInfo.newHolder, false);
+ }
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ newViewAnimation.setListener(null);
+ newView.setAlpha(1);
+ newView.setTranslationX(0);
+ newView.setTranslationY(0);
+ dispatchChangeFinished(changeInfo.newHolder, false);
+ mChangeAnimations.remove(changeInfo.newHolder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+ }
+
+ private void endChangeAnimation(List<ChangeInfo> infoList, ViewHolder item) {
+ for (int i = infoList.size() - 1; i >= 0; i--) {
+ ChangeInfo changeInfo = infoList.get(i);
+ if (endChangeAnimationIfNecessary(changeInfo, item)) {
+ if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
+ infoList.remove(changeInfo);
+ }
+ }
+ }
+ }
+
+ private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
+ if (changeInfo.oldHolder != null) {
+ endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
+ }
+ if (changeInfo.newHolder != null) {
+ endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
+ }
+ }
+ private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item) {
+ boolean oldItem = false;
+ if (changeInfo.newHolder == item) {
+ changeInfo.newHolder = null;
+ } else if (changeInfo.oldHolder == item) {
+ changeInfo.oldHolder = null;
+ oldItem = true;
+ } else {
+ return false;
+ }
+ item.itemView.setAlpha(1);
+ item.itemView.setTranslationX(0);
+ item.itemView.setTranslationY(0);
+ dispatchChangeFinished(item, oldItem);
+ return true;
+ }
+
+ @Override
+ public void endAnimation(ViewHolder item) {
+ final View view = item.itemView;
+ // this will trigger end callback which should set properties to their target values.
+ view.animate().cancel();
+ // TODO if some other animations are chained to end, how do we cancel them as well?
+ for (int i = mPendingMoves.size() - 1; i >= 0; i--) {
+ MoveInfo moveInfo = mPendingMoves.get(i);
+ if (moveInfo.holder == item) {
+ view.setTranslationY(0);
+ view.setTranslationX(0);
+ dispatchMoveFinished(item);
+ mPendingMoves.remove(i);
+ }
+ }
+ endChangeAnimation(mPendingChanges, item);
+ if (mPendingRemovals.remove(item)) {
+ view.setAlpha(1);
+ dispatchRemoveFinished(item);
+ }
+ if (mPendingAdditions.remove(item)) {
+ view.setAlpha(1);
+ dispatchAddFinished(item);
+ }
+
+ for (int i = mChangesList.size() - 1; i >= 0; i--) {
+ ArrayList<ChangeInfo> changes = mChangesList.get(i);
+ endChangeAnimation(changes, item);
+ if (changes.isEmpty()) {
+ mChangesList.remove(i);
+ }
+ }
+ for (int i = mMovesList.size() - 1; i >= 0; i--) {
+ ArrayList<MoveInfo> moves = mMovesList.get(i);
+ for (int j = moves.size() - 1; j >= 0; j--) {
+ MoveInfo moveInfo = moves.get(j);
+ if (moveInfo.holder == item) {
+ view.setTranslationY(0);
+ view.setTranslationX(0);
+ dispatchMoveFinished(item);
+ moves.remove(j);
+ if (moves.isEmpty()) {
+ mMovesList.remove(i);
+ }
+ break;
+ }
+ }
+ }
+ for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
+ ArrayList<ViewHolder> additions = mAdditionsList.get(i);
+ if (additions.remove(item)) {
+ view.setAlpha(1);
+ dispatchAddFinished(item);
+ if (additions.isEmpty()) {
+ mAdditionsList.remove(i);
+ }
+ }
+ }
+
+ // animations should be ended by the cancel above.
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (mRemoveAnimations.remove(item) && DEBUG) {
+ throw new IllegalStateException("after animation is cancelled, item should not be in "
+ + "mRemoveAnimations list");
+ }
+
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (mAddAnimations.remove(item) && DEBUG) {
+ throw new IllegalStateException("after animation is cancelled, item should not be in "
+ + "mAddAnimations list");
+ }
+
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (mChangeAnimations.remove(item) && DEBUG) {
+ throw new IllegalStateException("after animation is cancelled, item should not be in "
+ + "mChangeAnimations list");
+ }
+
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (mMoveAnimations.remove(item) && DEBUG) {
+ throw new IllegalStateException("after animation is cancelled, item should not be in "
+ + "mMoveAnimations list");
+ }
+ dispatchFinishedWhenDone();
+ }
+
+ private void resetAnimation(ViewHolder holder) {
+ if (sDefaultInterpolator == null) {
+ sDefaultInterpolator = new ValueAnimator().getInterpolator();
+ }
+ holder.itemView.animate().setInterpolator(sDefaultInterpolator);
+ endAnimation(holder);
+ }
+
+ @Override
+ public boolean isRunning() {
+ return (!mPendingAdditions.isEmpty()
+ || !mPendingChanges.isEmpty()
+ || !mPendingMoves.isEmpty()
+ || !mPendingRemovals.isEmpty()
+ || !mMoveAnimations.isEmpty()
+ || !mRemoveAnimations.isEmpty()
+ || !mAddAnimations.isEmpty()
+ || !mChangeAnimations.isEmpty()
+ || !mMovesList.isEmpty()
+ || !mAdditionsList.isEmpty()
+ || !mChangesList.isEmpty());
+ }
+
+ /**
+ * Check the state of currently pending and running animations. If there are none
+ * pending/running, call {@link #dispatchAnimationsFinished()} to notify any
+ * listeners.
+ */
+ void dispatchFinishedWhenDone() {
+ if (!isRunning()) {
+ dispatchAnimationsFinished();
+ }
+ }
+
+ @Override
+ public void endAnimations() {
+ int count = mPendingMoves.size();
+ for (int i = count - 1; i >= 0; i--) {
+ MoveInfo item = mPendingMoves.get(i);
+ View view = item.holder.itemView;
+ view.setTranslationY(0);
+ view.setTranslationX(0);
+ dispatchMoveFinished(item.holder);
+ mPendingMoves.remove(i);
+ }
+ count = mPendingRemovals.size();
+ for (int i = count - 1; i >= 0; i--) {
+ ViewHolder item = mPendingRemovals.get(i);
+ dispatchRemoveFinished(item);
+ mPendingRemovals.remove(i);
+ }
+ count = mPendingAdditions.size();
+ for (int i = count - 1; i >= 0; i--) {
+ ViewHolder item = mPendingAdditions.get(i);
+ item.itemView.setAlpha(1);
+ dispatchAddFinished(item);
+ mPendingAdditions.remove(i);
+ }
+ count = mPendingChanges.size();
+ for (int i = count - 1; i >= 0; i--) {
+ endChangeAnimationIfNecessary(mPendingChanges.get(i));
+ }
+ mPendingChanges.clear();
+ if (!isRunning()) {
+ return;
+ }
+
+ int listCount = mMovesList.size();
+ for (int i = listCount - 1; i >= 0; i--) {
+ ArrayList<MoveInfo> moves = mMovesList.get(i);
+ count = moves.size();
+ for (int j = count - 1; j >= 0; j--) {
+ MoveInfo moveInfo = moves.get(j);
+ ViewHolder item = moveInfo.holder;
+ View view = item.itemView;
+ view.setTranslationY(0);
+ view.setTranslationX(0);
+ dispatchMoveFinished(moveInfo.holder);
+ moves.remove(j);
+ if (moves.isEmpty()) {
+ mMovesList.remove(moves);
+ }
+ }
+ }
+ listCount = mAdditionsList.size();
+ for (int i = listCount - 1; i >= 0; i--) {
+ ArrayList<ViewHolder> additions = mAdditionsList.get(i);
+ count = additions.size();
+ for (int j = count - 1; j >= 0; j--) {
+ ViewHolder item = additions.get(j);
+ View view = item.itemView;
+ view.setAlpha(1);
+ dispatchAddFinished(item);
+ additions.remove(j);
+ if (additions.isEmpty()) {
+ mAdditionsList.remove(additions);
+ }
+ }
+ }
+ listCount = mChangesList.size();
+ for (int i = listCount - 1; i >= 0; i--) {
+ ArrayList<ChangeInfo> changes = mChangesList.get(i);
+ count = changes.size();
+ for (int j = count - 1; j >= 0; j--) {
+ endChangeAnimationIfNecessary(changes.get(j));
+ if (changes.isEmpty()) {
+ mChangesList.remove(changes);
+ }
+ }
+ }
+
+ cancelAll(mRemoveAnimations);
+ cancelAll(mMoveAnimations);
+ cancelAll(mAddAnimations);
+ cancelAll(mChangeAnimations);
+
+ dispatchAnimationsFinished();
+ }
+
+ void cancelAll(List<ViewHolder> viewHolders) {
+ for (int i = viewHolders.size() - 1; i >= 0; i--) {
+ viewHolders.get(i).itemView.animate().cancel();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * If the payload list is not empty, DefaultItemAnimator returns <code>true</code>.
+ * When this is the case:
+ * <ul>
+ * <li>If you override {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}, both
+ * ViewHolder arguments will be the same instance.
+ * </li>
+ * <li>
+ * If you are not overriding {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)},
+ * then DefaultItemAnimator will call {@link #animateMove(ViewHolder, int, int, int, int)} and
+ * run a move animation instead.
+ * </li>
+ * </ul>
+ */
+ @Override
+ public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
+ @NonNull List<Object> payloads) {
+ return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
+ }
+}
diff --git a/core/java/com/android/internal/widget/GapWorker.java b/core/java/com/android/internal/widget/GapWorker.java
new file mode 100644
index 0000000..5972396
--- /dev/null
+++ b/core/java/com/android/internal/widget/GapWorker.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.Nullable;
+import android.os.Trace;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.concurrent.TimeUnit;
+
+final class GapWorker implements Runnable {
+
+ static final ThreadLocal<GapWorker> sGapWorker = new ThreadLocal<>();
+
+ ArrayList<RecyclerView> mRecyclerViews = new ArrayList<>();
+ long mPostTimeNs;
+ long mFrameIntervalNs;
+
+ static class Task {
+ public boolean immediate;
+ public int viewVelocity;
+ public int distanceToItem;
+ public RecyclerView view;
+ public int position;
+
+ public void clear() {
+ immediate = false;
+ viewVelocity = 0;
+ distanceToItem = 0;
+ view = null;
+ position = 0;
+ }
+ }
+
+ /**
+ * Temporary storage for prefetch Tasks that execute in {@link #prefetch(long)}. Task objects
+ * are pooled in the ArrayList, and never removed to avoid allocations, but always cleared
+ * in between calls.
+ */
+ private ArrayList<Task> mTasks = new ArrayList<>();
+
+ /**
+ * Prefetch information associated with a specific RecyclerView.
+ */
+ static class LayoutPrefetchRegistryImpl
+ implements RecyclerView.LayoutManager.LayoutPrefetchRegistry {
+ int mPrefetchDx;
+ int mPrefetchDy;
+ int[] mPrefetchArray;
+
+ int mCount;
+
+ void setPrefetchVector(int dx, int dy) {
+ mPrefetchDx = dx;
+ mPrefetchDy = dy;
+ }
+
+ void collectPrefetchPositionsFromView(RecyclerView view, boolean nested) {
+ mCount = 0;
+ if (mPrefetchArray != null) {
+ Arrays.fill(mPrefetchArray, -1);
+ }
+
+ final RecyclerView.LayoutManager layout = view.mLayout;
+ if (view.mAdapter != null
+ && layout != null
+ && layout.isItemPrefetchEnabled()) {
+ if (nested) {
+ // nested prefetch, only if no adapter updates pending. Note: we don't query
+ // view.hasPendingAdapterUpdates(), as first layout may not have occurred
+ if (!view.mAdapterHelper.hasPendingUpdates()) {
+ layout.collectInitialPrefetchPositions(view.mAdapter.getItemCount(), this);
+ }
+ } else {
+ // momentum based prefetch, only if we trust current child/adapter state
+ if (!view.hasPendingAdapterUpdates()) {
+ layout.collectAdjacentPrefetchPositions(mPrefetchDx, mPrefetchDy,
+ view.mState, this);
+ }
+ }
+
+ if (mCount > layout.mPrefetchMaxCountObserved) {
+ layout.mPrefetchMaxCountObserved = mCount;
+ layout.mPrefetchMaxObservedInInitialPrefetch = nested;
+ view.mRecycler.updateViewCacheSize();
+ }
+ }
+ }
+
+ @Override
+ public void addPosition(int layoutPosition, int pixelDistance) {
+ if (pixelDistance < 0) {
+ throw new IllegalArgumentException("Pixel distance must be non-negative");
+ }
+
+ // allocate or expand array as needed, doubling when needed
+ final int storagePosition = mCount * 2;
+ if (mPrefetchArray == null) {
+ mPrefetchArray = new int[4];
+ Arrays.fill(mPrefetchArray, -1);
+ } else if (storagePosition >= mPrefetchArray.length) {
+ final int[] oldArray = mPrefetchArray;
+ mPrefetchArray = new int[storagePosition * 2];
+ System.arraycopy(oldArray, 0, mPrefetchArray, 0, oldArray.length);
+ }
+
+ // add position
+ mPrefetchArray[storagePosition] = layoutPosition;
+ mPrefetchArray[storagePosition + 1] = pixelDistance;
+
+ mCount++;
+ }
+
+ boolean lastPrefetchIncludedPosition(int position) {
+ if (mPrefetchArray != null) {
+ final int count = mCount * 2;
+ for (int i = 0; i < count; i += 2) {
+ if (mPrefetchArray[i] == position) return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called when prefetch indices are no longer valid for cache prioritization.
+ */
+ void clearPrefetchPositions() {
+ if (mPrefetchArray != null) {
+ Arrays.fill(mPrefetchArray, -1);
+ }
+ }
+ }
+
+ public void add(RecyclerView recyclerView) {
+ if (RecyclerView.DEBUG && mRecyclerViews.contains(recyclerView)) {
+ throw new IllegalStateException("RecyclerView already present in worker list!");
+ }
+ mRecyclerViews.add(recyclerView);
+ }
+
+ public void remove(RecyclerView recyclerView) {
+ boolean removeSuccess = mRecyclerViews.remove(recyclerView);
+ if (RecyclerView.DEBUG && !removeSuccess) {
+ throw new IllegalStateException("RecyclerView removal failed!");
+ }
+ }
+
+ /**
+ * Schedule a prefetch immediately after the current traversal.
+ */
+ void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) {
+ if (recyclerView.isAttachedToWindow()) {
+ if (RecyclerView.DEBUG && !mRecyclerViews.contains(recyclerView)) {
+ throw new IllegalStateException("attempting to post unregistered view!");
+ }
+ if (mPostTimeNs == 0) {
+ mPostTimeNs = recyclerView.getNanoTime();
+ recyclerView.post(this);
+ }
+ }
+
+ recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy);
+ }
+
+ static Comparator<Task> sTaskComparator = new Comparator<Task>() {
+ @Override
+ public int compare(Task lhs, Task rhs) {
+ // first, prioritize non-cleared tasks
+ if ((lhs.view == null) != (rhs.view == null)) {
+ return lhs.view == null ? 1 : -1;
+ }
+
+ // then prioritize immediate
+ if (lhs.immediate != rhs.immediate) {
+ return lhs.immediate ? -1 : 1;
+ }
+
+ // then prioritize _highest_ view velocity
+ int deltaViewVelocity = rhs.viewVelocity - lhs.viewVelocity;
+ if (deltaViewVelocity != 0) return deltaViewVelocity;
+
+ // then prioritize _lowest_ distance to item
+ int deltaDistanceToItem = lhs.distanceToItem - rhs.distanceToItem;
+ if (deltaDistanceToItem != 0) return deltaDistanceToItem;
+
+ return 0;
+ }
+ };
+
+ private void buildTaskList() {
+ // Update PrefetchRegistry in each view
+ final int viewCount = mRecyclerViews.size();
+ int totalTaskCount = 0;
+ for (int i = 0; i < viewCount; i++) {
+ RecyclerView view = mRecyclerViews.get(i);
+ view.mPrefetchRegistry.collectPrefetchPositionsFromView(view, false);
+ totalTaskCount += view.mPrefetchRegistry.mCount;
+ }
+
+ // Populate task list from prefetch data...
+ mTasks.ensureCapacity(totalTaskCount);
+ int totalTaskIndex = 0;
+ for (int i = 0; i < viewCount; i++) {
+ RecyclerView view = mRecyclerViews.get(i);
+ LayoutPrefetchRegistryImpl prefetchRegistry = view.mPrefetchRegistry;
+ final int viewVelocity = Math.abs(prefetchRegistry.mPrefetchDx)
+ + Math.abs(prefetchRegistry.mPrefetchDy);
+ for (int j = 0; j < prefetchRegistry.mCount * 2; j += 2) {
+ final Task task;
+ if (totalTaskIndex >= mTasks.size()) {
+ task = new Task();
+ mTasks.add(task);
+ } else {
+ task = mTasks.get(totalTaskIndex);
+ }
+ final int distanceToItem = prefetchRegistry.mPrefetchArray[j + 1];
+
+ task.immediate = distanceToItem <= viewVelocity;
+ task.viewVelocity = viewVelocity;
+ task.distanceToItem = distanceToItem;
+ task.view = view;
+ task.position = prefetchRegistry.mPrefetchArray[j];
+
+ totalTaskIndex++;
+ }
+ }
+
+ // ... and priority sort
+ Collections.sort(mTasks, sTaskComparator);
+ }
+
+ static boolean isPrefetchPositionAttached(RecyclerView view, int position) {
+ final int childCount = view.mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View attachedView = view.mChildHelper.getUnfilteredChildAt(i);
+ RecyclerView.ViewHolder holder = RecyclerView.getChildViewHolderInt(attachedView);
+ // Note: can use mPosition here because adapter doesn't have pending updates
+ if (holder.mPosition == position && !holder.isInvalid()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
+ int position, long deadlineNs) {
+ if (isPrefetchPositionAttached(view, position)) {
+ // don't attempt to prefetch attached views
+ return null;
+ }
+
+ RecyclerView.Recycler recycler = view.mRecycler;
+ RecyclerView.ViewHolder holder = recycler.tryGetViewHolderForPositionByDeadline(
+ position, false, deadlineNs);
+
+ if (holder != null) {
+ if (holder.isBound()) {
+ // Only give the view a chance to go into the cache if binding succeeded
+ // Note that we must use public method, since item may need cleanup
+ recycler.recycleView(holder.itemView);
+ } else {
+ // Didn't bind, so we can't cache the view, but it will stay in the pool until
+ // next prefetch/traversal. If a View fails to bind, it means we didn't have
+ // enough time prior to the deadline (and won't for other instances of this
+ // type, during this GapWorker prefetch pass).
+ recycler.addViewHolderToRecycledViewPool(holder, false);
+ }
+ }
+ return holder;
+ }
+
+ private void prefetchInnerRecyclerViewWithDeadline(@Nullable RecyclerView innerView,
+ long deadlineNs) {
+ if (innerView == null) {
+ return;
+ }
+
+ if (innerView.mDataSetHasChangedAfterLayout
+ && innerView.mChildHelper.getUnfilteredChildCount() != 0) {
+ // RecyclerView has new data, but old attached views. Clear everything, so that
+ // we can prefetch without partially stale data.
+ innerView.removeAndRecycleViews();
+ }
+
+ // do nested prefetch!
+ final LayoutPrefetchRegistryImpl innerPrefetchRegistry = innerView.mPrefetchRegistry;
+ innerPrefetchRegistry.collectPrefetchPositionsFromView(innerView, true);
+
+ if (innerPrefetchRegistry.mCount != 0) {
+ try {
+ Trace.beginSection(RecyclerView.TRACE_NESTED_PREFETCH_TAG);
+ innerView.mState.prepareForNestedPrefetch(innerView.mAdapter);
+ for (int i = 0; i < innerPrefetchRegistry.mCount * 2; i += 2) {
+ // Note that we ignore immediate flag for inner items because
+ // we have lower confidence they're needed next frame.
+ final int innerPosition = innerPrefetchRegistry.mPrefetchArray[i];
+ prefetchPositionWithDeadline(innerView, innerPosition, deadlineNs);
+ }
+ } finally {
+ Trace.endSection();
+ }
+ }
+ }
+
+ private void flushTaskWithDeadline(Task task, long deadlineNs) {
+ long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs;
+ RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view,
+ task.position, taskDeadlineNs);
+ if (holder != null && holder.mNestedRecyclerView != null) {
+ prefetchInnerRecyclerViewWithDeadline(holder.mNestedRecyclerView.get(), deadlineNs);
+ }
+ }
+
+ private void flushTasksWithDeadline(long deadlineNs) {
+ for (int i = 0; i < mTasks.size(); i++) {
+ final Task task = mTasks.get(i);
+ if (task.view == null) {
+ break; // done with populated tasks
+ }
+ flushTaskWithDeadline(task, deadlineNs);
+ task.clear();
+ }
+ }
+
+ void prefetch(long deadlineNs) {
+ buildTaskList();
+ flushTasksWithDeadline(deadlineNs);
+ }
+
+ @Override
+ public void run() {
+ try {
+ Trace.beginSection(RecyclerView.TRACE_PREFETCH_TAG);
+
+ if (mRecyclerViews.isEmpty()) {
+ // abort - no work to do
+ return;
+ }
+
+ // Query last vsync so we can predict next one. Note that drawing time not yet
+ // valid in animation/input callbacks, so query it here to be safe.
+ long lastFrameVsyncNs = TimeUnit.MILLISECONDS.toNanos(
+ mRecyclerViews.get(0).getDrawingTime());
+ if (lastFrameVsyncNs == 0) {
+ // abort - couldn't get last vsync for estimating next
+ return;
+ }
+
+ // TODO: consider rebasing deadline if frame was already dropped due to long UI work.
+ // Next frame will still wait for VSYNC, so we can still use the gap if it exists.
+ long nextFrameNs = lastFrameVsyncNs + mFrameIntervalNs;
+
+ prefetch(nextFrameNs);
+
+ // TODO: consider rescheduling self, if there's more work to do
+ } finally {
+ mPostTimeNs = 0;
+ Trace.endSection();
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/LinearLayoutManager.java b/core/java/com/android/internal/widget/LinearLayoutManager.java
new file mode 100644
index 0000000..d82c746
--- /dev/null
+++ b/core/java/com/android/internal/widget/LinearLayoutManager.java
@@ -0,0 +1,2398 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import static com.android.internal.widget.RecyclerView.NO_POSITION;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.internal.widget.RecyclerView.LayoutParams;
+import com.android.internal.widget.helper.ItemTouchHelper;
+
+import java.util.List;
+
+/**
+ * A {@link com.android.internal.widget.RecyclerView.LayoutManager} implementation which provides
+ * similar functionality to {@link android.widget.ListView}.
+ */
+public class LinearLayoutManager extends RecyclerView.LayoutManager implements
+ ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
+
+ private static final String TAG = "LinearLayoutManager";
+
+ static final boolean DEBUG = false;
+
+ public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
+
+ public static final int VERTICAL = OrientationHelper.VERTICAL;
+
+ public static final int INVALID_OFFSET = Integer.MIN_VALUE;
+
+
+ /**
+ * While trying to find next view to focus, LayoutManager will not try to scroll more
+ * than this factor times the total space of the list. If layout is vertical, total space is the
+ * height minus padding, if layout is horizontal, total space is the width minus padding.
+ */
+ private static final float MAX_SCROLL_FACTOR = 1 / 3f;
+
+
+ /**
+ * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}
+ */
+ int mOrientation;
+
+ /**
+ * Helper class that keeps temporary layout state.
+ * It does not keep state after layout is complete but we still keep a reference to re-use
+ * the same object.
+ */
+ private LayoutState mLayoutState;
+
+ /**
+ * Many calculations are made depending on orientation. To keep it clean, this interface
+ * helps {@link LinearLayoutManager} make those decisions.
+ * Based on {@link #mOrientation}, an implementation is lazily created in
+ * {@link #ensureLayoutState} method.
+ */
+ OrientationHelper mOrientationHelper;
+
+ /**
+ * We need to track this so that we can ignore current position when it changes.
+ */
+ private boolean mLastStackFromEnd;
+
+
+ /**
+ * Defines if layout should be calculated from end to start.
+ *
+ * @see #mShouldReverseLayout
+ */
+ private boolean mReverseLayout = false;
+
+ /**
+ * This keeps the final value for how LayoutManager should start laying out views.
+ * It is calculated by checking {@link #getReverseLayout()} and View's layout direction.
+ * {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)} is run.
+ */
+ boolean mShouldReverseLayout = false;
+
+ /**
+ * Works the same way as {@link android.widget.AbsListView#setStackFromBottom(boolean)} and
+ * it supports both orientations.
+ * see {@link android.widget.AbsListView#setStackFromBottom(boolean)}
+ */
+ private boolean mStackFromEnd = false;
+
+ /**
+ * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}.
+ * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}
+ */
+ private boolean mSmoothScrollbarEnabled = true;
+
+ /**
+ * When LayoutManager needs to scroll to a position, it sets this variable and requests a
+ * layout which will check this variable and re-layout accordingly.
+ */
+ int mPendingScrollPosition = NO_POSITION;
+
+ /**
+ * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
+ * called.
+ */
+ int mPendingScrollPositionOffset = INVALID_OFFSET;
+
+ private boolean mRecycleChildrenOnDetach;
+
+ SavedState mPendingSavedState = null;
+
+ /**
+ * Re-used variable to keep anchor information on re-layout.
+ * Anchor position and coordinate defines the reference point for LLM while doing a layout.
+ * */
+ final AnchorInfo mAnchorInfo = new AnchorInfo();
+
+ /**
+ * Stashed to avoid allocation, currently only used in #fill()
+ */
+ private final LayoutChunkResult mLayoutChunkResult = new LayoutChunkResult();
+
+ /**
+ * Number of items to prefetch when first coming on screen with new data.
+ */
+ private int mInitialItemPrefetchCount = 2;
+
+ /**
+ * Creates a vertical LinearLayoutManager
+ *
+ * @param context Current context, will be used to access resources.
+ */
+ public LinearLayoutManager(Context context) {
+ this(context, VERTICAL, false);
+ }
+
+ /**
+ * @param context Current context, will be used to access resources.
+ * @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link
+ * #VERTICAL}.
+ * @param reverseLayout When set to true, layouts from end to start.
+ */
+ public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
+ setOrientation(orientation);
+ setReverseLayout(reverseLayout);
+ setAutoMeasureEnabled(true);
+ }
+
+ /**
+ * Constructor used when layout manager is set in XML by RecyclerView attribute
+ * "layoutManager". Defaults to vertical orientation.
+ *
+ * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation
+ * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout
+ * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd
+ */
+ public LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
+ setOrientation(properties.orientation);
+ setReverseLayout(properties.reverseLayout);
+ setStackFromEnd(properties.stackFromEnd);
+ setAutoMeasureEnabled(true);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+
+ /**
+ * Returns whether LayoutManager will recycle its children when it is detached from
+ * RecyclerView.
+ *
+ * @return true if LayoutManager will recycle its children when it is detached from
+ * RecyclerView.
+ */
+ public boolean getRecycleChildrenOnDetach() {
+ return mRecycleChildrenOnDetach;
+ }
+
+ /**
+ * Set whether LayoutManager will recycle its children when it is detached from
+ * RecyclerView.
+ * <p>
+ * If you are using a {@link RecyclerView.RecycledViewPool}, it might be a good idea to set
+ * this flag to <code>true</code> so that views will be available to other RecyclerViews
+ * immediately.
+ * <p>
+ * Note that, setting this flag will result in a performance drop if RecyclerView
+ * is restored.
+ *
+ * @param recycleChildrenOnDetach Whether children should be recycled in detach or not.
+ */
+ public void setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
+ mRecycleChildrenOnDetach = recycleChildrenOnDetach;
+ }
+
+ @Override
+ public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
+ super.onDetachedFromWindow(view, recycler);
+ if (mRecycleChildrenOnDetach) {
+ removeAndRecycleAllViews(recycler);
+ recycler.clear();
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ if (getChildCount() > 0) {
+ event.setFromIndex(findFirstVisibleItemPosition());
+ event.setToIndex(findLastVisibleItemPosition());
+ }
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ if (mPendingSavedState != null) {
+ return new SavedState(mPendingSavedState);
+ }
+ SavedState state = new SavedState();
+ if (getChildCount() > 0) {
+ ensureLayoutState();
+ boolean didLayoutFromEnd = mLastStackFromEnd ^ mShouldReverseLayout;
+ state.mAnchorLayoutFromEnd = didLayoutFromEnd;
+ if (didLayoutFromEnd) {
+ final View refChild = getChildClosestToEnd();
+ state.mAnchorOffset = mOrientationHelper.getEndAfterPadding()
+ - mOrientationHelper.getDecoratedEnd(refChild);
+ state.mAnchorPosition = getPosition(refChild);
+ } else {
+ final View refChild = getChildClosestToStart();
+ state.mAnchorPosition = getPosition(refChild);
+ state.mAnchorOffset = mOrientationHelper.getDecoratedStart(refChild)
+ - mOrientationHelper.getStartAfterPadding();
+ }
+ } else {
+ state.invalidateAnchor();
+ }
+ return state;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ if (state instanceof SavedState) {
+ mPendingSavedState = (SavedState) state;
+ requestLayout();
+ if (DEBUG) {
+ Log.d(TAG, "loaded saved state");
+ }
+ } else if (DEBUG) {
+ Log.d(TAG, "invalid saved state class");
+ }
+ }
+
+ /**
+ * @return true if {@link #getOrientation()} is {@link #HORIZONTAL}
+ */
+ @Override
+ public boolean canScrollHorizontally() {
+ return mOrientation == HORIZONTAL;
+ }
+
+ /**
+ * @return true if {@link #getOrientation()} is {@link #VERTICAL}
+ */
+ @Override
+ public boolean canScrollVertically() {
+ return mOrientation == VERTICAL;
+ }
+
+ /**
+ * Compatibility support for {@link android.widget.AbsListView#setStackFromBottom(boolean)}
+ */
+ public void setStackFromEnd(boolean stackFromEnd) {
+ assertNotInLayoutOrScroll(null);
+ if (mStackFromEnd == stackFromEnd) {
+ return;
+ }
+ mStackFromEnd = stackFromEnd;
+ requestLayout();
+ }
+
+ public boolean getStackFromEnd() {
+ return mStackFromEnd;
+ }
+
+ /**
+ * Returns the current orientation of the layout.
+ *
+ * @return Current orientation, either {@link #HORIZONTAL} or {@link #VERTICAL}
+ * @see #setOrientation(int)
+ */
+ public int getOrientation() {
+ return mOrientation;
+ }
+
+ /**
+ * Sets the orientation of the layout. {@link com.android.internal.widget.LinearLayoutManager}
+ * will do its best to keep scroll position.
+ *
+ * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
+ */
+ public void setOrientation(int orientation) {
+ if (orientation != HORIZONTAL && orientation != VERTICAL) {
+ throw new IllegalArgumentException("invalid orientation:" + orientation);
+ }
+ assertNotInLayoutOrScroll(null);
+ if (orientation == mOrientation) {
+ return;
+ }
+ mOrientation = orientation;
+ mOrientationHelper = null;
+ requestLayout();
+ }
+
+ /**
+ * Calculates the view layout order. (e.g. from end to start or start to end)
+ * RTL layout support is applied automatically. So if layout is RTL and
+ * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
+ */
+ private void resolveShouldLayoutReverse() {
+ // A == B is the same result, but we rather keep it readable
+ if (mOrientation == VERTICAL || !isLayoutRTL()) {
+ mShouldReverseLayout = mReverseLayout;
+ } else {
+ mShouldReverseLayout = !mReverseLayout;
+ }
+ }
+
+ /**
+ * Returns if views are laid out from the opposite direction of the layout.
+ *
+ * @return If layout is reversed or not.
+ * @see #setReverseLayout(boolean)
+ */
+ public boolean getReverseLayout() {
+ return mReverseLayout;
+ }
+
+ /**
+ * Used to reverse item traversal and layout order.
+ * This behaves similar to the layout change for RTL views. When set to true, first item is
+ * laid out at the end of the UI, second item is laid out before it etc.
+ *
+ * For horizontal layouts, it depends on the layout direction.
+ * When set to true, If {@link com.android.internal.widget.RecyclerView} is LTR, than it will
+ * layout from RTL, if {@link com.android.internal.widget.RecyclerView}} is RTL, it will layout
+ * from LTR.
+ *
+ * If you are looking for the exact same behavior of
+ * {@link android.widget.AbsListView#setStackFromBottom(boolean)}, use
+ * {@link #setStackFromEnd(boolean)}
+ */
+ public void setReverseLayout(boolean reverseLayout) {
+ assertNotInLayoutOrScroll(null);
+ if (reverseLayout == mReverseLayout) {
+ return;
+ }
+ mReverseLayout = reverseLayout;
+ requestLayout();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View findViewByPosition(int position) {
+ final int childCount = getChildCount();
+ if (childCount == 0) {
+ return null;
+ }
+ final int firstChild = getPosition(getChildAt(0));
+ final int viewPosition = position - firstChild;
+ if (viewPosition >= 0 && viewPosition < childCount) {
+ final View child = getChildAt(viewPosition);
+ if (getPosition(child) == position) {
+ return child; // in pre-layout, this may not match
+ }
+ }
+ // fallback to traversal. This might be necessary in pre-layout.
+ return super.findViewByPosition(position);
+ }
+
+ /**
+ * <p>Returns the amount of extra space that should be laid out by LayoutManager.</p>
+ *
+ * <p>By default, {@link com.android.internal.widget.LinearLayoutManager} lays out 1 extra page
+ * of items while smooth scrolling and 0 otherwise. You can override this method to implement
+ * your custom layout pre-cache logic.</p>
+ *
+ * <p><strong>Note:</strong>Laying out invisible elements generally comes with significant
+ * performance cost. It's typically only desirable in places like smooth scrolling to an unknown
+ * location, where 1) the extra content helps LinearLayoutManager know in advance when its
+ * target is approaching, so it can decelerate early and smoothly and 2) while motion is
+ * continuous.</p>
+ *
+ * <p>Extending the extra layout space is especially expensive if done while the user may change
+ * scrolling direction. Changing direction will cause the extra layout space to swap to the
+ * opposite side of the viewport, incurring many rebinds/recycles, unless the cache is large
+ * enough to handle it.</p>
+ *
+ * @return The extra space that should be laid out (in pixels).
+ */
+ protected int getExtraLayoutSpace(RecyclerView.State state) {
+ if (state.hasTargetScrollPosition()) {
+ return mOrientationHelper.getTotalSpace();
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
+ int position) {
+ LinearSmoothScroller linearSmoothScroller =
+ new LinearSmoothScroller(recyclerView.getContext());
+ linearSmoothScroller.setTargetPosition(position);
+ startSmoothScroll(linearSmoothScroller);
+ }
+
+ @Override
+ public PointF computeScrollVectorForPosition(int targetPosition) {
+ if (getChildCount() == 0) {
+ return null;
+ }
+ final int firstChildPos = getPosition(getChildAt(0));
+ final int direction = targetPosition < firstChildPos != mShouldReverseLayout ? -1 : 1;
+ if (mOrientation == HORIZONTAL) {
+ return new PointF(direction, 0);
+ } else {
+ return new PointF(0, direction);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ // layout algorithm:
+ // 1) by checking children and other variables, find an anchor coordinate and an anchor
+ // item position.
+ // 2) fill towards start, stacking from bottom
+ // 3) fill towards end, stacking from top
+ // 4) scroll to fulfill requirements like stack from bottom.
+ // create layout state
+ if (DEBUG) {
+ Log.d(TAG, "is pre layout:" + state.isPreLayout());
+ }
+ if (mPendingSavedState != null || mPendingScrollPosition != NO_POSITION) {
+ if (state.getItemCount() == 0) {
+ removeAndRecycleAllViews(recycler);
+ return;
+ }
+ }
+ if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
+ mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
+ }
+
+ ensureLayoutState();
+ mLayoutState.mRecycle = false;
+ // resolve layout direction
+ resolveShouldLayoutReverse();
+
+ if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
+ || mPendingSavedState != null) {
+ mAnchorInfo.reset();
+ mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
+ // calculate anchor position and coordinate
+ updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
+ mAnchorInfo.mValid = true;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Anchor info:" + mAnchorInfo);
+ }
+
+ // LLM may decide to layout items for "extra" pixels to account for scrolling target,
+ // caching or predictive animations.
+ int extraForStart;
+ int extraForEnd;
+ final int extra = getExtraLayoutSpace(state);
+ // If the previous scroll delta was less than zero, the extra space should be laid out
+ // at the start. Otherwise, it should be at the end.
+ if (mLayoutState.mLastScrollDelta >= 0) {
+ extraForEnd = extra;
+ extraForStart = 0;
+ } else {
+ extraForStart = extra;
+ extraForEnd = 0;
+ }
+ extraForStart += mOrientationHelper.getStartAfterPadding();
+ extraForEnd += mOrientationHelper.getEndPadding();
+ if (state.isPreLayout() && mPendingScrollPosition != NO_POSITION
+ && mPendingScrollPositionOffset != INVALID_OFFSET) {
+ // if the child is visible and we are going to move it around, we should layout
+ // extra items in the opposite direction to make sure new items animate nicely
+ // instead of just fading in
+ final View existing = findViewByPosition(mPendingScrollPosition);
+ if (existing != null) {
+ final int current;
+ final int upcomingOffset;
+ if (mShouldReverseLayout) {
+ current = mOrientationHelper.getEndAfterPadding()
+ - mOrientationHelper.getDecoratedEnd(existing);
+ upcomingOffset = current - mPendingScrollPositionOffset;
+ } else {
+ current = mOrientationHelper.getDecoratedStart(existing)
+ - mOrientationHelper.getStartAfterPadding();
+ upcomingOffset = mPendingScrollPositionOffset - current;
+ }
+ if (upcomingOffset > 0) {
+ extraForStart += upcomingOffset;
+ } else {
+ extraForEnd -= upcomingOffset;
+ }
+ }
+ }
+ int startOffset;
+ int endOffset;
+ final int firstLayoutDirection;
+ if (mAnchorInfo.mLayoutFromEnd) {
+ firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
+ : LayoutState.ITEM_DIRECTION_HEAD;
+ } else {
+ firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
+ : LayoutState.ITEM_DIRECTION_TAIL;
+ }
+
+ onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
+ detachAndScrapAttachedViews(recycler);
+ mLayoutState.mInfinite = resolveIsInfinite();
+ mLayoutState.mIsPreLayout = state.isPreLayout();
+ if (mAnchorInfo.mLayoutFromEnd) {
+ // fill towards start
+ updateLayoutStateToFillStart(mAnchorInfo);
+ mLayoutState.mExtra = extraForStart;
+ fill(recycler, mLayoutState, state, false);
+ startOffset = mLayoutState.mOffset;
+ final int firstElement = mLayoutState.mCurrentPosition;
+ if (mLayoutState.mAvailable > 0) {
+ extraForEnd += mLayoutState.mAvailable;
+ }
+ // fill towards end
+ updateLayoutStateToFillEnd(mAnchorInfo);
+ mLayoutState.mExtra = extraForEnd;
+ mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
+ fill(recycler, mLayoutState, state, false);
+ endOffset = mLayoutState.mOffset;
+
+ if (mLayoutState.mAvailable > 0) {
+ // end could not consume all. add more items towards start
+ extraForStart = mLayoutState.mAvailable;
+ updateLayoutStateToFillStart(firstElement, startOffset);
+ mLayoutState.mExtra = extraForStart;
+ fill(recycler, mLayoutState, state, false);
+ startOffset = mLayoutState.mOffset;
+ }
+ } else {
+ // fill towards end
+ updateLayoutStateToFillEnd(mAnchorInfo);
+ mLayoutState.mExtra = extraForEnd;
+ fill(recycler, mLayoutState, state, false);
+ endOffset = mLayoutState.mOffset;
+ final int lastElement = mLayoutState.mCurrentPosition;
+ if (mLayoutState.mAvailable > 0) {
+ extraForStart += mLayoutState.mAvailable;
+ }
+ // fill towards start
+ updateLayoutStateToFillStart(mAnchorInfo);
+ mLayoutState.mExtra = extraForStart;
+ mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
+ fill(recycler, mLayoutState, state, false);
+ startOffset = mLayoutState.mOffset;
+
+ if (mLayoutState.mAvailable > 0) {
+ extraForEnd = mLayoutState.mAvailable;
+ // start could not consume all it should. add more items towards end
+ updateLayoutStateToFillEnd(lastElement, endOffset);
+ mLayoutState.mExtra = extraForEnd;
+ fill(recycler, mLayoutState, state, false);
+ endOffset = mLayoutState.mOffset;
+ }
+ }
+
+ // changes may cause gaps on the UI, try to fix them.
+ // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have
+ // changed
+ if (getChildCount() > 0) {
+ // because layout from end may be changed by scroll to position
+ // we re-calculate it.
+ // find which side we should check for gaps.
+ if (mShouldReverseLayout ^ mStackFromEnd) {
+ int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
+ startOffset += fixOffset;
+ endOffset += fixOffset;
+ fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
+ startOffset += fixOffset;
+ endOffset += fixOffset;
+ } else {
+ int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
+ startOffset += fixOffset;
+ endOffset += fixOffset;
+ fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
+ startOffset += fixOffset;
+ endOffset += fixOffset;
+ }
+ }
+ layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
+ if (!state.isPreLayout()) {
+ mOrientationHelper.onLayoutComplete();
+ } else {
+ mAnchorInfo.reset();
+ }
+ mLastStackFromEnd = mStackFromEnd;
+ if (DEBUG) {
+ validateChildOrder();
+ }
+ }
+
+ @Override
+ public void onLayoutCompleted(RecyclerView.State state) {
+ super.onLayoutCompleted(state);
+ mPendingSavedState = null; // we don't need this anymore
+ mPendingScrollPosition = NO_POSITION;
+ mPendingScrollPositionOffset = INVALID_OFFSET;
+ mAnchorInfo.reset();
+ }
+
+ /**
+ * Method called when Anchor position is decided. Extending class can setup accordingly or
+ * even update anchor info if necessary.
+ * @param recycler The recycler for the layout
+ * @param state The layout state
+ * @param anchorInfo The mutable POJO that keeps the position and offset.
+ * @param firstLayoutItemDirection The direction of the first layout filling in terms of adapter
+ * indices.
+ */
+ void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state,
+ AnchorInfo anchorInfo, int firstLayoutItemDirection) {
+ }
+
+ /**
+ * If necessary, layouts new items for predictive animations
+ */
+ private void layoutForPredictiveAnimations(RecyclerView.Recycler recycler,
+ RecyclerView.State state, int startOffset, int endOffset) {
+ // If there are scrap children that we did not layout, we need to find where they did go
+ // and layout them accordingly so that animations can work as expected.
+ // This case may happen if new views are added or an existing view expands and pushes
+ // another view out of bounds.
+ if (!state.willRunPredictiveAnimations() || getChildCount() == 0 || state.isPreLayout()
+ || !supportsPredictiveItemAnimations()) {
+ return;
+ }
+ // to make the logic simpler, we calculate the size of children and call fill.
+ int scrapExtraStart = 0, scrapExtraEnd = 0;
+ final List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList();
+ final int scrapSize = scrapList.size();
+ final int firstChildPos = getPosition(getChildAt(0));
+ for (int i = 0; i < scrapSize; i++) {
+ RecyclerView.ViewHolder scrap = scrapList.get(i);
+ if (scrap.isRemoved()) {
+ continue;
+ }
+ final int position = scrap.getLayoutPosition();
+ final int direction = position < firstChildPos != mShouldReverseLayout
+ ? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END;
+ if (direction == LayoutState.LAYOUT_START) {
+ scrapExtraStart += mOrientationHelper.getDecoratedMeasurement(scrap.itemView);
+ } else {
+ scrapExtraEnd += mOrientationHelper.getDecoratedMeasurement(scrap.itemView);
+ }
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "for unused scrap, decided to add " + scrapExtraStart
+ + " towards start and " + scrapExtraEnd + " towards end");
+ }
+ mLayoutState.mScrapList = scrapList;
+ if (scrapExtraStart > 0) {
+ View anchor = getChildClosestToStart();
+ updateLayoutStateToFillStart(getPosition(anchor), startOffset);
+ mLayoutState.mExtra = scrapExtraStart;
+ mLayoutState.mAvailable = 0;
+ mLayoutState.assignPositionFromScrapList();
+ fill(recycler, mLayoutState, state, false);
+ }
+
+ if (scrapExtraEnd > 0) {
+ View anchor = getChildClosestToEnd();
+ updateLayoutStateToFillEnd(getPosition(anchor), endOffset);
+ mLayoutState.mExtra = scrapExtraEnd;
+ mLayoutState.mAvailable = 0;
+ mLayoutState.assignPositionFromScrapList();
+ fill(recycler, mLayoutState, state, false);
+ }
+ mLayoutState.mScrapList = null;
+ }
+
+ private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
+ AnchorInfo anchorInfo) {
+ if (updateAnchorFromPendingData(state, anchorInfo)) {
+ if (DEBUG) {
+ Log.d(TAG, "updated anchor info from pending information");
+ }
+ return;
+ }
+
+ if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
+ if (DEBUG) {
+ Log.d(TAG, "updated anchor info from existing children");
+ }
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "deciding anchor info for fresh state");
+ }
+ anchorInfo.assignCoordinateFromPadding();
+ anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
+ }
+
+ /**
+ * Finds an anchor child from existing Views. Most of the time, this is the view closest to
+ * start or end that has a valid position (e.g. not removed).
+ * <p>
+ * If a child has focus, it is given priority.
+ */
+ private boolean updateAnchorFromChildren(RecyclerView.Recycler recycler,
+ RecyclerView.State state, AnchorInfo anchorInfo) {
+ if (getChildCount() == 0) {
+ return false;
+ }
+ final View focused = getFocusedChild();
+ if (focused != null && anchorInfo.isViewValidAsAnchor(focused, state)) {
+ anchorInfo.assignFromViewAndKeepVisibleRect(focused);
+ return true;
+ }
+ if (mLastStackFromEnd != mStackFromEnd) {
+ return false;
+ }
+ View referenceChild = anchorInfo.mLayoutFromEnd
+ ? findReferenceChildClosestToEnd(recycler, state)
+ : findReferenceChildClosestToStart(recycler, state);
+ if (referenceChild != null) {
+ anchorInfo.assignFromView(referenceChild);
+ // If all visible views are removed in 1 pass, reference child might be out of bounds.
+ // If that is the case, offset it back to 0 so that we use these pre-layout children.
+ if (!state.isPreLayout() && supportsPredictiveItemAnimations()) {
+ // validate this child is at least partially visible. if not, offset it to start
+ final boolean notVisible =
+ mOrientationHelper.getDecoratedStart(referenceChild) >= mOrientationHelper
+ .getEndAfterPadding()
+ || mOrientationHelper.getDecoratedEnd(referenceChild)
+ < mOrientationHelper.getStartAfterPadding();
+ if (notVisible) {
+ anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd
+ ? mOrientationHelper.getEndAfterPadding()
+ : mOrientationHelper.getStartAfterPadding();
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * If there is a pending scroll position or saved states, updates the anchor info from that
+ * data and returns true
+ */
+ private boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) {
+ if (state.isPreLayout() || mPendingScrollPosition == NO_POSITION) {
+ return false;
+ }
+ // validate scroll position
+ if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) {
+ mPendingScrollPosition = NO_POSITION;
+ mPendingScrollPositionOffset = INVALID_OFFSET;
+ if (DEBUG) {
+ Log.e(TAG, "ignoring invalid scroll position " + mPendingScrollPosition);
+ }
+ return false;
+ }
+
+ // if child is visible, try to make it a reference child and ensure it is fully visible.
+ // if child is not visible, align it depending on its virtual position.
+ anchorInfo.mPosition = mPendingScrollPosition;
+ if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
+ // Anchor offset depends on how that child was laid out. Here, we update it
+ // according to our current view bounds
+ anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
+ if (anchorInfo.mLayoutFromEnd) {
+ anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding()
+ - mPendingSavedState.mAnchorOffset;
+ } else {
+ anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding()
+ + mPendingSavedState.mAnchorOffset;
+ }
+ return true;
+ }
+
+ if (mPendingScrollPositionOffset == INVALID_OFFSET) {
+ View child = findViewByPosition(mPendingScrollPosition);
+ if (child != null) {
+ final int childSize = mOrientationHelper.getDecoratedMeasurement(child);
+ if (childSize > mOrientationHelper.getTotalSpace()) {
+ // item does not fit. fix depending on layout direction
+ anchorInfo.assignCoordinateFromPadding();
+ return true;
+ }
+ final int startGap = mOrientationHelper.getDecoratedStart(child)
+ - mOrientationHelper.getStartAfterPadding();
+ if (startGap < 0) {
+ anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding();
+ anchorInfo.mLayoutFromEnd = false;
+ return true;
+ }
+ final int endGap = mOrientationHelper.getEndAfterPadding()
+ - mOrientationHelper.getDecoratedEnd(child);
+ if (endGap < 0) {
+ anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding();
+ anchorInfo.mLayoutFromEnd = true;
+ return true;
+ }
+ anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd
+ ? (mOrientationHelper.getDecoratedEnd(child) + mOrientationHelper
+ .getTotalSpaceChange())
+ : mOrientationHelper.getDecoratedStart(child);
+ } else { // item is not visible.
+ if (getChildCount() > 0) {
+ // get position of any child, does not matter
+ int pos = getPosition(getChildAt(0));
+ anchorInfo.mLayoutFromEnd = mPendingScrollPosition < pos
+ == mShouldReverseLayout;
+ }
+ anchorInfo.assignCoordinateFromPadding();
+ }
+ return true;
+ }
+ // override layout from end values for consistency
+ anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
+ // if this changes, we should update prepareForDrop as well
+ if (mShouldReverseLayout) {
+ anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding()
+ - mPendingScrollPositionOffset;
+ } else {
+ anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding()
+ + mPendingScrollPositionOffset;
+ }
+ return true;
+ }
+
+ /**
+ * @return The final offset amount for children
+ */
+ private int fixLayoutEndGap(int endOffset, RecyclerView.Recycler recycler,
+ RecyclerView.State state, boolean canOffsetChildren) {
+ int gap = mOrientationHelper.getEndAfterPadding() - endOffset;
+ int fixOffset = 0;
+ if (gap > 0) {
+ fixOffset = -scrollBy(-gap, recycler, state);
+ } else {
+ return 0; // nothing to fix
+ }
+ // move offset according to scroll amount
+ endOffset += fixOffset;
+ if (canOffsetChildren) {
+ // re-calculate gap, see if we could fix it
+ gap = mOrientationHelper.getEndAfterPadding() - endOffset;
+ if (gap > 0) {
+ mOrientationHelper.offsetChildren(gap);
+ return gap + fixOffset;
+ }
+ }
+ return fixOffset;
+ }
+
+ /**
+ * @return The final offset amount for children
+ */
+ private int fixLayoutStartGap(int startOffset, RecyclerView.Recycler recycler,
+ RecyclerView.State state, boolean canOffsetChildren) {
+ int gap = startOffset - mOrientationHelper.getStartAfterPadding();
+ int fixOffset = 0;
+ if (gap > 0) {
+ // check if we should fix this gap.
+ fixOffset = -scrollBy(gap, recycler, state);
+ } else {
+ return 0; // nothing to fix
+ }
+ startOffset += fixOffset;
+ if (canOffsetChildren) {
+ // re-calculate gap, see if we could fix it
+ gap = startOffset - mOrientationHelper.getStartAfterPadding();
+ if (gap > 0) {
+ mOrientationHelper.offsetChildren(-gap);
+ return fixOffset - gap;
+ }
+ }
+ return fixOffset;
+ }
+
+ private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) {
+ updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate);
+ }
+
+ private void updateLayoutStateToFillEnd(int itemPosition, int offset) {
+ mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset;
+ mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
+ LayoutState.ITEM_DIRECTION_TAIL;
+ mLayoutState.mCurrentPosition = itemPosition;
+ mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END;
+ mLayoutState.mOffset = offset;
+ mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
+ }
+
+ private void updateLayoutStateToFillStart(AnchorInfo anchorInfo) {
+ updateLayoutStateToFillStart(anchorInfo.mPosition, anchorInfo.mCoordinate);
+ }
+
+ private void updateLayoutStateToFillStart(int itemPosition, int offset) {
+ mLayoutState.mAvailable = offset - mOrientationHelper.getStartAfterPadding();
+ mLayoutState.mCurrentPosition = itemPosition;
+ mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
+ LayoutState.ITEM_DIRECTION_HEAD;
+ mLayoutState.mLayoutDirection = LayoutState.LAYOUT_START;
+ mLayoutState.mOffset = offset;
+ mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
+
+ }
+
+ protected boolean isLayoutRTL() {
+ return getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ }
+
+ void ensureLayoutState() {
+ if (mLayoutState == null) {
+ mLayoutState = createLayoutState();
+ }
+ if (mOrientationHelper == null) {
+ mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);
+ }
+ }
+
+ /**
+ * Test overrides this to plug some tracking and verification.
+ *
+ * @return A new LayoutState
+ */
+ LayoutState createLayoutState() {
+ return new LayoutState();
+ }
+
+ /**
+ * <p>Scroll the RecyclerView to make the position visible.</p>
+ *
+ * <p>RecyclerView will scroll the minimum amount that is necessary to make the
+ * target position visible. If you are looking for a similar behavior to
+ * {@link android.widget.ListView#setSelection(int)} or
+ * {@link android.widget.ListView#setSelectionFromTop(int, int)}, use
+ * {@link #scrollToPositionWithOffset(int, int)}.</p>
+ *
+ * <p>Note that scroll position change will not be reflected until the next layout call.</p>
+ *
+ * @param position Scroll to this adapter position
+ * @see #scrollToPositionWithOffset(int, int)
+ */
+ @Override
+ public void scrollToPosition(int position) {
+ mPendingScrollPosition = position;
+ mPendingScrollPositionOffset = INVALID_OFFSET;
+ if (mPendingSavedState != null) {
+ mPendingSavedState.invalidateAnchor();
+ }
+ requestLayout();
+ }
+
+ /**
+ * Scroll to the specified adapter position with the given offset from resolved layout
+ * start. Resolved layout start depends on {@link #getReverseLayout()},
+ * {@link View#getLayoutDirection()} and {@link #getStackFromEnd()}.
+ * <p>
+ * For example, if layout is {@link #VERTICAL} and {@link #getStackFromEnd()} is true, calling
+ * <code>scrollToPositionWithOffset(10, 20)</code> will layout such that
+ * <code>item[10]</code>'s bottom is 20 pixels above the RecyclerView's bottom.
+ * <p>
+ * Note that scroll position change will not be reflected until the next layout call.
+ * <p>
+ * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.
+ *
+ * @param position Index (starting at 0) of the reference item.
+ * @param offset The distance (in pixels) between the start edge of the item view and
+ * start edge of the RecyclerView.
+ * @see #setReverseLayout(boolean)
+ * @see #scrollToPosition(int)
+ */
+ public void scrollToPositionWithOffset(int position, int offset) {
+ mPendingScrollPosition = position;
+ mPendingScrollPositionOffset = offset;
+ if (mPendingSavedState != null) {
+ mPendingSavedState.invalidateAnchor();
+ }
+ requestLayout();
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ if (mOrientation == VERTICAL) {
+ return 0;
+ }
+ return scrollBy(dx, recycler, state);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ if (mOrientation == HORIZONTAL) {
+ return 0;
+ }
+ return scrollBy(dy, recycler, state);
+ }
+
+ @Override
+ public int computeHorizontalScrollOffset(RecyclerView.State state) {
+ return computeScrollOffset(state);
+ }
+
+ @Override
+ public int computeVerticalScrollOffset(RecyclerView.State state) {
+ return computeScrollOffset(state);
+ }
+
+ @Override
+ public int computeHorizontalScrollExtent(RecyclerView.State state) {
+ return computeScrollExtent(state);
+ }
+
+ @Override
+ public int computeVerticalScrollExtent(RecyclerView.State state) {
+ return computeScrollExtent(state);
+ }
+
+ @Override
+ public int computeHorizontalScrollRange(RecyclerView.State state) {
+ return computeScrollRange(state);
+ }
+
+ @Override
+ public int computeVerticalScrollRange(RecyclerView.State state) {
+ return computeScrollRange(state);
+ }
+
+ private int computeScrollOffset(RecyclerView.State state) {
+ if (getChildCount() == 0) {
+ return 0;
+ }
+ ensureLayoutState();
+ return ScrollbarHelper.computeScrollOffset(state, mOrientationHelper,
+ findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
+ findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
+ this, mSmoothScrollbarEnabled, mShouldReverseLayout);
+ }
+
+ private int computeScrollExtent(RecyclerView.State state) {
+ if (getChildCount() == 0) {
+ return 0;
+ }
+ ensureLayoutState();
+ return ScrollbarHelper.computeScrollExtent(state, mOrientationHelper,
+ findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
+ findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
+ this, mSmoothScrollbarEnabled);
+ }
+
+ private int computeScrollRange(RecyclerView.State state) {
+ if (getChildCount() == 0) {
+ return 0;
+ }
+ ensureLayoutState();
+ return ScrollbarHelper.computeScrollRange(state, mOrientationHelper,
+ findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
+ findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
+ this, mSmoothScrollbarEnabled);
+ }
+
+ /**
+ * When smooth scrollbar is enabled, the position and size of the scrollbar thumb is computed
+ * based on the number of visible pixels in the visible items. This however assumes that all
+ * list items have similar or equal widths or heights (depending on list orientation).
+ * If you use a list in which items have different dimensions, the scrollbar will change
+ * appearance as the user scrolls through the list. To avoid this issue, you need to disable
+ * this property.
+ *
+ * When smooth scrollbar is disabled, the position and size of the scrollbar thumb is based
+ * solely on the number of items in the adapter and the position of the visible items inside
+ * the adapter. This provides a stable scrollbar as the user navigates through a list of items
+ * with varying widths / heights.
+ *
+ * @param enabled Whether or not to enable smooth scrollbar.
+ *
+ * @see #setSmoothScrollbarEnabled(boolean)
+ */
+ public void setSmoothScrollbarEnabled(boolean enabled) {
+ mSmoothScrollbarEnabled = enabled;
+ }
+
+ /**
+ * Returns the current state of the smooth scrollbar feature. It is enabled by default.
+ *
+ * @return True if smooth scrollbar is enabled, false otherwise.
+ *
+ * @see #setSmoothScrollbarEnabled(boolean)
+ */
+ public boolean isSmoothScrollbarEnabled() {
+ return mSmoothScrollbarEnabled;
+ }
+
+ private void updateLayoutState(int layoutDirection, int requiredSpace,
+ boolean canUseExistingSpace, RecyclerView.State state) {
+ // If parent provides a hint, don't measure unlimited.
+ mLayoutState.mInfinite = resolveIsInfinite();
+ mLayoutState.mExtra = getExtraLayoutSpace(state);
+ mLayoutState.mLayoutDirection = layoutDirection;
+ int scrollingOffset;
+ if (layoutDirection == LayoutState.LAYOUT_END) {
+ mLayoutState.mExtra += mOrientationHelper.getEndPadding();
+ // get the first child in the direction we are going
+ final View child = getChildClosestToEnd();
+ // the direction in which we are traversing children
+ mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
+ : LayoutState.ITEM_DIRECTION_TAIL;
+ mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
+ mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
+ // calculate how much we can scroll without adding new children (independent of layout)
+ scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
+ - mOrientationHelper.getEndAfterPadding();
+
+ } else {
+ final View child = getChildClosestToStart();
+ mLayoutState.mExtra += mOrientationHelper.getStartAfterPadding();
+ mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
+ : LayoutState.ITEM_DIRECTION_HEAD;
+ mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
+ mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child);
+ scrollingOffset = -mOrientationHelper.getDecoratedStart(child)
+ + mOrientationHelper.getStartAfterPadding();
+ }
+ mLayoutState.mAvailable = requiredSpace;
+ if (canUseExistingSpace) {
+ mLayoutState.mAvailable -= scrollingOffset;
+ }
+ mLayoutState.mScrollingOffset = scrollingOffset;
+ }
+
+ boolean resolveIsInfinite() {
+ return mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED
+ && mOrientationHelper.getEnd() == 0;
+ }
+
+ void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState,
+ LayoutPrefetchRegistry layoutPrefetchRegistry) {
+ final int pos = layoutState.mCurrentPosition;
+ if (pos >= 0 && pos < state.getItemCount()) {
+ layoutPrefetchRegistry.addPosition(pos, layoutState.mScrollingOffset);
+ }
+ }
+
+ @Override
+ public void collectInitialPrefetchPositions(int adapterItemCount,
+ LayoutPrefetchRegistry layoutPrefetchRegistry) {
+ final boolean fromEnd;
+ final int anchorPos;
+ if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
+ // use restored state, since it hasn't been resolved yet
+ fromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
+ anchorPos = mPendingSavedState.mAnchorPosition;
+ } else {
+ resolveShouldLayoutReverse();
+ fromEnd = mShouldReverseLayout;
+ if (mPendingScrollPosition == NO_POSITION) {
+ anchorPos = fromEnd ? adapterItemCount - 1 : 0;
+ } else {
+ anchorPos = mPendingScrollPosition;
+ }
+ }
+
+ final int direction = fromEnd
+ ? LayoutState.ITEM_DIRECTION_HEAD
+ : LayoutState.ITEM_DIRECTION_TAIL;
+ int targetPos = anchorPos;
+ for (int i = 0; i < mInitialItemPrefetchCount; i++) {
+ if (targetPos >= 0 && targetPos < adapterItemCount) {
+ layoutPrefetchRegistry.addPosition(targetPos, 0);
+ } else {
+ break; // no more to prefetch
+ }
+ targetPos += direction;
+ }
+ }
+
+ /**
+ * Sets the number of items to prefetch in
+ * {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, which defines
+ * how many inner items should be prefetched when this LayoutManager's RecyclerView
+ * is nested inside another RecyclerView.
+ *
+ * <p>Set this value to the number of items this inner LayoutManager will display when it is
+ * first scrolled into the viewport. RecyclerView will attempt to prefetch that number of items
+ * so they are ready, avoiding jank as the inner RecyclerView is scrolled into the viewport.</p>
+ *
+ * <p>For example, take a vertically scrolling RecyclerView with horizontally scrolling inner
+ * RecyclerViews. The rows always have 4 items visible in them (or 5 if not aligned). Passing
+ * <code>4</code> to this method for each inner RecyclerView's LinearLayoutManager will enable
+ * RecyclerView's prefetching feature to do create/bind work for 4 views within a row early,
+ * before it is scrolled on screen, instead of just the default 2.</p>
+ *
+ * <p>Calling this method does nothing unless the LayoutManager is in a RecyclerView
+ * nested in another RecyclerView.</p>
+ *
+ * <p class="note"><strong>Note:</strong> Setting this value to be larger than the number of
+ * views that will be visible in this view can incur unnecessary bind work, and an increase to
+ * the number of Views created and in active use.</p>
+ *
+ * @param itemCount Number of items to prefetch
+ *
+ * @see #isItemPrefetchEnabled()
+ * @see #getInitialItemPrefetchCount()
+ * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)
+ */
+ public void setInitialPrefetchItemCount(int itemCount) {
+ mInitialItemPrefetchCount = itemCount;
+ }
+
+ /**
+ * Gets the number of items to prefetch in
+ * {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, which defines
+ * how many inner items should be prefetched when this LayoutManager's RecyclerView
+ * is nested inside another RecyclerView.
+ *
+ * @see #isItemPrefetchEnabled()
+ * @see #setInitialPrefetchItemCount(int)
+ * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)
+ *
+ * @return number of items to prefetch.
+ */
+ public int getInitialItemPrefetchCount() {
+ return mInitialItemPrefetchCount;
+ }
+
+ @Override
+ public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
+ LayoutPrefetchRegistry layoutPrefetchRegistry) {
+ int delta = (mOrientation == HORIZONTAL) ? dx : dy;
+ if (getChildCount() == 0 || delta == 0) {
+ // can't support this scroll, so don't bother prefetching
+ return;
+ }
+
+ final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
+ final int absDy = Math.abs(delta);
+ updateLayoutState(layoutDirection, absDy, true, state);
+ collectPrefetchPositionsForLayoutState(state, mLayoutState, layoutPrefetchRegistry);
+ }
+
+ int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
+ if (getChildCount() == 0 || dy == 0) {
+ return 0;
+ }
+ mLayoutState.mRecycle = true;
+ ensureLayoutState();
+ final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
+ final int absDy = Math.abs(dy);
+ updateLayoutState(layoutDirection, absDy, true, state);
+ final int consumed = mLayoutState.mScrollingOffset
+ + fill(recycler, mLayoutState, state, false);
+ if (consumed < 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Don't have any more elements to scroll");
+ }
+ return 0;
+ }
+ final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
+ mOrientationHelper.offsetChildren(-scrolled);
+ if (DEBUG) {
+ Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
+ }
+ mLayoutState.mLastScrollDelta = scrolled;
+ return scrolled;
+ }
+
+ @Override
+ public void assertNotInLayoutOrScroll(String message) {
+ if (mPendingSavedState == null) {
+ super.assertNotInLayoutOrScroll(message);
+ }
+ }
+
+ /**
+ * Recycles children between given indices.
+ *
+ * @param startIndex inclusive
+ * @param endIndex exclusive
+ */
+ private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
+ if (startIndex == endIndex) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items");
+ }
+ if (endIndex > startIndex) {
+ for (int i = endIndex - 1; i >= startIndex; i--) {
+ removeAndRecycleViewAt(i, recycler);
+ }
+ } else {
+ for (int i = startIndex; i > endIndex; i--) {
+ removeAndRecycleViewAt(i, recycler);
+ }
+ }
+ }
+
+ /**
+ * Recycles views that went out of bounds after scrolling towards the end of the layout.
+ * <p>
+ * Checks both layout position and visible position to guarantee that the view is not visible.
+ *
+ * @param recycler Recycler instance of {@link com.android.internal.widget.RecyclerView}
+ * @param dt This can be used to add additional padding to the visible area. This is used
+ * to detect children that will go out of bounds after scrolling, without
+ * actually moving them.
+ */
+ private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
+ if (dt < 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Called recycle from start with a negative value. This might happen"
+ + " during layout changes but may be sign of a bug");
+ }
+ return;
+ }
+ // ignore padding, ViewGroup may not clip children.
+ final int limit = dt;
+ final int childCount = getChildCount();
+ if (mShouldReverseLayout) {
+ for (int i = childCount - 1; i >= 0; i--) {
+ View child = getChildAt(i);
+ if (mOrientationHelper.getDecoratedEnd(child) > limit
+ || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
+ // stop here
+ recycleChildren(recycler, childCount - 1, i);
+ return;
+ }
+ }
+ } else {
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (mOrientationHelper.getDecoratedEnd(child) > limit
+ || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
+ // stop here
+ recycleChildren(recycler, 0, i);
+ return;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Recycles views that went out of bounds after scrolling towards the start of the layout.
+ * <p>
+ * Checks both layout position and visible position to guarantee that the view is not visible.
+ *
+ * @param recycler Recycler instance of {@link com.android.internal.widget.RecyclerView}
+ * @param dt This can be used to add additional padding to the visible area. This is used
+ * to detect children that will go out of bounds after scrolling, without
+ * actually moving them.
+ */
+ private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) {
+ final int childCount = getChildCount();
+ if (dt < 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Called recycle from end with a negative value. This might happen"
+ + " during layout changes but may be sign of a bug");
+ }
+ return;
+ }
+ final int limit = mOrientationHelper.getEnd() - dt;
+ if (mShouldReverseLayout) {
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (mOrientationHelper.getDecoratedStart(child) < limit
+ || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
+ // stop here
+ recycleChildren(recycler, 0, i);
+ return;
+ }
+ }
+ } else {
+ for (int i = childCount - 1; i >= 0; i--) {
+ View child = getChildAt(i);
+ if (mOrientationHelper.getDecoratedStart(child) < limit
+ || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
+ // stop here
+ recycleChildren(recycler, childCount - 1, i);
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper method to call appropriate recycle method depending on current layout direction
+ *
+ * @param recycler Current recycler that is attached to RecyclerView
+ * @param layoutState Current layout state. Right now, this object does not change but
+ * we may consider moving it out of this view so passing around as a
+ * parameter for now, rather than accessing {@link #mLayoutState}
+ * @see #recycleViewsFromStart(com.android.internal.widget.RecyclerView.Recycler, int)
+ * @see #recycleViewsFromEnd(com.android.internal.widget.RecyclerView.Recycler, int)
+ * @see com.android.internal.widget.LinearLayoutManager.LayoutState#mLayoutDirection
+ */
+ private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
+ if (!layoutState.mRecycle || layoutState.mInfinite) {
+ return;
+ }
+ if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
+ recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
+ } else {
+ recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
+ }
+ }
+
+ /**
+ * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
+ * independent from the rest of the {@link com.android.internal.widget.LinearLayoutManager}
+ * and with little change, can be made publicly available as a helper class.
+ *
+ * @param recycler Current recycler that is attached to RecyclerView
+ * @param layoutState Configuration on how we should fill out the available space.
+ * @param state Context passed by the RecyclerView to control scroll steps.
+ * @param stopOnFocusable If true, filling stops in the first focusable new child
+ * @return Number of pixels that it added. Useful for scroll functions.
+ */
+ int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
+ RecyclerView.State state, boolean stopOnFocusable) {
+ // max offset we should set is mFastScroll + available
+ final int start = layoutState.mAvailable;
+ if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
+ // TODO ugly bug fix. should not happen
+ if (layoutState.mAvailable < 0) {
+ layoutState.mScrollingOffset += layoutState.mAvailable;
+ }
+ recycleByLayoutState(recycler, layoutState);
+ }
+ int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
+ LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
+ while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
+ layoutChunkResult.resetInternal();
+ layoutChunk(recycler, state, layoutState, layoutChunkResult);
+ if (layoutChunkResult.mFinished) {
+ break;
+ }
+ layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
+ /**
+ * Consume the available space if:
+ * * layoutChunk did not request to be ignored
+ * * OR we are laying out scrap children
+ * * OR we are not doing pre-layout
+ */
+ if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
+ || !state.isPreLayout()) {
+ layoutState.mAvailable -= layoutChunkResult.mConsumed;
+ // we keep a separate remaining space because mAvailable is important for recycling
+ remainingSpace -= layoutChunkResult.mConsumed;
+ }
+
+ if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
+ layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
+ if (layoutState.mAvailable < 0) {
+ layoutState.mScrollingOffset += layoutState.mAvailable;
+ }
+ recycleByLayoutState(recycler, layoutState);
+ }
+ if (stopOnFocusable && layoutChunkResult.mFocusable) {
+ break;
+ }
+ }
+ if (DEBUG) {
+ validateChildOrder();
+ }
+ return start - layoutState.mAvailable;
+ }
+
+ void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
+ LayoutState layoutState, LayoutChunkResult result) {
+ View view = layoutState.next(recycler);
+ if (view == null) {
+ if (DEBUG && layoutState.mScrapList == null) {
+ throw new RuntimeException("received null view when unexpected");
+ }
+ // if we are laying out views in scrap, this may return null which means there is
+ // no more items to layout.
+ result.mFinished = true;
+ return;
+ }
+ LayoutParams params = (LayoutParams) view.getLayoutParams();
+ if (layoutState.mScrapList == null) {
+ if (mShouldReverseLayout == (layoutState.mLayoutDirection
+ == LayoutState.LAYOUT_START)) {
+ addView(view);
+ } else {
+ addView(view, 0);
+ }
+ } else {
+ if (mShouldReverseLayout == (layoutState.mLayoutDirection
+ == LayoutState.LAYOUT_START)) {
+ addDisappearingView(view);
+ } else {
+ addDisappearingView(view, 0);
+ }
+ }
+ measureChildWithMargins(view, 0, 0);
+ result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
+ int left, top, right, bottom;
+ if (mOrientation == VERTICAL) {
+ if (isLayoutRTL()) {
+ right = getWidth() - getPaddingRight();
+ left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
+ } else {
+ left = getPaddingLeft();
+ right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
+ }
+ if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
+ bottom = layoutState.mOffset;
+ top = layoutState.mOffset - result.mConsumed;
+ } else {
+ top = layoutState.mOffset;
+ bottom = layoutState.mOffset + result.mConsumed;
+ }
+ } else {
+ top = getPaddingTop();
+ bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
+
+ if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
+ right = layoutState.mOffset;
+ left = layoutState.mOffset - result.mConsumed;
+ } else {
+ left = layoutState.mOffset;
+ right = layoutState.mOffset + result.mConsumed;
+ }
+ }
+ // We calculate everything with View's bounding box (which includes decor and margins)
+ // To calculate correct layout position, we subtract margins.
+ layoutDecoratedWithMargins(view, left, top, right, bottom);
+ if (DEBUG) {
+ Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
+ + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
+ + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
+ }
+ // Consume the available space if the view is not removed OR changed
+ if (params.isItemRemoved() || params.isItemChanged()) {
+ result.mIgnoreConsumed = true;
+ }
+ result.mFocusable = view.isFocusable();
+ }
+
+ @Override
+ boolean shouldMeasureTwice() {
+ return getHeightMode() != View.MeasureSpec.EXACTLY
+ && getWidthMode() != View.MeasureSpec.EXACTLY
+ && hasFlexibleChildInBothOrientations();
+ }
+
+ /**
+ * Converts a focusDirection to orientation.
+ *
+ * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+ * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+ * or 0 for not applicable
+ * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction
+ * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise.
+ */
+ int convertFocusDirectionToLayoutDirection(int focusDirection) {
+ switch (focusDirection) {
+ case View.FOCUS_BACKWARD:
+ if (mOrientation == VERTICAL) {
+ return LayoutState.LAYOUT_START;
+ } else if (isLayoutRTL()) {
+ return LayoutState.LAYOUT_END;
+ } else {
+ return LayoutState.LAYOUT_START;
+ }
+ case View.FOCUS_FORWARD:
+ if (mOrientation == VERTICAL) {
+ return LayoutState.LAYOUT_END;
+ } else if (isLayoutRTL()) {
+ return LayoutState.LAYOUT_START;
+ } else {
+ return LayoutState.LAYOUT_END;
+ }
+ case View.FOCUS_UP:
+ return mOrientation == VERTICAL ? LayoutState.LAYOUT_START
+ : LayoutState.INVALID_LAYOUT;
+ case View.FOCUS_DOWN:
+ return mOrientation == VERTICAL ? LayoutState.LAYOUT_END
+ : LayoutState.INVALID_LAYOUT;
+ case View.FOCUS_LEFT:
+ return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START
+ : LayoutState.INVALID_LAYOUT;
+ case View.FOCUS_RIGHT:
+ return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END
+ : LayoutState.INVALID_LAYOUT;
+ default:
+ if (DEBUG) {
+ Log.d(TAG, "Unknown focus request:" + focusDirection);
+ }
+ return LayoutState.INVALID_LAYOUT;
+ }
+
+ }
+
+ /**
+ * Convenience method to find the child closes to start. Caller should check it has enough
+ * children.
+ *
+ * @return The child closes to start of the layout from user's perspective.
+ */
+ private View getChildClosestToStart() {
+ return getChildAt(mShouldReverseLayout ? getChildCount() - 1 : 0);
+ }
+
+ /**
+ * Convenience method to find the child closes to end. Caller should check it has enough
+ * children.
+ *
+ * @return The child closes to end of the layout from user's perspective.
+ */
+ private View getChildClosestToEnd() {
+ return getChildAt(mShouldReverseLayout ? 0 : getChildCount() - 1);
+ }
+
+ /**
+ * Convenience method to find the visible child closes to start. Caller should check if it has
+ * enough children.
+ *
+ * @param completelyVisible Whether child should be completely visible or not
+ * @return The first visible child closest to start of the layout from user's perspective.
+ */
+ private View findFirstVisibleChildClosestToStart(boolean completelyVisible,
+ boolean acceptPartiallyVisible) {
+ if (mShouldReverseLayout) {
+ return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible,
+ acceptPartiallyVisible);
+ } else {
+ return findOneVisibleChild(0, getChildCount(), completelyVisible,
+ acceptPartiallyVisible);
+ }
+ }
+
+ /**
+ * Convenience method to find the visible child closes to end. Caller should check if it has
+ * enough children.
+ *
+ * @param completelyVisible Whether child should be completely visible or not
+ * @return The first visible child closest to end of the layout from user's perspective.
+ */
+ private View findFirstVisibleChildClosestToEnd(boolean completelyVisible,
+ boolean acceptPartiallyVisible) {
+ if (mShouldReverseLayout) {
+ return findOneVisibleChild(0, getChildCount(), completelyVisible,
+ acceptPartiallyVisible);
+ } else {
+ return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible,
+ acceptPartiallyVisible);
+ }
+ }
+
+
+ /**
+ * Among the children that are suitable to be considered as an anchor child, returns the one
+ * closest to the end of the layout.
+ * <p>
+ * Due to ambiguous adapter updates or children being removed, some children's positions may be
+ * invalid. This method is a best effort to find a position within adapter bounds if possible.
+ * <p>
+ * It also prioritizes children that are within the visible bounds.
+ * @return A View that can be used an an anchor View.
+ */
+ private View findReferenceChildClosestToEnd(RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ return mShouldReverseLayout ? findFirstReferenceChild(recycler, state) :
+ findLastReferenceChild(recycler, state);
+ }
+
+ /**
+ * Among the children that are suitable to be considered as an anchor child, returns the one
+ * closest to the start of the layout.
+ * <p>
+ * Due to ambiguous adapter updates or children being removed, some children's positions may be
+ * invalid. This method is a best effort to find a position within adapter bounds if possible.
+ * <p>
+ * It also prioritizes children that are within the visible bounds.
+ *
+ * @return A View that can be used an an anchor View.
+ */
+ private View findReferenceChildClosestToStart(RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ return mShouldReverseLayout ? findLastReferenceChild(recycler, state) :
+ findFirstReferenceChild(recycler, state);
+ }
+
+ private View findFirstReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ return findReferenceChild(recycler, state, 0, getChildCount(), state.getItemCount());
+ }
+
+ private View findLastReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ return findReferenceChild(recycler, state, getChildCount() - 1, -1, state.getItemCount());
+ }
+
+ // overridden by GridLayoutManager
+ View findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state,
+ int start, int end, int itemCount) {
+ ensureLayoutState();
+ View invalidMatch = null;
+ View outOfBoundsMatch = null;
+ final int boundsStart = mOrientationHelper.getStartAfterPadding();
+ final int boundsEnd = mOrientationHelper.getEndAfterPadding();
+ final int diff = end > start ? 1 : -1;
+ for (int i = start; i != end; i += diff) {
+ final View view = getChildAt(i);
+ final int position = getPosition(view);
+ if (position >= 0 && position < itemCount) {
+ if (((LayoutParams) view.getLayoutParams()).isItemRemoved()) {
+ if (invalidMatch == null) {
+ invalidMatch = view; // removed item, least preferred
+ }
+ } else if (mOrientationHelper.getDecoratedStart(view) >= boundsEnd
+ || mOrientationHelper.getDecoratedEnd(view) < boundsStart) {
+ if (outOfBoundsMatch == null) {
+ outOfBoundsMatch = view; // item is not visible, less preferred
+ }
+ } else {
+ return view;
+ }
+ }
+ }
+ return outOfBoundsMatch != null ? outOfBoundsMatch : invalidMatch;
+ }
+
+ /**
+ * Returns the adapter position of the first visible view. This position does not include
+ * adapter changes that were dispatched after the last layout pass.
+ * <p>
+ * Note that, this value is not affected by layout orientation or item order traversal.
+ * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
+ * not in the layout.
+ * <p>
+ * If RecyclerView has item decorators, they will be considered in calculations as well.
+ * <p>
+ * LayoutManager may pre-cache some views that are not necessarily visible. Those views
+ * are ignored in this method.
+ *
+ * @return The adapter position of the first visible item or {@link RecyclerView#NO_POSITION} if
+ * there aren't any visible items.
+ * @see #findFirstCompletelyVisibleItemPosition()
+ * @see #findLastVisibleItemPosition()
+ */
+ public int findFirstVisibleItemPosition() {
+ final View child = findOneVisibleChild(0, getChildCount(), false, true);
+ return child == null ? NO_POSITION : getPosition(child);
+ }
+
+ /**
+ * Returns the adapter position of the first fully visible view. This position does not include
+ * adapter changes that were dispatched after the last layout pass.
+ * <p>
+ * Note that bounds check is only performed in the current orientation. That means, if
+ * LayoutManager is horizontal, it will only check the view's left and right edges.
+ *
+ * @return The adapter position of the first fully visible item or
+ * {@link RecyclerView#NO_POSITION} if there aren't any visible items.
+ * @see #findFirstVisibleItemPosition()
+ * @see #findLastCompletelyVisibleItemPosition()
+ */
+ public int findFirstCompletelyVisibleItemPosition() {
+ final View child = findOneVisibleChild(0, getChildCount(), true, false);
+ return child == null ? NO_POSITION : getPosition(child);
+ }
+
+ /**
+ * Returns the adapter position of the last visible view. This position does not include
+ * adapter changes that were dispatched after the last layout pass.
+ * <p>
+ * Note that, this value is not affected by layout orientation or item order traversal.
+ * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
+ * not in the layout.
+ * <p>
+ * If RecyclerView has item decorators, they will be considered in calculations as well.
+ * <p>
+ * LayoutManager may pre-cache some views that are not necessarily visible. Those views
+ * are ignored in this method.
+ *
+ * @return The adapter position of the last visible view or {@link RecyclerView#NO_POSITION} if
+ * there aren't any visible items.
+ * @see #findLastCompletelyVisibleItemPosition()
+ * @see #findFirstVisibleItemPosition()
+ */
+ public int findLastVisibleItemPosition() {
+ final View child = findOneVisibleChild(getChildCount() - 1, -1, false, true);
+ return child == null ? NO_POSITION : getPosition(child);
+ }
+
+ /**
+ * Returns the adapter position of the last fully visible view. This position does not include
+ * adapter changes that were dispatched after the last layout pass.
+ * <p>
+ * Note that bounds check is only performed in the current orientation. That means, if
+ * LayoutManager is horizontal, it will only check the view's left and right edges.
+ *
+ * @return The adapter position of the last fully visible view or
+ * {@link RecyclerView#NO_POSITION} if there aren't any visible items.
+ * @see #findLastVisibleItemPosition()
+ * @see #findFirstCompletelyVisibleItemPosition()
+ */
+ public int findLastCompletelyVisibleItemPosition() {
+ final View child = findOneVisibleChild(getChildCount() - 1, -1, true, false);
+ return child == null ? NO_POSITION : getPosition(child);
+ }
+
+ View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible,
+ boolean acceptPartiallyVisible) {
+ ensureLayoutState();
+ final int start = mOrientationHelper.getStartAfterPadding();
+ final int end = mOrientationHelper.getEndAfterPadding();
+ final int next = toIndex > fromIndex ? 1 : -1;
+ View partiallyVisible = null;
+ for (int i = fromIndex; i != toIndex; i += next) {
+ final View child = getChildAt(i);
+ final int childStart = mOrientationHelper.getDecoratedStart(child);
+ final int childEnd = mOrientationHelper.getDecoratedEnd(child);
+ if (childStart < end && childEnd > start) {
+ if (completelyVisible) {
+ if (childStart >= start && childEnd <= end) {
+ return child;
+ } else if (acceptPartiallyVisible && partiallyVisible == null) {
+ partiallyVisible = child;
+ }
+ } else {
+ return child;
+ }
+ }
+ }
+ return partiallyVisible;
+ }
+
+ @Override
+ public View onFocusSearchFailed(View focused, int focusDirection,
+ RecyclerView.Recycler recycler, RecyclerView.State state) {
+ resolveShouldLayoutReverse();
+ if (getChildCount() == 0) {
+ return null;
+ }
+
+ final int layoutDir = convertFocusDirectionToLayoutDirection(focusDirection);
+ if (layoutDir == LayoutState.INVALID_LAYOUT) {
+ return null;
+ }
+ ensureLayoutState();
+ final View referenceChild;
+ if (layoutDir == LayoutState.LAYOUT_START) {
+ referenceChild = findReferenceChildClosestToStart(recycler, state);
+ } else {
+ referenceChild = findReferenceChildClosestToEnd(recycler, state);
+ }
+ if (referenceChild == null) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "Cannot find a child with a valid position to be used for focus search.");
+ }
+ return null;
+ }
+ ensureLayoutState();
+ final int maxScroll = (int) (MAX_SCROLL_FACTOR * mOrientationHelper.getTotalSpace());
+ updateLayoutState(layoutDir, maxScroll, false, state);
+ mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
+ mLayoutState.mRecycle = false;
+ fill(recycler, mLayoutState, state, true);
+ final View nextFocus;
+ if (layoutDir == LayoutState.LAYOUT_START) {
+ nextFocus = getChildClosestToStart();
+ } else {
+ nextFocus = getChildClosestToEnd();
+ }
+ if (nextFocus == referenceChild || !nextFocus.isFocusable()) {
+ return null;
+ }
+ return nextFocus;
+ }
+
+ /**
+ * Used for debugging.
+ * Logs the internal representation of children to default logger.
+ */
+ private void logChildren() {
+ Log.d(TAG, "internal representation of views on the screen");
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ Log.d(TAG, "item " + getPosition(child) + ", coord:"
+ + mOrientationHelper.getDecoratedStart(child));
+ }
+ Log.d(TAG, "==============");
+ }
+
+ /**
+ * Used for debugging.
+ * Validates that child views are laid out in correct order. This is important because rest of
+ * the algorithm relies on this constraint.
+ *
+ * In default layout, child 0 should be closest to screen position 0 and last child should be
+ * closest to position WIDTH or HEIGHT.
+ * In reverse layout, last child should be closes to screen position 0 and first child should
+ * be closest to position WIDTH or HEIGHT
+ */
+ void validateChildOrder() {
+ Log.d(TAG, "validating child count " + getChildCount());
+ if (getChildCount() < 1) {
+ return;
+ }
+ int lastPos = getPosition(getChildAt(0));
+ int lastScreenLoc = mOrientationHelper.getDecoratedStart(getChildAt(0));
+ if (mShouldReverseLayout) {
+ for (int i = 1; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ int pos = getPosition(child);
+ int screenLoc = mOrientationHelper.getDecoratedStart(child);
+ if (pos < lastPos) {
+ logChildren();
+ throw new RuntimeException("detected invalid position. loc invalid? "
+ + (screenLoc < lastScreenLoc));
+ }
+ if (screenLoc > lastScreenLoc) {
+ logChildren();
+ throw new RuntimeException("detected invalid location");
+ }
+ }
+ } else {
+ for (int i = 1; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ int pos = getPosition(child);
+ int screenLoc = mOrientationHelper.getDecoratedStart(child);
+ if (pos < lastPos) {
+ logChildren();
+ throw new RuntimeException("detected invalid position. loc invalid? "
+ + (screenLoc < lastScreenLoc));
+ }
+ if (screenLoc < lastScreenLoc) {
+ logChildren();
+ throw new RuntimeException("detected invalid location");
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean supportsPredictiveItemAnimations() {
+ return mPendingSavedState == null && mLastStackFromEnd == mStackFromEnd;
+ }
+
+ /**
+ * @hide This method should be called by ItemTouchHelper only.
+ */
+ @Override
+ public void prepareForDrop(View view, View target, int x, int y) {
+ assertNotInLayoutOrScroll("Cannot drop a view during a scroll or layout calculation");
+ ensureLayoutState();
+ resolveShouldLayoutReverse();
+ final int myPos = getPosition(view);
+ final int targetPos = getPosition(target);
+ final int dropDirection = myPos < targetPos ? LayoutState.ITEM_DIRECTION_TAIL
+ : LayoutState.ITEM_DIRECTION_HEAD;
+ if (mShouldReverseLayout) {
+ if (dropDirection == LayoutState.ITEM_DIRECTION_TAIL) {
+ scrollToPositionWithOffset(targetPos,
+ mOrientationHelper.getEndAfterPadding()
+ - (mOrientationHelper.getDecoratedStart(target)
+ + mOrientationHelper.getDecoratedMeasurement(view)));
+ } else {
+ scrollToPositionWithOffset(targetPos,
+ mOrientationHelper.getEndAfterPadding()
+ - mOrientationHelper.getDecoratedEnd(target));
+ }
+ } else {
+ if (dropDirection == LayoutState.ITEM_DIRECTION_HEAD) {
+ scrollToPositionWithOffset(targetPos, mOrientationHelper.getDecoratedStart(target));
+ } else {
+ scrollToPositionWithOffset(targetPos,
+ mOrientationHelper.getDecoratedEnd(target)
+ - mOrientationHelper.getDecoratedMeasurement(view));
+ }
+ }
+ }
+
+ /**
+ * Helper class that keeps temporary state while {LayoutManager} is filling out the empty
+ * space.
+ */
+ static class LayoutState {
+
+ static final String TAG = "LLM#LayoutState";
+
+ static final int LAYOUT_START = -1;
+
+ static final int LAYOUT_END = 1;
+
+ static final int INVALID_LAYOUT = Integer.MIN_VALUE;
+
+ static final int ITEM_DIRECTION_HEAD = -1;
+
+ static final int ITEM_DIRECTION_TAIL = 1;
+
+ static final int SCROLLING_OFFSET_NaN = Integer.MIN_VALUE;
+
+ /**
+ * We may not want to recycle children in some cases (e.g. layout)
+ */
+ boolean mRecycle = true;
+
+ /**
+ * Pixel offset where layout should start
+ */
+ int mOffset;
+
+ /**
+ * Number of pixels that we should fill, in the layout direction.
+ */
+ int mAvailable;
+
+ /**
+ * Current position on the adapter to get the next item.
+ */
+ int mCurrentPosition;
+
+ /**
+ * Defines the direction in which the data adapter is traversed.
+ * Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
+ */
+ int mItemDirection;
+
+ /**
+ * Defines the direction in which the layout is filled.
+ * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
+ */
+ int mLayoutDirection;
+
+ /**
+ * Used when LayoutState is constructed in a scrolling state.
+ * It should be set the amount of scrolling we can make without creating a new view.
+ * Settings this is required for efficient view recycling.
+ */
+ int mScrollingOffset;
+
+ /**
+ * Used if you want to pre-layout items that are not yet visible.
+ * The difference with {@link #mAvailable} is that, when recycling, distance laid out for
+ * {@link #mExtra} is not considered to avoid recycling visible children.
+ */
+ int mExtra = 0;
+
+ /**
+ * Equal to {@link RecyclerView.State#isPreLayout()}. When consuming scrap, if this value
+ * is set to true, we skip removed views since they should not be laid out in post layout
+ * step.
+ */
+ boolean mIsPreLayout = false;
+
+ /**
+ * The most recent {@link #scrollBy(int, RecyclerView.Recycler, RecyclerView.State)}
+ * amount.
+ */
+ int mLastScrollDelta;
+
+ /**
+ * When LLM needs to layout particular views, it sets this list in which case, LayoutState
+ * will only return views from this list and return null if it cannot find an item.
+ */
+ List<RecyclerView.ViewHolder> mScrapList = null;
+
+ /**
+ * Used when there is no limit in how many views can be laid out.
+ */
+ boolean mInfinite;
+
+ /**
+ * @return true if there are more items in the data adapter
+ */
+ boolean hasMore(RecyclerView.State state) {
+ return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
+ }
+
+ /**
+ * Gets the view for the next element that we should layout.
+ * Also updates current item index to the next item, based on {@link #mItemDirection}
+ *
+ * @return The next element that we should layout.
+ */
+ View next(RecyclerView.Recycler recycler) {
+ if (mScrapList != null) {
+ return nextViewFromScrapList();
+ }
+ final View view = recycler.getViewForPosition(mCurrentPosition);
+ mCurrentPosition += mItemDirection;
+ return view;
+ }
+
+ /**
+ * Returns the next item from the scrap list.
+ * <p>
+ * Upon finding a valid VH, sets current item position to VH.itemPosition + mItemDirection
+ *
+ * @return View if an item in the current position or direction exists if not null.
+ */
+ private View nextViewFromScrapList() {
+ final int size = mScrapList.size();
+ for (int i = 0; i < size; i++) {
+ final View view = mScrapList.get(i).itemView;
+ final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ if (lp.isItemRemoved()) {
+ continue;
+ }
+ if (mCurrentPosition == lp.getViewLayoutPosition()) {
+ assignPositionFromScrapList(view);
+ return view;
+ }
+ }
+ return null;
+ }
+
+ public void assignPositionFromScrapList() {
+ assignPositionFromScrapList(null);
+ }
+
+ public void assignPositionFromScrapList(View ignore) {
+ final View closest = nextViewInLimitedList(ignore);
+ if (closest == null) {
+ mCurrentPosition = NO_POSITION;
+ } else {
+ mCurrentPosition = ((LayoutParams) closest.getLayoutParams())
+ .getViewLayoutPosition();
+ }
+ }
+
+ public View nextViewInLimitedList(View ignore) {
+ int size = mScrapList.size();
+ View closest = null;
+ int closestDistance = Integer.MAX_VALUE;
+ if (DEBUG && mIsPreLayout) {
+ throw new IllegalStateException("Scrap list cannot be used in pre layout");
+ }
+ for (int i = 0; i < size; i++) {
+ View view = mScrapList.get(i).itemView;
+ final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ if (view == ignore || lp.isItemRemoved()) {
+ continue;
+ }
+ final int distance = (lp.getViewLayoutPosition() - mCurrentPosition)
+ * mItemDirection;
+ if (distance < 0) {
+ continue; // item is not in current direction
+ }
+ if (distance < closestDistance) {
+ closest = view;
+ closestDistance = distance;
+ if (distance == 0) {
+ break;
+ }
+ }
+ }
+ return closest;
+ }
+
+ void log() {
+ Log.d(TAG, "avail:" + mAvailable + ", ind:" + mCurrentPosition + ", dir:"
+ + mItemDirection + ", offset:" + mOffset + ", layoutDir:" + mLayoutDirection);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static class SavedState implements Parcelable {
+
+ int mAnchorPosition;
+
+ int mAnchorOffset;
+
+ boolean mAnchorLayoutFromEnd;
+
+ public SavedState() {
+
+ }
+
+ SavedState(Parcel in) {
+ mAnchorPosition = in.readInt();
+ mAnchorOffset = in.readInt();
+ mAnchorLayoutFromEnd = in.readInt() == 1;
+ }
+
+ public SavedState(SavedState other) {
+ mAnchorPosition = other.mAnchorPosition;
+ mAnchorOffset = other.mAnchorOffset;
+ mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd;
+ }
+
+ boolean hasValidAnchor() {
+ return mAnchorPosition >= 0;
+ }
+
+ void invalidateAnchor() {
+ mAnchorPosition = NO_POSITION;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mAnchorPosition);
+ dest.writeInt(mAnchorOffset);
+ dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ /**
+ * Simple data class to keep Anchor information
+ */
+ class AnchorInfo {
+ int mPosition;
+ int mCoordinate;
+ boolean mLayoutFromEnd;
+ boolean mValid;
+
+ AnchorInfo() {
+ reset();
+ }
+
+ void reset() {
+ mPosition = NO_POSITION;
+ mCoordinate = INVALID_OFFSET;
+ mLayoutFromEnd = false;
+ mValid = false;
+ }
+
+ /**
+ * assigns anchor coordinate from the RecyclerView's padding depending on current
+ * layoutFromEnd value
+ */
+ void assignCoordinateFromPadding() {
+ mCoordinate = mLayoutFromEnd
+ ? mOrientationHelper.getEndAfterPadding()
+ : mOrientationHelper.getStartAfterPadding();
+ }
+
+ @Override
+ public String toString() {
+ return "AnchorInfo{"
+ + "mPosition=" + mPosition
+ + ", mCoordinate=" + mCoordinate
+ + ", mLayoutFromEnd=" + mLayoutFromEnd
+ + ", mValid=" + mValid
+ + '}';
+ }
+
+ boolean isViewValidAsAnchor(View child, RecyclerView.State state) {
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ return !lp.isItemRemoved() && lp.getViewLayoutPosition() >= 0
+ && lp.getViewLayoutPosition() < state.getItemCount();
+ }
+
+ public void assignFromViewAndKeepVisibleRect(View child) {
+ final int spaceChange = mOrientationHelper.getTotalSpaceChange();
+ if (spaceChange >= 0) {
+ assignFromView(child);
+ return;
+ }
+ mPosition = getPosition(child);
+ if (mLayoutFromEnd) {
+ final int prevLayoutEnd = mOrientationHelper.getEndAfterPadding() - spaceChange;
+ final int childEnd = mOrientationHelper.getDecoratedEnd(child);
+ final int previousEndMargin = prevLayoutEnd - childEnd;
+ mCoordinate = mOrientationHelper.getEndAfterPadding() - previousEndMargin;
+ // ensure we did not push child's top out of bounds because of this
+ if (previousEndMargin > 0) { // we have room to shift bottom if necessary
+ final int childSize = mOrientationHelper.getDecoratedMeasurement(child);
+ final int estimatedChildStart = mCoordinate - childSize;
+ final int layoutStart = mOrientationHelper.getStartAfterPadding();
+ final int previousStartMargin = mOrientationHelper.getDecoratedStart(child)
+ - layoutStart;
+ final int startReference = layoutStart + Math.min(previousStartMargin, 0);
+ final int startMargin = estimatedChildStart - startReference;
+ if (startMargin < 0) {
+ // offset to make top visible but not too much
+ mCoordinate += Math.min(previousEndMargin, -startMargin);
+ }
+ }
+ } else {
+ final int childStart = mOrientationHelper.getDecoratedStart(child);
+ final int startMargin = childStart - mOrientationHelper.getStartAfterPadding();
+ mCoordinate = childStart;
+ if (startMargin > 0) { // we have room to fix end as well
+ final int estimatedEnd = childStart
+ + mOrientationHelper.getDecoratedMeasurement(child);
+ final int previousLayoutEnd = mOrientationHelper.getEndAfterPadding()
+ - spaceChange;
+ final int previousEndMargin = previousLayoutEnd
+ - mOrientationHelper.getDecoratedEnd(child);
+ final int endReference = mOrientationHelper.getEndAfterPadding()
+ - Math.min(0, previousEndMargin);
+ final int endMargin = endReference - estimatedEnd;
+ if (endMargin < 0) {
+ mCoordinate -= Math.min(startMargin, -endMargin);
+ }
+ }
+ }
+ }
+
+ public void assignFromView(View child) {
+ if (mLayoutFromEnd) {
+ mCoordinate = mOrientationHelper.getDecoratedEnd(child)
+ + mOrientationHelper.getTotalSpaceChange();
+ } else {
+ mCoordinate = mOrientationHelper.getDecoratedStart(child);
+ }
+
+ mPosition = getPosition(child);
+ }
+ }
+
+ protected static class LayoutChunkResult {
+ public int mConsumed;
+ public boolean mFinished;
+ public boolean mIgnoreConsumed;
+ public boolean mFocusable;
+
+ void resetInternal() {
+ mConsumed = 0;
+ mFinished = false;
+ mIgnoreConsumed = false;
+ mFocusable = false;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/LinearSmoothScroller.java b/core/java/com/android/internal/widget/LinearSmoothScroller.java
new file mode 100644
index 0000000..d024f21
--- /dev/null
+++ b/core/java/com/android/internal/widget/LinearSmoothScroller.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.PointF;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+
+/**
+ * {@link RecyclerView.SmoothScroller} implementation which uses a {@link LinearInterpolator} until
+ * the target position becomes a child of the RecyclerView and then uses a
+ * {@link DecelerateInterpolator} to slowly approach to target position.
+ * <p>
+ * If the {@link RecyclerView.LayoutManager} you are using does not implement the
+ * {@link RecyclerView.SmoothScroller.ScrollVectorProvider} interface, then you must override the
+ * {@link #computeScrollVectorForPosition(int)} method. All the LayoutManagers bundled with
+ * the support library implement this interface.
+ */
+public class LinearSmoothScroller extends RecyclerView.SmoothScroller {
+
+ private static final String TAG = "LinearSmoothScroller";
+
+ private static final boolean DEBUG = false;
+
+ private static final float MILLISECONDS_PER_INCH = 25f;
+
+ private static final int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;
+
+ /**
+ * Align child view's left or top with parent view's left or top
+ *
+ * @see #calculateDtToFit(int, int, int, int, int)
+ * @see #calculateDxToMakeVisible(android.view.View, int)
+ * @see #calculateDyToMakeVisible(android.view.View, int)
+ */
+ public static final int SNAP_TO_START = -1;
+
+ /**
+ * Align child view's right or bottom with parent view's right or bottom
+ *
+ * @see #calculateDtToFit(int, int, int, int, int)
+ * @see #calculateDxToMakeVisible(android.view.View, int)
+ * @see #calculateDyToMakeVisible(android.view.View, int)
+ */
+ public static final int SNAP_TO_END = 1;
+
+ /**
+ * <p>Decides if the child should be snapped from start or end, depending on where it
+ * currently is in relation to its parent.</p>
+ * <p>For instance, if the view is virtually on the left of RecyclerView, using
+ * {@code SNAP_TO_ANY} is the same as using {@code SNAP_TO_START}</p>
+ *
+ * @see #calculateDtToFit(int, int, int, int, int)
+ * @see #calculateDxToMakeVisible(android.view.View, int)
+ * @see #calculateDyToMakeVisible(android.view.View, int)
+ */
+ public static final int SNAP_TO_ANY = 0;
+
+ // Trigger a scroll to a further distance than TARGET_SEEK_SCROLL_DISTANCE_PX so that if target
+ // view is not laid out until interim target position is reached, we can detect the case before
+ // scrolling slows down and reschedule another interim target scroll
+ private static final float TARGET_SEEK_EXTRA_SCROLL_RATIO = 1.2f;
+
+ protected final LinearInterpolator mLinearInterpolator = new LinearInterpolator();
+
+ protected final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
+
+ protected PointF mTargetVector;
+
+ private final float MILLISECONDS_PER_PX;
+
+ // Temporary variables to keep track of the interim scroll target. These values do not
+ // point to a real item position, rather point to an estimated location pixels.
+ protected int mInterimTargetDx = 0, mInterimTargetDy = 0;
+
+ public LinearSmoothScroller(Context context) {
+ MILLISECONDS_PER_PX = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onStart() {
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
+ final int dx = calculateDxToMakeVisible(targetView, getHorizontalSnapPreference());
+ final int dy = calculateDyToMakeVisible(targetView, getVerticalSnapPreference());
+ final int distance = (int) Math.sqrt(dx * dx + dy * dy);
+ final int time = calculateTimeForDeceleration(distance);
+ if (time > 0) {
+ action.update(-dx, -dy, time, mDecelerateInterpolator);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onSeekTargetStep(int dx, int dy, RecyclerView.State state, Action action) {
+ if (getChildCount() == 0) {
+ stop();
+ return;
+ }
+ //noinspection PointlessBooleanExpression
+ if (DEBUG && mTargetVector != null
+ && ((mTargetVector.x * dx < 0 || mTargetVector.y * dy < 0))) {
+ throw new IllegalStateException("Scroll happened in the opposite direction"
+ + " of the target. Some calculations are wrong");
+ }
+ mInterimTargetDx = clampApplyScroll(mInterimTargetDx, dx);
+ mInterimTargetDy = clampApplyScroll(mInterimTargetDy, dy);
+
+ if (mInterimTargetDx == 0 && mInterimTargetDy == 0) {
+ updateActionForInterimTarget(action);
+ } // everything is valid, keep going
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onStop() {
+ mInterimTargetDx = mInterimTargetDy = 0;
+ mTargetVector = null;
+ }
+
+ /**
+ * Calculates the scroll speed.
+ *
+ * @param displayMetrics DisplayMetrics to be used for real dimension calculations
+ * @return The time (in ms) it should take for each pixel. For instance, if returned value is
+ * 2 ms, it means scrolling 1000 pixels with LinearInterpolation should take 2 seconds.
+ */
+ protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
+ return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
+ }
+
+ /**
+ * <p>Calculates the time for deceleration so that transition from LinearInterpolator to
+ * DecelerateInterpolator looks smooth.</p>
+ *
+ * @param dx Distance to scroll
+ * @return Time for DecelerateInterpolator to smoothly traverse the distance when transitioning
+ * from LinearInterpolation
+ */
+ protected int calculateTimeForDeceleration(int dx) {
+ // we want to cover same area with the linear interpolator for the first 10% of the
+ // interpolation. After that, deceleration will take control.
+ // area under curve (1-(1-x)^2) can be calculated as (1 - x/3) * x * x
+ // which gives 0.100028 when x = .3356
+ // this is why we divide linear scrolling time with .3356
+ return (int) Math.ceil(calculateTimeForScrolling(dx) / .3356);
+ }
+
+ /**
+ * Calculates the time it should take to scroll the given distance (in pixels)
+ *
+ * @param dx Distance in pixels that we want to scroll
+ * @return Time in milliseconds
+ * @see #calculateSpeedPerPixel(android.util.DisplayMetrics)
+ */
+ protected int calculateTimeForScrolling(int dx) {
+ // In a case where dx is very small, rounding may return 0 although dx > 0.
+ // To avoid that issue, ceil the result so that if dx > 0, we'll always return positive
+ // time.
+ return (int) Math.ceil(Math.abs(dx) * MILLISECONDS_PER_PX);
+ }
+
+ /**
+ * When scrolling towards a child view, this method defines whether we should align the left
+ * or the right edge of the child with the parent RecyclerView.
+ *
+ * @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
+ * @see #SNAP_TO_START
+ * @see #SNAP_TO_END
+ * @see #SNAP_TO_ANY
+ */
+ protected int getHorizontalSnapPreference() {
+ return mTargetVector == null || mTargetVector.x == 0 ? SNAP_TO_ANY :
+ mTargetVector.x > 0 ? SNAP_TO_END : SNAP_TO_START;
+ }
+
+ /**
+ * When scrolling towards a child view, this method defines whether we should align the top
+ * or the bottom edge of the child with the parent RecyclerView.
+ *
+ * @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
+ * @see #SNAP_TO_START
+ * @see #SNAP_TO_END
+ * @see #SNAP_TO_ANY
+ */
+ protected int getVerticalSnapPreference() {
+ return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :
+ mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;
+ }
+
+ /**
+ * When the target scroll position is not a child of the RecyclerView, this method calculates
+ * a direction vector towards that child and triggers a smooth scroll.
+ *
+ * @see #computeScrollVectorForPosition(int)
+ */
+ protected void updateActionForInterimTarget(Action action) {
+ // find an interim target position
+ PointF scrollVector = computeScrollVectorForPosition(getTargetPosition());
+ if (scrollVector == null || (scrollVector.x == 0 && scrollVector.y == 0)) {
+ final int target = getTargetPosition();
+ action.jumpTo(target);
+ stop();
+ return;
+ }
+ normalize(scrollVector);
+ mTargetVector = scrollVector;
+
+ mInterimTargetDx = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.x);
+ mInterimTargetDy = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.y);
+ final int time = calculateTimeForScrolling(TARGET_SEEK_SCROLL_DISTANCE_PX);
+ // To avoid UI hiccups, trigger a smooth scroll to a distance little further than the
+ // interim target. Since we track the distance travelled in onSeekTargetStep callback, it
+ // won't actually scroll more than what we need.
+ action.update((int) (mInterimTargetDx * TARGET_SEEK_EXTRA_SCROLL_RATIO),
+ (int) (mInterimTargetDy * TARGET_SEEK_EXTRA_SCROLL_RATIO),
+ (int) (time * TARGET_SEEK_EXTRA_SCROLL_RATIO), mLinearInterpolator);
+ }
+
+ private int clampApplyScroll(int tmpDt, int dt) {
+ final int before = tmpDt;
+ tmpDt -= dt;
+ if (before * tmpDt <= 0) { // changed sign, reached 0 or was 0, reset
+ return 0;
+ }
+ return tmpDt;
+ }
+
+ /**
+ * Helper method for {@link #calculateDxToMakeVisible(android.view.View, int)} and
+ * {@link #calculateDyToMakeVisible(android.view.View, int)}
+ */
+ public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int
+ snapPreference) {
+ switch (snapPreference) {
+ case SNAP_TO_START:
+ return boxStart - viewStart;
+ case SNAP_TO_END:
+ return boxEnd - viewEnd;
+ case SNAP_TO_ANY:
+ final int dtStart = boxStart - viewStart;
+ if (dtStart > 0) {
+ return dtStart;
+ }
+ final int dtEnd = boxEnd - viewEnd;
+ if (dtEnd < 0) {
+ return dtEnd;
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("snap preference should be one of the"
+ + " constants defined in SmoothScroller, starting with SNAP_");
+ }
+ return 0;
+ }
+
+ /**
+ * Calculates the vertical scroll amount necessary to make the given view fully visible
+ * inside the RecyclerView.
+ *
+ * @param view The view which we want to make fully visible
+ * @param snapPreference The edge which the view should snap to when entering the visible
+ * area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or
+ * {@link #SNAP_TO_ANY}.
+ * @return The vertical scroll amount necessary to make the view visible with the given
+ * snap preference.
+ */
+ public int calculateDyToMakeVisible(View view, int snapPreference) {
+ final RecyclerView.LayoutManager layoutManager = getLayoutManager();
+ if (layoutManager == null || !layoutManager.canScrollVertically()) {
+ return 0;
+ }
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ final int top = layoutManager.getDecoratedTop(view) - params.topMargin;
+ final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin;
+ final int start = layoutManager.getPaddingTop();
+ final int end = layoutManager.getHeight() - layoutManager.getPaddingBottom();
+ return calculateDtToFit(top, bottom, start, end, snapPreference);
+ }
+
+ /**
+ * Calculates the horizontal scroll amount necessary to make the given view fully visible
+ * inside the RecyclerView.
+ *
+ * @param view The view which we want to make fully visible
+ * @param snapPreference The edge which the view should snap to when entering the visible
+ * area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or
+ * {@link #SNAP_TO_END}
+ * @return The vertical scroll amount necessary to make the view visible with the given
+ * snap preference.
+ */
+ public int calculateDxToMakeVisible(View view, int snapPreference) {
+ final RecyclerView.LayoutManager layoutManager = getLayoutManager();
+ if (layoutManager == null || !layoutManager.canScrollHorizontally()) {
+ return 0;
+ }
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ final int left = layoutManager.getDecoratedLeft(view) - params.leftMargin;
+ final int right = layoutManager.getDecoratedRight(view) + params.rightMargin;
+ final int start = layoutManager.getPaddingLeft();
+ final int end = layoutManager.getWidth() - layoutManager.getPaddingRight();
+ return calculateDtToFit(left, right, start, end, snapPreference);
+ }
+
+ /**
+ * Compute the scroll vector for a given target position.
+ * <p>
+ * This method can return null if the layout manager cannot calculate a scroll vector
+ * for the given position (e.g. it has no current scroll position).
+ *
+ * @param targetPosition the position to which the scroller is scrolling
+ *
+ * @return the scroll vector for a given target position
+ */
+ @Nullable
+ public PointF computeScrollVectorForPosition(int targetPosition) {
+ RecyclerView.LayoutManager layoutManager = getLayoutManager();
+ if (layoutManager instanceof ScrollVectorProvider) {
+ return ((ScrollVectorProvider) layoutManager)
+ .computeScrollVectorForPosition(targetPosition);
+ }
+ Log.w(TAG, "You should override computeScrollVectorForPosition when the LayoutManager"
+ + " does not implement " + ScrollVectorProvider.class.getCanonicalName());
+ return null;
+ }
+}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index a29882b4..ece5540 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1314,7 +1314,7 @@
}
private boolean isDoNotAskCredentialsOnBootSet() {
- return mDevicePolicyManager.getDoNotAskCredentialsOnBoot();
+ return getDevicePolicyManager().getDoNotAskCredentialsOnBoot();
}
private boolean shouldEncryptWithCredentials(boolean defaultValue) {
diff --git a/core/java/com/android/internal/widget/NestedScrollingChild.java b/core/java/com/android/internal/widget/NestedScrollingChild.java
new file mode 100644
index 0000000..20285b5
--- /dev/null
+++ b/core/java/com/android/internal/widget/NestedScrollingChild.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+
+/**
+ * This interface should be implemented by {@link android.view.View View} subclasses that wish
+ * to support dispatching nested scrolling operations to a cooperating parent
+ * {@link android.view.ViewGroup ViewGroup}.
+ *
+ * <p>Classes implementing this interface should create a final instance of a
+ * {@link NestedScrollingChildHelper} as a field and delegate any View methods to the
+ * <code>NestedScrollingChildHelper</code> methods of the same signature.</p>
+ *
+ * <p>Views invoking nested scrolling functionality should always do so from the relevant
+ * {@link ViewCompat}, {@link ViewGroupCompat} or {@link ViewParentCompat} compatibility
+ * shim static methods. This ensures interoperability with nested scrolling views on Android
+ * 5.0 Lollipop and newer.</p>
+ */
+public interface NestedScrollingChild {
+ /**
+ * Enable or disable nested scrolling for this view.
+ *
+ * <p>If this property is set to true the view will be permitted to initiate nested
+ * scrolling operations with a compatible parent view in the current hierarchy. If this
+ * view does not implement nested scrolling this will have no effect. Disabling nested scrolling
+ * while a nested scroll is in progress has the effect of {@link #stopNestedScroll() stopping}
+ * the nested scroll.</p>
+ *
+ * @param enabled true to enable nested scrolling, false to disable
+ *
+ * @see #isNestedScrollingEnabled()
+ */
+ void setNestedScrollingEnabled(boolean enabled);
+
+ /**
+ * Returns true if nested scrolling is enabled for this view.
+ *
+ * <p>If nested scrolling is enabled and this View class implementation supports it,
+ * this view will act as a nested scrolling child view when applicable, forwarding data
+ * about the scroll operation in progress to a compatible and cooperating nested scrolling
+ * parent.</p>
+ *
+ * @return true if nested scrolling is enabled
+ *
+ * @see #setNestedScrollingEnabled(boolean)
+ */
+ boolean isNestedScrollingEnabled();
+
+ /**
+ * Begin a nestable scroll operation along the given axes.
+ *
+ * <p>A view starting a nested scroll promises to abide by the following contract:</p>
+ *
+ * <p>The view will call startNestedScroll upon initiating a scroll operation. In the case
+ * of a touch scroll this corresponds to the initial {@link MotionEvent#ACTION_DOWN}.
+ * In the case of touch scrolling the nested scroll will be terminated automatically in
+ * the same manner as {@link ViewParent#requestDisallowInterceptTouchEvent(boolean)}.
+ * In the event of programmatic scrolling the caller must explicitly call
+ * {@link #stopNestedScroll()} to indicate the end of the nested scroll.</p>
+ *
+ * <p>If <code>startNestedScroll</code> returns true, a cooperative parent was found.
+ * If it returns false the caller may ignore the rest of this contract until the next scroll.
+ * Calling startNestedScroll while a nested scroll is already in progress will return true.</p>
+ *
+ * <p>At each incremental step of the scroll the caller should invoke
+ * {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll}
+ * once it has calculated the requested scrolling delta. If it returns true the nested scrolling
+ * parent at least partially consumed the scroll and the caller should adjust the amount it
+ * scrolls by.</p>
+ *
+ * <p>After applying the remainder of the scroll delta the caller should invoke
+ * {@link #dispatchNestedScroll(int, int, int, int, int[]) dispatchNestedScroll}, passing
+ * both the delta consumed and the delta unconsumed. A nested scrolling parent may treat
+ * these values differently. See
+ * {@link NestedScrollingParent#onNestedScroll(View, int, int, int, int)}.
+ * </p>
+ *
+ * @param axes Flags consisting of a combination of {@link ViewCompat#SCROLL_AXIS_HORIZONTAL}
+ * and/or {@link ViewCompat#SCROLL_AXIS_VERTICAL}.
+ * @return true if a cooperative parent was found and nested scrolling has been enabled for
+ * the current gesture.
+ *
+ * @see #stopNestedScroll()
+ * @see #dispatchNestedPreScroll(int, int, int[], int[])
+ * @see #dispatchNestedScroll(int, int, int, int, int[])
+ */
+ boolean startNestedScroll(int axes);
+
+ /**
+ * Stop a nested scroll in progress.
+ *
+ * <p>Calling this method when a nested scroll is not currently in progress is harmless.</p>
+ *
+ * @see #startNestedScroll(int)
+ */
+ void stopNestedScroll();
+
+ /**
+ * Returns true if this view has a nested scrolling parent.
+ *
+ * <p>The presence of a nested scrolling parent indicates that this view has initiated
+ * a nested scroll and it was accepted by an ancestor view further up the view hierarchy.</p>
+ *
+ * @return whether this view has a nested scrolling parent
+ */
+ boolean hasNestedScrollingParent();
+
+ /**
+ * Dispatch one step of a nested scroll in progress.
+ *
+ * <p>Implementations of views that support nested scrolling should call this to report
+ * info about a scroll in progress to the current nested scrolling parent. If a nested scroll
+ * is not currently in progress or nested scrolling is not
+ * {@link #isNestedScrollingEnabled() enabled} for this view this method does nothing.</p>
+ *
+ * <p>Compatible View implementations should also call
+ * {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll} before
+ * consuming a component of the scroll event themselves.</p>
+ *
+ * @param dxConsumed Horizontal distance in pixels consumed by this view during this scroll step
+ * @param dyConsumed Vertical distance in pixels consumed by this view during this scroll step
+ * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by this view
+ * @param dyUnconsumed Horizontal scroll distance in pixels not consumed by this view
+ * @param offsetInWindow Optional. If not null, on return this will contain the offset
+ * in local view coordinates of this view from before this operation
+ * to after it completes. View implementations may use this to adjust
+ * expected input coordinate tracking.
+ * @return true if the event was dispatched, false if it could not be dispatched.
+ * @see #dispatchNestedPreScroll(int, int, int[], int[])
+ */
+ boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
+
+ /**
+ * Dispatch one step of a nested scroll in progress before this view consumes any portion of it.
+ *
+ * <p>Nested pre-scroll events are to nested scroll events what touch intercept is to touch.
+ * <code>dispatchNestedPreScroll</code> offers an opportunity for the parent view in a nested
+ * scrolling operation to consume some or all of the scroll operation before the child view
+ * consumes it.</p>
+ *
+ * @param dx Horizontal scroll distance in pixels
+ * @param dy Vertical scroll distance in pixels
+ * @param consumed Output. If not null, consumed[0] will contain the consumed component of dx
+ * and consumed[1] the consumed dy.
+ * @param offsetInWindow Optional. If not null, on return this will contain the offset
+ * in local view coordinates of this view from before this operation
+ * to after it completes. View implementations may use this to adjust
+ * expected input coordinate tracking.
+ * @return true if the parent consumed some or all of the scroll delta
+ * @see #dispatchNestedScroll(int, int, int, int, int[])
+ */
+ boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
+
+ /**
+ * Dispatch a fling to a nested scrolling parent.
+ *
+ * <p>This method should be used to indicate that a nested scrolling child has detected
+ * suitable conditions for a fling. Generally this means that a touch scroll has ended with a
+ * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+ * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+ * along a scrollable axis.</p>
+ *
+ * <p>If a nested scrolling child view would normally fling but it is at the edge of
+ * its own content, it can use this method to delegate the fling to its nested scrolling
+ * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
+ *
+ * @param velocityX Horizontal fling velocity in pixels per second
+ * @param velocityY Vertical fling velocity in pixels per second
+ * @param consumed true if the child consumed the fling, false otherwise
+ * @return true if the nested scrolling parent consumed or otherwise reacted to the fling
+ */
+ boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
+
+ /**
+ * Dispatch a fling to a nested scrolling parent before it is processed by this view.
+ *
+ * <p>Nested pre-fling events are to nested fling events what touch intercept is to touch
+ * and what nested pre-scroll is to nested scroll. <code>dispatchNestedPreFling</code>
+ * offsets an opportunity for the parent view in a nested fling to fully consume the fling
+ * before the child view consumes it. If this method returns <code>true</code>, a nested
+ * parent view consumed the fling and this view should not scroll as a result.</p>
+ *
+ * <p>For a better user experience, only one view in a nested scrolling chain should consume
+ * the fling at a time. If a parent view consumed the fling this method will return false.
+ * Custom view implementations should account for this in two ways:</p>
+ *
+ * <ul>
+ * <li>If a custom view is paged and needs to settle to a fixed page-point, do not
+ * call <code>dispatchNestedPreFling</code>; consume the fling and settle to a valid
+ * position regardless.</li>
+ * <li>If a nested parent does consume the fling, this view should not scroll at all,
+ * even to settle back to a valid idle position.</li>
+ * </ul>
+ *
+ * <p>Views should also not offer fling velocities to nested parent views along an axis
+ * where scrolling is not currently supported; a {@link android.widget.ScrollView ScrollView}
+ * should not offer a horizontal fling velocity to its parents since scrolling along that
+ * axis is not permitted and carrying velocity along that motion does not make sense.</p>
+ *
+ * @param velocityX Horizontal fling velocity in pixels per second
+ * @param velocityY Vertical fling velocity in pixels per second
+ * @return true if a nested scrolling parent consumed the fling
+ */
+ boolean dispatchNestedPreFling(float velocityX, float velocityY);
+}
diff --git a/core/java/com/android/internal/widget/OpReorderer.java b/core/java/com/android/internal/widget/OpReorderer.java
new file mode 100644
index 0000000..babb087
--- /dev/null
+++ b/core/java/com/android/internal/widget/OpReorderer.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import static com.android.internal.widget.AdapterHelper.UpdateOp.ADD;
+import static com.android.internal.widget.AdapterHelper.UpdateOp.MOVE;
+import static com.android.internal.widget.AdapterHelper.UpdateOp.REMOVE;
+import static com.android.internal.widget.AdapterHelper.UpdateOp.UPDATE;
+
+import com.android.internal.widget.AdapterHelper.UpdateOp;
+
+import java.util.List;
+
+class OpReorderer {
+
+ final Callback mCallback;
+
+ OpReorderer(Callback callback) {
+ mCallback = callback;
+ }
+
+ void reorderOps(List<UpdateOp> ops) {
+ // since move operations breaks continuity, their effects on ADD/RM are hard to handle.
+ // we push them to the end of the list so that they can be handled easily.
+ int badMove;
+ while ((badMove = getLastMoveOutOfOrder(ops)) != -1) {
+ swapMoveOp(ops, badMove, badMove + 1);
+ }
+ }
+
+ private void swapMoveOp(List<UpdateOp> list, int badMove, int next) {
+ final UpdateOp moveOp = list.get(badMove);
+ final UpdateOp nextOp = list.get(next);
+ switch (nextOp.cmd) {
+ case REMOVE:
+ swapMoveRemove(list, badMove, moveOp, next, nextOp);
+ break;
+ case ADD:
+ swapMoveAdd(list, badMove, moveOp, next, nextOp);
+ break;
+ case UPDATE:
+ swapMoveUpdate(list, badMove, moveOp, next, nextOp);
+ break;
+ }
+ }
+
+ void swapMoveRemove(List<UpdateOp> list, int movePos, UpdateOp moveOp,
+ int removePos, UpdateOp removeOp) {
+ UpdateOp extraRm = null;
+ // check if move is nulled out by remove
+ boolean revertedMove = false;
+ final boolean moveIsBackwards;
+
+ if (moveOp.positionStart < moveOp.itemCount) {
+ moveIsBackwards = false;
+ if (removeOp.positionStart == moveOp.positionStart
+ && removeOp.itemCount == moveOp.itemCount - moveOp.positionStart) {
+ revertedMove = true;
+ }
+ } else {
+ moveIsBackwards = true;
+ if (removeOp.positionStart == moveOp.itemCount + 1
+ && removeOp.itemCount == moveOp.positionStart - moveOp.itemCount) {
+ revertedMove = true;
+ }
+ }
+
+ // going in reverse, first revert the effect of add
+ if (moveOp.itemCount < removeOp.positionStart) {
+ removeOp.positionStart--;
+ } else if (moveOp.itemCount < removeOp.positionStart + removeOp.itemCount) {
+ // move is removed.
+ removeOp.itemCount--;
+ moveOp.cmd = REMOVE;
+ moveOp.itemCount = 1;
+ if (removeOp.itemCount == 0) {
+ list.remove(removePos);
+ mCallback.recycleUpdateOp(removeOp);
+ }
+ // no need to swap, it is already a remove
+ return;
+ }
+
+ // now affect of add is consumed. now apply effect of first remove
+ if (moveOp.positionStart <= removeOp.positionStart) {
+ removeOp.positionStart++;
+ } else if (moveOp.positionStart < removeOp.positionStart + removeOp.itemCount) {
+ final int remaining = removeOp.positionStart + removeOp.itemCount
+ - moveOp.positionStart;
+ extraRm = mCallback.obtainUpdateOp(REMOVE, moveOp.positionStart + 1, remaining, null);
+ removeOp.itemCount = moveOp.positionStart - removeOp.positionStart;
+ }
+
+ // if effects of move is reverted by remove, we are done.
+ if (revertedMove) {
+ list.set(movePos, removeOp);
+ list.remove(removePos);
+ mCallback.recycleUpdateOp(moveOp);
+ return;
+ }
+
+ // now find out the new locations for move actions
+ if (moveIsBackwards) {
+ if (extraRm != null) {
+ if (moveOp.positionStart > extraRm.positionStart) {
+ moveOp.positionStart -= extraRm.itemCount;
+ }
+ if (moveOp.itemCount > extraRm.positionStart) {
+ moveOp.itemCount -= extraRm.itemCount;
+ }
+ }
+ if (moveOp.positionStart > removeOp.positionStart) {
+ moveOp.positionStart -= removeOp.itemCount;
+ }
+ if (moveOp.itemCount > removeOp.positionStart) {
+ moveOp.itemCount -= removeOp.itemCount;
+ }
+ } else {
+ if (extraRm != null) {
+ if (moveOp.positionStart >= extraRm.positionStart) {
+ moveOp.positionStart -= extraRm.itemCount;
+ }
+ if (moveOp.itemCount >= extraRm.positionStart) {
+ moveOp.itemCount -= extraRm.itemCount;
+ }
+ }
+ if (moveOp.positionStart >= removeOp.positionStart) {
+ moveOp.positionStart -= removeOp.itemCount;
+ }
+ if (moveOp.itemCount >= removeOp.positionStart) {
+ moveOp.itemCount -= removeOp.itemCount;
+ }
+ }
+
+ list.set(movePos, removeOp);
+ if (moveOp.positionStart != moveOp.itemCount) {
+ list.set(removePos, moveOp);
+ } else {
+ list.remove(removePos);
+ }
+ if (extraRm != null) {
+ list.add(movePos, extraRm);
+ }
+ }
+
+ private void swapMoveAdd(List<UpdateOp> list, int move, UpdateOp moveOp, int add,
+ UpdateOp addOp) {
+ int offset = 0;
+ // going in reverse, first revert the effect of add
+ if (moveOp.itemCount < addOp.positionStart) {
+ offset--;
+ }
+ if (moveOp.positionStart < addOp.positionStart) {
+ offset++;
+ }
+ if (addOp.positionStart <= moveOp.positionStart) {
+ moveOp.positionStart += addOp.itemCount;
+ }
+ if (addOp.positionStart <= moveOp.itemCount) {
+ moveOp.itemCount += addOp.itemCount;
+ }
+ addOp.positionStart += offset;
+ list.set(move, addOp);
+ list.set(add, moveOp);
+ }
+
+ void swapMoveUpdate(List<UpdateOp> list, int move, UpdateOp moveOp, int update,
+ UpdateOp updateOp) {
+ UpdateOp extraUp1 = null;
+ UpdateOp extraUp2 = null;
+ // going in reverse, first revert the effect of add
+ if (moveOp.itemCount < updateOp.positionStart) {
+ updateOp.positionStart--;
+ } else if (moveOp.itemCount < updateOp.positionStart + updateOp.itemCount) {
+ // moved item is updated. add an update for it
+ updateOp.itemCount--;
+ extraUp1 = mCallback.obtainUpdateOp(UPDATE, moveOp.positionStart, 1, updateOp.payload);
+ }
+ // now affect of add is consumed. now apply effect of first remove
+ if (moveOp.positionStart <= updateOp.positionStart) {
+ updateOp.positionStart++;
+ } else if (moveOp.positionStart < updateOp.positionStart + updateOp.itemCount) {
+ final int remaining = updateOp.positionStart + updateOp.itemCount
+ - moveOp.positionStart;
+ extraUp2 = mCallback.obtainUpdateOp(UPDATE, moveOp.positionStart + 1, remaining,
+ updateOp.payload);
+ updateOp.itemCount -= remaining;
+ }
+ list.set(update, moveOp);
+ if (updateOp.itemCount > 0) {
+ list.set(move, updateOp);
+ } else {
+ list.remove(move);
+ mCallback.recycleUpdateOp(updateOp);
+ }
+ if (extraUp1 != null) {
+ list.add(move, extraUp1);
+ }
+ if (extraUp2 != null) {
+ list.add(move, extraUp2);
+ }
+ }
+
+ private int getLastMoveOutOfOrder(List<UpdateOp> list) {
+ boolean foundNonMove = false;
+ for (int i = list.size() - 1; i >= 0; i--) {
+ final UpdateOp op1 = list.get(i);
+ if (op1.cmd == MOVE) {
+ if (foundNonMove) {
+ return i;
+ }
+ } else {
+ foundNonMove = true;
+ }
+ }
+ return -1;
+ }
+
+ interface Callback {
+
+ UpdateOp obtainUpdateOp(int cmd, int startPosition, int itemCount, Object payload);
+
+ void recycleUpdateOp(UpdateOp op);
+ }
+}
diff --git a/core/java/com/android/internal/widget/OrientationHelper.java b/core/java/com/android/internal/widget/OrientationHelper.java
new file mode 100644
index 0000000..1b02c88
--- /dev/null
+++ b/core/java/com/android/internal/widget/OrientationHelper.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.graphics.Rect;
+import android.view.View;
+import android.widget.LinearLayout;
+
+/**
+ * Helper class for LayoutManagers to abstract measurements depending on the View's orientation.
+ * <p>
+ * It is developed to easily support vertical and horizontal orientations in a LayoutManager but
+ * can also be used to abstract calls around view bounds and child measurements with margins and
+ * decorations.
+ *
+ * @see #createHorizontalHelper(RecyclerView.LayoutManager)
+ * @see #createVerticalHelper(RecyclerView.LayoutManager)
+ */
+public abstract class OrientationHelper {
+
+ private static final int INVALID_SIZE = Integer.MIN_VALUE;
+
+ protected final RecyclerView.LayoutManager mLayoutManager;
+
+ public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
+
+ public static final int VERTICAL = LinearLayout.VERTICAL;
+
+ private int mLastTotalSpace = INVALID_SIZE;
+
+ final Rect mTmpRect = new Rect();
+
+ private OrientationHelper(RecyclerView.LayoutManager layoutManager) {
+ mLayoutManager = layoutManager;
+ }
+
+ /**
+ * Call this method after onLayout method is complete if state is NOT pre-layout.
+ * This method records information like layout bounds that might be useful in the next layout
+ * calculations.
+ */
+ public void onLayoutComplete() {
+ mLastTotalSpace = getTotalSpace();
+ }
+
+ /**
+ * Returns the layout space change between the previous layout pass and current layout pass.
+ * <p>
+ * Make sure you call {@link #onLayoutComplete()} at the end of your LayoutManager's
+ * {@link RecyclerView.LayoutManager#onLayoutChildren(RecyclerView.Recycler,
+ * RecyclerView.State)} method.
+ *
+ * @return The difference between the current total space and previous layout's total space.
+ * @see #onLayoutComplete()
+ */
+ public int getTotalSpaceChange() {
+ return INVALID_SIZE == mLastTotalSpace ? 0 : getTotalSpace() - mLastTotalSpace;
+ }
+
+ /**
+ * Returns the start of the view including its decoration and margin.
+ * <p>
+ * For example, for the horizontal helper, if a View's left is at pixel 20, has 2px left
+ * decoration and 3px left margin, returned value will be 15px.
+ *
+ * @param view The view element to check
+ * @return The first pixel of the element
+ * @see #getDecoratedEnd(android.view.View)
+ */
+ public abstract int getDecoratedStart(View view);
+
+ /**
+ * Returns the end of the view including its decoration and margin.
+ * <p>
+ * For example, for the horizontal helper, if a View's right is at pixel 200, has 2px right
+ * decoration and 3px right margin, returned value will be 205.
+ *
+ * @param view The view element to check
+ * @return The last pixel of the element
+ * @see #getDecoratedStart(android.view.View)
+ */
+ public abstract int getDecoratedEnd(View view);
+
+ /**
+ * Returns the end of the View after its matrix transformations are applied to its layout
+ * position.
+ * <p>
+ * This method is useful when trying to detect the visible edge of a View.
+ * <p>
+ * It includes the decorations but does not include the margins.
+ *
+ * @param view The view whose transformed end will be returned
+ * @return The end of the View after its decor insets and transformation matrix is applied to
+ * its position
+ *
+ * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect)
+ */
+ public abstract int getTransformedEndWithDecoration(View view);
+
+ /**
+ * Returns the start of the View after its matrix transformations are applied to its layout
+ * position.
+ * <p>
+ * This method is useful when trying to detect the visible edge of a View.
+ * <p>
+ * It includes the decorations but does not include the margins.
+ *
+ * @param view The view whose transformed start will be returned
+ * @return The start of the View after its decor insets and transformation matrix is applied to
+ * its position
+ *
+ * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect)
+ */
+ public abstract int getTransformedStartWithDecoration(View view);
+
+ /**
+ * Returns the space occupied by this View in the current orientation including decorations and
+ * margins.
+ *
+ * @param view The view element to check
+ * @return Total space occupied by this view
+ * @see #getDecoratedMeasurementInOther(View)
+ */
+ public abstract int getDecoratedMeasurement(View view);
+
+ /**
+ * Returns the space occupied by this View in the perpendicular orientation including
+ * decorations and margins.
+ *
+ * @param view The view element to check
+ * @return Total space occupied by this view in the perpendicular orientation to current one
+ * @see #getDecoratedMeasurement(View)
+ */
+ public abstract int getDecoratedMeasurementInOther(View view);
+
+ /**
+ * Returns the start position of the layout after the start padding is added.
+ *
+ * @return The very first pixel we can draw.
+ */
+ public abstract int getStartAfterPadding();
+
+ /**
+ * Returns the end position of the layout after the end padding is removed.
+ *
+ * @return The end boundary for this layout.
+ */
+ public abstract int getEndAfterPadding();
+
+ /**
+ * Returns the end position of the layout without taking padding into account.
+ *
+ * @return The end boundary for this layout without considering padding.
+ */
+ public abstract int getEnd();
+
+ /**
+ * Offsets all children's positions by the given amount.
+ *
+ * @param amount Value to add to each child's layout parameters
+ */
+ public abstract void offsetChildren(int amount);
+
+ /**
+ * Returns the total space to layout. This number is the difference between
+ * {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}.
+ *
+ * @return Total space to layout children
+ */
+ public abstract int getTotalSpace();
+
+ /**
+ * Offsets the child in this orientation.
+ *
+ * @param view View to offset
+ * @param offset offset amount
+ */
+ public abstract void offsetChild(View view, int offset);
+
+ /**
+ * Returns the padding at the end of the layout. For horizontal helper, this is the right
+ * padding and for vertical helper, this is the bottom padding. This method does not check
+ * whether the layout is RTL or not.
+ *
+ * @return The padding at the end of the layout.
+ */
+ public abstract int getEndPadding();
+
+ /**
+ * Returns the MeasureSpec mode for the current orientation from the LayoutManager.
+ *
+ * @return The current measure spec mode.
+ *
+ * @see View.MeasureSpec
+ * @see RecyclerView.LayoutManager#getWidthMode()
+ * @see RecyclerView.LayoutManager#getHeightMode()
+ */
+ public abstract int getMode();
+
+ /**
+ * Returns the MeasureSpec mode for the perpendicular orientation from the LayoutManager.
+ *
+ * @return The current measure spec mode.
+ *
+ * @see View.MeasureSpec
+ * @see RecyclerView.LayoutManager#getWidthMode()
+ * @see RecyclerView.LayoutManager#getHeightMode()
+ */
+ public abstract int getModeInOther();
+
+ /**
+ * Creates an OrientationHelper for the given LayoutManager and orientation.
+ *
+ * @param layoutManager LayoutManager to attach to
+ * @param orientation Desired orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}
+ * @return A new OrientationHelper
+ */
+ public static OrientationHelper createOrientationHelper(
+ RecyclerView.LayoutManager layoutManager, int orientation) {
+ switch (orientation) {
+ case HORIZONTAL:
+ return createHorizontalHelper(layoutManager);
+ case VERTICAL:
+ return createVerticalHelper(layoutManager);
+ }
+ throw new IllegalArgumentException("invalid orientation");
+ }
+
+ /**
+ * Creates a horizontal OrientationHelper for the given LayoutManager.
+ *
+ * @param layoutManager The LayoutManager to attach to.
+ * @return A new OrientationHelper
+ */
+ public static OrientationHelper createHorizontalHelper(
+ RecyclerView.LayoutManager layoutManager) {
+ return new OrientationHelper(layoutManager) {
+ @Override
+ public int getEndAfterPadding() {
+ return mLayoutManager.getWidth() - mLayoutManager.getPaddingRight();
+ }
+
+ @Override
+ public int getEnd() {
+ return mLayoutManager.getWidth();
+ }
+
+ @Override
+ public void offsetChildren(int amount) {
+ mLayoutManager.offsetChildrenHorizontal(amount);
+ }
+
+ @Override
+ public int getStartAfterPadding() {
+ return mLayoutManager.getPaddingLeft();
+ }
+
+ @Override
+ public int getDecoratedMeasurement(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
+ + params.rightMargin;
+ }
+
+ @Override
+ public int getDecoratedMeasurementInOther(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
+ + params.bottomMargin;
+ }
+
+ @Override
+ public int getDecoratedEnd(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedRight(view) + params.rightMargin;
+ }
+
+ @Override
+ public int getDecoratedStart(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedLeft(view) - params.leftMargin;
+ }
+
+ @Override
+ public int getTransformedEndWithDecoration(View view) {
+ mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+ return mTmpRect.right;
+ }
+
+ @Override
+ public int getTransformedStartWithDecoration(View view) {
+ mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+ return mTmpRect.left;
+ }
+
+ @Override
+ public int getTotalSpace() {
+ return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft()
+ - mLayoutManager.getPaddingRight();
+ }
+
+ @Override
+ public void offsetChild(View view, int offset) {
+ view.offsetLeftAndRight(offset);
+ }
+
+ @Override
+ public int getEndPadding() {
+ return mLayoutManager.getPaddingRight();
+ }
+
+ @Override
+ public int getMode() {
+ return mLayoutManager.getWidthMode();
+ }
+
+ @Override
+ public int getModeInOther() {
+ return mLayoutManager.getHeightMode();
+ }
+ };
+ }
+
+ /**
+ * Creates a vertical OrientationHelper for the given LayoutManager.
+ *
+ * @param layoutManager The LayoutManager to attach to.
+ * @return A new OrientationHelper
+ */
+ public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) {
+ return new OrientationHelper(layoutManager) {
+ @Override
+ public int getEndAfterPadding() {
+ return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom();
+ }
+
+ @Override
+ public int getEnd() {
+ return mLayoutManager.getHeight();
+ }
+
+ @Override
+ public void offsetChildren(int amount) {
+ mLayoutManager.offsetChildrenVertical(amount);
+ }
+
+ @Override
+ public int getStartAfterPadding() {
+ return mLayoutManager.getPaddingTop();
+ }
+
+ @Override
+ public int getDecoratedMeasurement(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
+ + params.bottomMargin;
+ }
+
+ @Override
+ public int getDecoratedMeasurementInOther(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
+ + params.rightMargin;
+ }
+
+ @Override
+ public int getDecoratedEnd(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin;
+ }
+
+ @Override
+ public int getDecoratedStart(View view) {
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+ view.getLayoutParams();
+ return mLayoutManager.getDecoratedTop(view) - params.topMargin;
+ }
+
+ @Override
+ public int getTransformedEndWithDecoration(View view) {
+ mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+ return mTmpRect.bottom;
+ }
+
+ @Override
+ public int getTransformedStartWithDecoration(View view) {
+ mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+ return mTmpRect.top;
+ }
+
+ @Override
+ public int getTotalSpace() {
+ return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
+ - mLayoutManager.getPaddingBottom();
+ }
+
+ @Override
+ public void offsetChild(View view, int offset) {
+ view.offsetTopAndBottom(offset);
+ }
+
+ @Override
+ public int getEndPadding() {
+ return mLayoutManager.getPaddingBottom();
+ }
+
+ @Override
+ public int getMode() {
+ return mLayoutManager.getHeightMode();
+ }
+
+ @Override
+ public int getModeInOther() {
+ return mLayoutManager.getWidthMode();
+ }
+ };
+ }
+}
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
new file mode 100644
index 0000000..0cf3164
--- /dev/null
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -0,0 +1,12255 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.CallSuper;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.Observable;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.AbsSavedState;
+import android.view.Display;
+import android.view.FocusFinder;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Interpolator;
+import android.widget.EdgeEffect;
+import android.widget.OverScroller;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A flexible view for providing a limited window into a large data set.
+ *
+ * <h3>Glossary of terms:</h3>
+ *
+ * <ul>
+ * <li><em>Adapter:</em> A subclass of {@link Adapter} responsible for providing views
+ * that represent items in a data set.</li>
+ * <li><em>Position:</em> The position of a data item within an <em>Adapter</em>.</li>
+ * <li><em>Index:</em> The index of an attached child view as used in a call to
+ * {@link ViewGroup#getChildAt}. Contrast with <em>Position.</em></li>
+ * <li><em>Binding:</em> The process of preparing a child view to display data corresponding
+ * to a <em>position</em> within the adapter.</li>
+ * <li><em>Recycle (view):</em> A view previously used to display data for a specific adapter
+ * position may be placed in a cache for later reuse to display the same type of data again
+ * later. This can drastically improve performance by skipping initial layout inflation
+ * or construction.</li>
+ * <li><em>Scrap (view):</em> A child view that has entered into a temporarily detached
+ * state during layout. Scrap views may be reused without becoming fully detached
+ * from the parent RecyclerView, either unmodified if no rebinding is required or modified
+ * by the adapter if the view was considered <em>dirty</em>.</li>
+ * <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before
+ * being displayed.</li>
+ * </ul>
+ *
+ * <h4>Positions in RecyclerView:</h4>
+ * <p>
+ * RecyclerView introduces an additional level of abstraction between the {@link Adapter} and
+ * {@link LayoutManager} to be able to detect data set changes in batches during a layout
+ * calculation. This saves LayoutManager from tracking adapter changes to calculate animations.
+ * It also helps with performance because all view bindings happen at the same time and unnecessary
+ * bindings are avoided.
+ * <p>
+ * For this reason, there are two types of <code>position</code> related methods in RecyclerView:
+ * <ul>
+ * <li>layout position: Position of an item in the latest layout calculation. This is the
+ * position from the LayoutManager's perspective.</li>
+ * <li>adapter position: Position of an item in the adapter. This is the position from
+ * the Adapter's perspective.</li>
+ * </ul>
+ * <p>
+ * These two positions are the same except the time between dispatching <code>adapter.notify*
+ * </code> events and calculating the updated layout.
+ * <p>
+ * Methods that return or receive <code>*LayoutPosition*</code> use position as of the latest
+ * layout calculation (e.g. {@link ViewHolder#getLayoutPosition()},
+ * {@link #findViewHolderForLayoutPosition(int)}). These positions include all changes until the
+ * last layout calculation. You can rely on these positions to be consistent with what user is
+ * currently seeing on the screen. For example, if you have a list of items on the screen and user
+ * asks for the 5<sup>th</sup> element, you should use these methods as they'll match what user
+ * is seeing.
+ * <p>
+ * The other set of position related methods are in the form of
+ * <code>*AdapterPosition*</code>. (e.g. {@link ViewHolder#getAdapterPosition()},
+ * {@link #findViewHolderForAdapterPosition(int)}) You should use these methods when you need to
+ * work with up-to-date adapter positions even if they may not have been reflected to layout yet.
+ * For example, if you want to access the item in the adapter on a ViewHolder click, you should use
+ * {@link ViewHolder#getAdapterPosition()}. Beware that these methods may not be able to calculate
+ * adapter positions if {@link Adapter#notifyDataSetChanged()} has been called and new layout has
+ * not yet been calculated. For this reasons, you should carefully handle {@link #NO_POSITION} or
+ * <code>null</code> results from these methods.
+ * <p>
+ * When writing a {@link LayoutManager} you almost always want to use layout positions whereas when
+ * writing an {@link Adapter}, you probably want to use adapter positions.
+ *
+ * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_layoutManager
+ */
+public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {
+
+ static final String TAG = "RecyclerView";
+
+ static final boolean DEBUG = false;
+
+ private static final int[] NESTED_SCROLLING_ATTRS = { android.R.attr.nestedScrollingEnabled };
+
+ private static final int[] CLIP_TO_PADDING_ATTR = {android.R.attr.clipToPadding};
+
+ /**
+ * On Kitkat and JB MR2, there is a bug which prevents DisplayList from being invalidated if
+ * a View is two levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by
+ * setting View's visibility to INVISIBLE when View is detached. On Kitkat and JB MR2, Recycler
+ * recursively traverses itemView and invalidates display list for each ViewGroup that matches
+ * this criteria.
+ */
+ static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 18
+ || Build.VERSION.SDK_INT == 19 || Build.VERSION.SDK_INT == 20;
+ /**
+ * On M+, an unspecified measure spec may include a hint which we can use. On older platforms,
+ * this value might be garbage. To save LayoutManagers from it, RecyclerView sets the size to
+ * 0 when mode is unspecified.
+ */
+ static final boolean ALLOW_SIZE_IN_UNSPECIFIED_SPEC = Build.VERSION.SDK_INT >= 23;
+
+ static final boolean POST_UPDATES_ON_ANIMATION = Build.VERSION.SDK_INT >= 16;
+
+ /**
+ * On L+, with RenderThread, the UI thread has idle time after it has passed a frame off to
+ * RenderThread but before the next frame begins. We schedule prefetch work in this window.
+ */
+ private static final boolean ALLOW_THREAD_GAP_WORK = Build.VERSION.SDK_INT >= 21;
+
+ /**
+ * FocusFinder#findNextFocus is broken on ICS MR1 and older for View.FOCUS_BACKWARD direction.
+ * We convert it to an absolute direction such as FOCUS_DOWN or FOCUS_LEFT.
+ */
+ private static final boolean FORCE_ABS_FOCUS_SEARCH_DIRECTION = Build.VERSION.SDK_INT <= 15;
+
+ /**
+ * on API 15-, a focused child can still be considered a focused child of RV even after
+ * it's being removed or its focusable flag is set to false. This is because when this focused
+ * child is detached, the reference to this child is not removed in clearFocus. API 16 and above
+ * properly handle this case by calling ensureInputFocusOnFirstFocusable or rootViewRequestFocus
+ * to request focus on a new child, which will clear the focus on the old (detached) child as a
+ * side-effect.
+ */
+ private static final boolean IGNORE_DETACHED_FOCUSED_CHILD = Build.VERSION.SDK_INT <= 15;
+
+ static final boolean DISPATCH_TEMP_DETACH = false;
+ public static final int HORIZONTAL = 0;
+ public static final int VERTICAL = 1;
+
+ public static final int NO_POSITION = -1;
+ public static final long NO_ID = -1;
+ public static final int INVALID_TYPE = -1;
+
+ /**
+ * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates
+ * that the RecyclerView should use the standard touch slop for smooth,
+ * continuous scrolling.
+ */
+ public static final int TOUCH_SLOP_DEFAULT = 0;
+
+ /**
+ * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates
+ * that the RecyclerView should use the standard touch slop for scrolling
+ * widgets that snap to a page or other coarse-grained barrier.
+ */
+ public static final int TOUCH_SLOP_PAGING = 1;
+
+ static final int MAX_SCROLL_DURATION = 2000;
+
+ /**
+ * RecyclerView is calculating a scroll.
+ * If there are too many of these in Systrace, some Views inside RecyclerView might be causing
+ * it. Try to avoid using EditText, focusable views or handle them with care.
+ */
+ static final String TRACE_SCROLL_TAG = "RV Scroll";
+
+ /**
+ * OnLayout has been called by the View system.
+ * If this shows up too many times in Systrace, make sure the children of RecyclerView do not
+ * update themselves directly. This will cause a full re-layout but when it happens via the
+ * Adapter notifyItemChanged, RecyclerView can avoid full layout calculation.
+ */
+ private static final String TRACE_ON_LAYOUT_TAG = "RV OnLayout";
+
+ /**
+ * NotifyDataSetChanged or equal has been called.
+ * If this is taking a long time, try sending granular notify adapter changes instead of just
+ * calling notifyDataSetChanged or setAdapter / swapAdapter. Adding stable ids to your adapter
+ * might help.
+ */
+ private static final String TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG = "RV FullInvalidate";
+
+ /**
+ * RecyclerView is doing a layout for partial adapter updates (we know what has changed)
+ * If this is taking a long time, you may have dispatched too many Adapter updates causing too
+ * many Views being rebind. Make sure all are necessary and also prefer using notify*Range
+ * methods.
+ */
+ private static final String TRACE_HANDLE_ADAPTER_UPDATES_TAG = "RV PartialInvalidate";
+
+ /**
+ * RecyclerView is rebinding a View.
+ * If this is taking a lot of time, consider optimizing your layout or make sure you are not
+ * doing extra operations in onBindViewHolder call.
+ */
+ static final String TRACE_BIND_VIEW_TAG = "RV OnBindView";
+
+ /**
+ * RecyclerView is attempting to pre-populate off screen views.
+ */
+ static final String TRACE_PREFETCH_TAG = "RV Prefetch";
+
+ /**
+ * RecyclerView is attempting to pre-populate off screen itemviews within an off screen
+ * RecyclerView.
+ */
+ static final String TRACE_NESTED_PREFETCH_TAG = "RV Nested Prefetch";
+
+ /**
+ * RecyclerView is creating a new View.
+ * If too many of these present in Systrace:
+ * - There might be a problem in Recycling (e.g. custom Animations that set transient state and
+ * prevent recycling or ItemAnimator not implementing the contract properly. ({@link
+ * > Adapter#onFailedToRecycleView(ViewHolder)})
+ *
+ * - There might be too many item view types.
+ * > Try merging them
+ *
+ * - There might be too many itemChange animations and not enough space in RecyclerPool.
+ * >Try increasing your pool size and item cache size.
+ */
+ static final String TRACE_CREATE_VIEW_TAG = "RV CreateView";
+ private static final Class<?>[] LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE =
+ new Class[]{Context.class, AttributeSet.class, int.class, int.class};
+
+ private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
+
+ final Recycler mRecycler = new Recycler();
+
+ private SavedState mPendingSavedState;
+
+ /**
+ * Handles adapter updates
+ */
+ AdapterHelper mAdapterHelper;
+
+ /**
+ * Handles abstraction between LayoutManager children and RecyclerView children
+ */
+ ChildHelper mChildHelper;
+
+ /**
+ * Keeps data about views to be used for animations
+ */
+ final ViewInfoStore mViewInfoStore = new ViewInfoStore();
+
+ /**
+ * Prior to L, there is no way to query this variable which is why we override the setter and
+ * track it here.
+ */
+ boolean mClipToPadding;
+
+ /**
+ * Note: this Runnable is only ever posted if:
+ * 1) We've been through first layout
+ * 2) We know we have a fixed size (mHasFixedSize)
+ * 3) We're attached
+ */
+ final Runnable mUpdateChildViewsRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (!mFirstLayoutComplete || isLayoutRequested()) {
+ // a layout request will happen, we should not do layout here.
+ return;
+ }
+ if (!mIsAttached) {
+ requestLayout();
+ // if we are not attached yet, mark us as requiring layout and skip
+ return;
+ }
+ if (mLayoutFrozen) {
+ mLayoutRequestEaten = true;
+ return; //we'll process updates when ice age ends.
+ }
+ consumePendingUpdateOperations();
+ }
+ };
+
+ final Rect mTempRect = new Rect();
+ private final Rect mTempRect2 = new Rect();
+ final RectF mTempRectF = new RectF();
+ Adapter mAdapter;
+ @VisibleForTesting LayoutManager mLayout;
+ RecyclerListener mRecyclerListener;
+ final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<>();
+ private final ArrayList<OnItemTouchListener> mOnItemTouchListeners =
+ new ArrayList<>();
+ private OnItemTouchListener mActiveOnItemTouchListener;
+ boolean mIsAttached;
+ boolean mHasFixedSize;
+ @VisibleForTesting boolean mFirstLayoutComplete;
+
+ // Counting lock to control whether we should ignore requestLayout calls from children or not.
+ private int mEatRequestLayout = 0;
+
+ boolean mLayoutRequestEaten;
+ boolean mLayoutFrozen;
+ private boolean mIgnoreMotionEventTillDown;
+
+ // binary OR of change events that were eaten during a layout or scroll.
+ private int mEatenAccessibilityChangeFlags;
+ boolean mAdapterUpdateDuringMeasure;
+
+ private final AccessibilityManager mAccessibilityManager;
+ private List<OnChildAttachStateChangeListener> mOnChildAttachStateListeners;
+
+ /**
+ * Set to true when an adapter data set changed notification is received.
+ * In that case, we cannot run any animations since we don't know what happened until layout.
+ *
+ * Attached items are invalid until next layout, at which point layout will animate/replace
+ * items as necessary, building up content from the (effectively) new adapter from scratch.
+ *
+ * Cached items must be discarded when setting this to true, so that the cache may be freely
+ * used by prefetching until the next layout occurs.
+ *
+ * @see #setDataSetChangedAfterLayout()
+ */
+ boolean mDataSetHasChangedAfterLayout = false;
+
+ /**
+ * This variable is incremented during a dispatchLayout and/or scroll.
+ * Some methods should not be called during these periods (e.g. adapter data change).
+ * Doing so will create hard to find bugs so we better check it and throw an exception.
+ *
+ * @see #assertInLayoutOrScroll(String)
+ * @see #assertNotInLayoutOrScroll(String)
+ */
+ private int mLayoutOrScrollCounter = 0;
+
+ /**
+ * Similar to mLayoutOrScrollCounter but logs a warning instead of throwing an exception
+ * (for API compatibility).
+ * <p>
+ * It is a bad practice for a developer to update the data in a scroll callback since it is
+ * potentially called during a layout.
+ */
+ private int mDispatchScrollCounter = 0;
+
+ private EdgeEffect mLeftGlow, mTopGlow, mRightGlow, mBottomGlow;
+
+ ItemAnimator mItemAnimator = new DefaultItemAnimator();
+
+ private static final int INVALID_POINTER = -1;
+
+ /**
+ * The RecyclerView is not currently scrolling.
+ * @see #getScrollState()
+ */
+ public static final int SCROLL_STATE_IDLE = 0;
+
+ /**
+ * The RecyclerView is currently being dragged by outside input such as user touch input.
+ * @see #getScrollState()
+ */
+ public static final int SCROLL_STATE_DRAGGING = 1;
+
+ /**
+ * The RecyclerView is currently animating to a final position while not under
+ * outside control.
+ * @see #getScrollState()
+ */
+ public static final int SCROLL_STATE_SETTLING = 2;
+
+ static final long FOREVER_NS = Long.MAX_VALUE;
+
+ // Touch/scrolling handling
+
+ private int mScrollState = SCROLL_STATE_IDLE;
+ private int mScrollPointerId = INVALID_POINTER;
+ private VelocityTracker mVelocityTracker;
+ private int mInitialTouchX;
+ private int mInitialTouchY;
+ private int mLastTouchX;
+ private int mLastTouchY;
+ private int mTouchSlop;
+ private OnFlingListener mOnFlingListener;
+ private final int mMinFlingVelocity;
+ private final int mMaxFlingVelocity;
+ // This value is used when handling generic motion events.
+ private float mScrollFactor = Float.MIN_VALUE;
+ private boolean mPreserveFocusAfterLayout = true;
+
+ final ViewFlinger mViewFlinger = new ViewFlinger();
+
+ GapWorker mGapWorker;
+ GapWorker.LayoutPrefetchRegistryImpl mPrefetchRegistry =
+ ALLOW_THREAD_GAP_WORK ? new GapWorker.LayoutPrefetchRegistryImpl() : null;
+
+ final State mState = new State();
+
+ private OnScrollListener mScrollListener;
+ private List<OnScrollListener> mScrollListeners;
+
+ // For use in item animations
+ boolean mItemsAddedOrRemoved = false;
+ boolean mItemsChanged = false;
+ private ItemAnimator.ItemAnimatorListener mItemAnimatorListener =
+ new ItemAnimatorRestoreListener();
+ boolean mPostedAnimatorRunner = false;
+ RecyclerViewAccessibilityDelegate mAccessibilityDelegate;
+ private ChildDrawingOrderCallback mChildDrawingOrderCallback;
+
+ // simple array to keep min and max child position during a layout calculation
+ // preserved not to create a new one in each layout pass
+ private final int[] mMinMaxLayoutPositions = new int[2];
+
+ private final int[] mScrollOffset = new int[2];
+ private final int[] mScrollConsumed = new int[2];
+ private final int[] mNestedOffsets = new int[2];
+
+ /**
+ * These are views that had their a11y importance changed during a layout. We defer these events
+ * until the end of the layout because a11y service may make sync calls back to the RV while
+ * the View's state is undefined.
+ */
+ @VisibleForTesting
+ final List<ViewHolder> mPendingAccessibilityImportanceChange = new ArrayList();
+
+ private Runnable mItemAnimatorRunner = new Runnable() {
+ @Override
+ public void run() {
+ if (mItemAnimator != null) {
+ mItemAnimator.runPendingAnimations();
+ }
+ mPostedAnimatorRunner = false;
+ }
+ };
+
+ static final Interpolator sQuinticInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float t) {
+ t -= 1.0f;
+ return t * t * t * t * t + 1.0f;
+ }
+ };
+
+ /**
+ * The callback to convert view info diffs into animations.
+ */
+ private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
+ new ViewInfoStore.ProcessCallback() {
+ @Override
+ public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
+ @Nullable ItemHolderInfo postInfo) {
+ mRecycler.unscrapView(viewHolder);
+ animateDisappearance(viewHolder, info, postInfo);
+ }
+ @Override
+ public void processAppeared(ViewHolder viewHolder,
+ ItemHolderInfo preInfo, ItemHolderInfo info) {
+ animateAppearance(viewHolder, preInfo, info);
+ }
+
+ @Override
+ public void processPersistent(ViewHolder viewHolder,
+ @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
+ viewHolder.setIsRecyclable(false);
+ if (mDataSetHasChangedAfterLayout) {
+ // since it was rebound, use change instead as we'll be mapping them from
+ // stable ids. If stable ids were false, we would not be running any
+ // animations
+ if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo, postInfo)) {
+ postAnimationRunner();
+ }
+ } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
+ postAnimationRunner();
+ }
+ }
+ @Override
+ public void unused(ViewHolder viewHolder) {
+ mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
+ }
+ };
+
+ public RecyclerView(Context context) {
+ this(context, null);
+ }
+
+ public RecyclerView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ if (attrs != null) {
+ TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);
+ mClipToPadding = a.getBoolean(0, true);
+ a.recycle();
+ } else {
+ mClipToPadding = true;
+ }
+ setScrollContainer(true);
+ setFocusableInTouchMode(true);
+
+ final ViewConfiguration vc = ViewConfiguration.get(context);
+ mTouchSlop = vc.getScaledTouchSlop();
+ mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
+ mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
+ setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);
+
+ mItemAnimator.setListener(mItemAnimatorListener);
+ initAdapterManager();
+ initChildrenHelper();
+ // If not explicitly specified this view is important for accessibility.
+ if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+ mAccessibilityManager = (AccessibilityManager) getContext()
+ .getSystemService(Context.ACCESSIBILITY_SERVICE);
+ setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this));
+ // Create the layoutManager if specified.
+
+ boolean nestedScrollingEnabled = true;
+
+ if (attrs != null) {
+ int defStyleRes = 0;
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
+ defStyle, defStyleRes);
+ String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager);
+ int descendantFocusability = a.getInt(
+ R.styleable.RecyclerView_descendantFocusability, -1);
+ if (descendantFocusability == -1) {
+ setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ }
+ a.recycle();
+ createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);
+
+ if (Build.VERSION.SDK_INT >= 21) {
+ a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS,
+ defStyle, defStyleRes);
+ nestedScrollingEnabled = a.getBoolean(0, true);
+ a.recycle();
+ }
+ } else {
+ setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ }
+
+ // Re-set whether nested scrolling is enabled so that it is set on all API levels
+ setNestedScrollingEnabled(nestedScrollingEnabled);
+ }
+
+ /**
+ * Returns the accessibility delegate compatibility implementation used by the RecyclerView.
+ * @return An instance of AccessibilityDelegateCompat used by RecyclerView
+ */
+ public RecyclerViewAccessibilityDelegate getCompatAccessibilityDelegate() {
+ return mAccessibilityDelegate;
+ }
+
+ /**
+ * Sets the accessibility delegate compatibility implementation used by RecyclerView.
+ * @param accessibilityDelegate The accessibility delegate to be used by RecyclerView.
+ */
+ public void setAccessibilityDelegateCompat(
+ RecyclerViewAccessibilityDelegate accessibilityDelegate) {
+ mAccessibilityDelegate = accessibilityDelegate;
+ setAccessibilityDelegate(mAccessibilityDelegate);
+ }
+
+ /**
+ * Instantiate and set a LayoutManager, if specified in the attributes.
+ */
+ private void createLayoutManager(Context context, String className, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ if (className != null) {
+ className = className.trim();
+ if (className.length() != 0) { // Can't use isEmpty since it was added in API 9.
+ className = getFullClassName(context, className);
+ try {
+ ClassLoader classLoader;
+ if (isInEditMode()) {
+ // Stupid layoutlib cannot handle simple class loaders.
+ classLoader = this.getClass().getClassLoader();
+ } else {
+ classLoader = context.getClassLoader();
+ }
+ Class<? extends LayoutManager> layoutManagerClass =
+ classLoader.loadClass(className).asSubclass(LayoutManager.class);
+ Constructor<? extends LayoutManager> constructor;
+ Object[] constructorArgs = null;
+ try {
+ constructor = layoutManagerClass
+ .getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE);
+ constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes};
+ } catch (NoSuchMethodException e) {
+ try {
+ constructor = layoutManagerClass.getConstructor();
+ } catch (NoSuchMethodException e1) {
+ e1.initCause(e);
+ throw new IllegalStateException(attrs.getPositionDescription()
+ + ": Error creating LayoutManager " + className, e1);
+ }
+ }
+ constructor.setAccessible(true);
+ setLayoutManager(constructor.newInstance(constructorArgs));
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException(attrs.getPositionDescription()
+ + ": Unable to find LayoutManager " + className, e);
+ } catch (InvocationTargetException e) {
+ throw new IllegalStateException(attrs.getPositionDescription()
+ + ": Could not instantiate the LayoutManager: " + className, e);
+ } catch (InstantiationException e) {
+ throw new IllegalStateException(attrs.getPositionDescription()
+ + ": Could not instantiate the LayoutManager: " + className, e);
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(attrs.getPositionDescription()
+ + ": Cannot access non-public constructor " + className, e);
+ } catch (ClassCastException e) {
+ throw new IllegalStateException(attrs.getPositionDescription()
+ + ": Class is not a LayoutManager " + className, e);
+ }
+ }
+ }
+ }
+
+ private String getFullClassName(Context context, String className) {
+ if (className.charAt(0) == '.') {
+ return context.getPackageName() + className;
+ }
+ if (className.contains(".")) {
+ return className;
+ }
+ return RecyclerView.class.getPackage().getName() + '.' + className;
+ }
+
+ private void initChildrenHelper() {
+ mChildHelper = new ChildHelper(new ChildHelper.Callback() {
+ @Override
+ public int getChildCount() {
+ return RecyclerView.this.getChildCount();
+ }
+
+ @Override
+ public void addView(View child, int index) {
+ RecyclerView.this.addView(child, index);
+ dispatchChildAttached(child);
+ }
+
+ @Override
+ public int indexOfChild(View view) {
+ return RecyclerView.this.indexOfChild(view);
+ }
+
+ @Override
+ public void removeViewAt(int index) {
+ final View child = RecyclerView.this.getChildAt(index);
+ if (child != null) {
+ dispatchChildDetached(child);
+ }
+ RecyclerView.this.removeViewAt(index);
+ }
+
+ @Override
+ public View getChildAt(int offset) {
+ return RecyclerView.this.getChildAt(offset);
+ }
+
+ @Override
+ public void removeAllViews() {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ dispatchChildDetached(getChildAt(i));
+ }
+ RecyclerView.this.removeAllViews();
+ }
+
+ @Override
+ public ViewHolder getChildViewHolder(View view) {
+ return getChildViewHolderInt(view);
+ }
+
+ @Override
+ public void attachViewToParent(View child, int index,
+ ViewGroup.LayoutParams layoutParams) {
+ final ViewHolder vh = getChildViewHolderInt(child);
+ if (vh != null) {
+ if (!vh.isTmpDetached() && !vh.shouldIgnore()) {
+ throw new IllegalArgumentException("Called attach on a child which is not"
+ + " detached: " + vh);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "reAttach " + vh);
+ }
+ vh.clearTmpDetachFlag();
+ }
+ RecyclerView.this.attachViewToParent(child, index, layoutParams);
+ }
+
+ @Override
+ public void detachViewFromParent(int offset) {
+ final View view = getChildAt(offset);
+ if (view != null) {
+ final ViewHolder vh = getChildViewHolderInt(view);
+ if (vh != null) {
+ if (vh.isTmpDetached() && !vh.shouldIgnore()) {
+ throw new IllegalArgumentException("called detach on an already"
+ + " detached child " + vh);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "tmpDetach " + vh);
+ }
+ vh.addFlags(ViewHolder.FLAG_TMP_DETACHED);
+ }
+ }
+ RecyclerView.this.detachViewFromParent(offset);
+ }
+
+ @Override
+ public void onEnteredHiddenState(View child) {
+ final ViewHolder vh = getChildViewHolderInt(child);
+ if (vh != null) {
+ vh.onEnteredHiddenState(RecyclerView.this);
+ }
+ }
+
+ @Override
+ public void onLeftHiddenState(View child) {
+ final ViewHolder vh = getChildViewHolderInt(child);
+ if (vh != null) {
+ vh.onLeftHiddenState(RecyclerView.this);
+ }
+ }
+ });
+ }
+
+ void initAdapterManager() {
+ mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
+ @Override
+ public ViewHolder findViewHolder(int position) {
+ final ViewHolder vh = findViewHolderForPosition(position, true);
+ if (vh == null) {
+ return null;
+ }
+ // ensure it is not hidden because for adapter helper, the only thing matter is that
+ // LM thinks view is a child.
+ if (mChildHelper.isHidden(vh.itemView)) {
+ if (DEBUG) {
+ Log.d(TAG, "assuming view holder cannot be find because it is hidden");
+ }
+ return null;
+ }
+ return vh;
+ }
+
+ @Override
+ public void offsetPositionsForRemovingInvisible(int start, int count) {
+ offsetPositionRecordsForRemove(start, count, true);
+ mItemsAddedOrRemoved = true;
+ mState.mDeletedInvisibleItemCountSincePreviousLayout += count;
+ }
+
+ @Override
+ public void offsetPositionsForRemovingLaidOutOrNewView(
+ int positionStart, int itemCount) {
+ offsetPositionRecordsForRemove(positionStart, itemCount, false);
+ mItemsAddedOrRemoved = true;
+ }
+
+ @Override
+ public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {
+ viewRangeUpdate(positionStart, itemCount, payload);
+ mItemsChanged = true;
+ }
+
+ @Override
+ public void onDispatchFirstPass(AdapterHelper.UpdateOp op) {
+ dispatchUpdate(op);
+ }
+
+ void dispatchUpdate(AdapterHelper.UpdateOp op) {
+ switch (op.cmd) {
+ case AdapterHelper.UpdateOp.ADD:
+ mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount);
+ break;
+ case AdapterHelper.UpdateOp.REMOVE:
+ mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount);
+ break;
+ case AdapterHelper.UpdateOp.UPDATE:
+ mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount,
+ op.payload);
+ break;
+ case AdapterHelper.UpdateOp.MOVE:
+ mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1);
+ break;
+ }
+ }
+
+ @Override
+ public void onDispatchSecondPass(AdapterHelper.UpdateOp op) {
+ dispatchUpdate(op);
+ }
+
+ @Override
+ public void offsetPositionsForAdd(int positionStart, int itemCount) {
+ offsetPositionRecordsForInsert(positionStart, itemCount);
+ mItemsAddedOrRemoved = true;
+ }
+
+ @Override
+ public void offsetPositionsForMove(int from, int to) {
+ offsetPositionRecordsForMove(from, to);
+ // should we create mItemsMoved ?
+ mItemsAddedOrRemoved = true;
+ }
+ });
+ }
+
+ /**
+ * RecyclerView can perform several optimizations if it can know in advance that RecyclerView's
+ * size is not affected by the adapter contents. RecyclerView can still change its size based
+ * on other factors (e.g. its parent's size) but this size calculation cannot depend on the
+ * size of its children or contents of its adapter (except the number of items in the adapter).
+ * <p>
+ * If your use of RecyclerView falls into this category, set this to {@code true}. It will allow
+ * RecyclerView to avoid invalidating the whole layout when its adapter contents change.
+ *
+ * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView.
+ */
+ public void setHasFixedSize(boolean hasFixedSize) {
+ mHasFixedSize = hasFixedSize;
+ }
+
+ /**
+ * @return true if the app has specified that changes in adapter content cannot change
+ * the size of the RecyclerView itself.
+ */
+ public boolean hasFixedSize() {
+ return mHasFixedSize;
+ }
+
+ @Override
+ public void setClipToPadding(boolean clipToPadding) {
+ if (clipToPadding != mClipToPadding) {
+ invalidateGlows();
+ }
+ mClipToPadding = clipToPadding;
+ super.setClipToPadding(clipToPadding);
+ if (mFirstLayoutComplete) {
+ requestLayout();
+ }
+ }
+
+ /**
+ * Returns whether this RecyclerView will clip its children to its padding, and resize (but
+ * not clip) any EdgeEffect to the padded region, if padding is present.
+ * <p>
+ * By default, children are clipped to the padding of their parent
+ * RecyclerView. This clipping behavior is only enabled if padding is non-zero.
+ *
+ * @return true if this RecyclerView clips children to its padding and resizes (but doesn't
+ * clip) any EdgeEffect to the padded region, false otherwise.
+ *
+ * @attr name android:clipToPadding
+ */
+ @Override
+ public boolean getClipToPadding() {
+ return mClipToPadding;
+ }
+
+ /**
+ * Configure the scrolling touch slop for a specific use case.
+ *
+ * Set up the RecyclerView's scrolling motion threshold based on common usages.
+ * Valid arguments are {@link #TOUCH_SLOP_DEFAULT} and {@link #TOUCH_SLOP_PAGING}.
+ *
+ * @param slopConstant One of the <code>TOUCH_SLOP_</code> constants representing
+ * the intended usage of this RecyclerView
+ */
+ public void setScrollingTouchSlop(int slopConstant) {
+ final ViewConfiguration vc = ViewConfiguration.get(getContext());
+ switch (slopConstant) {
+ default:
+ Log.w(TAG, "setScrollingTouchSlop(): bad argument constant "
+ + slopConstant + "; using default value");
+ // fall-through
+ case TOUCH_SLOP_DEFAULT:
+ mTouchSlop = vc.getScaledTouchSlop();
+ break;
+
+ case TOUCH_SLOP_PAGING:
+ mTouchSlop = vc.getScaledPagingTouchSlop();
+ break;
+ }
+ }
+
+ /**
+ * Swaps the current adapter with the provided one. It is similar to
+ * {@link #setAdapter(Adapter)} but assumes existing adapter and the new adapter uses the same
+ * {@link ViewHolder} and does not clear the RecycledViewPool.
+ * <p>
+ * Note that it still calls onAdapterChanged callbacks.
+ *
+ * @param adapter The new adapter to set, or null to set no adapter.
+ * @param removeAndRecycleExistingViews If set to true, RecyclerView will recycle all existing
+ * Views. If adapters have stable ids and/or you want to
+ * animate the disappearing views, you may prefer to set
+ * this to false.
+ * @see #setAdapter(Adapter)
+ */
+ public void swapAdapter(Adapter adapter, boolean removeAndRecycleExistingViews) {
+ // bail out if layout is frozen
+ setLayoutFrozen(false);
+ setAdapterInternal(adapter, true, removeAndRecycleExistingViews);
+ setDataSetChangedAfterLayout();
+ requestLayout();
+ }
+ /**
+ * Set a new adapter to provide child views on demand.
+ * <p>
+ * When adapter is changed, all existing views are recycled back to the pool. If the pool has
+ * only one adapter, it will be cleared.
+ *
+ * @param adapter The new adapter to set, or null to set no adapter.
+ * @see #swapAdapter(Adapter, boolean)
+ */
+ public void setAdapter(Adapter adapter) {
+ // bail out if layout is frozen
+ setLayoutFrozen(false);
+ setAdapterInternal(adapter, false, true);
+ requestLayout();
+ }
+
+ /**
+ * Removes and recycles all views - both those currently attached, and those in the Recycler.
+ */
+ void removeAndRecycleViews() {
+ // end all running animations
+ if (mItemAnimator != null) {
+ mItemAnimator.endAnimations();
+ }
+ // Since animations are ended, mLayout.children should be equal to
+ // recyclerView.children. This may not be true if item animator's end does not work as
+ // expected. (e.g. not release children instantly). It is safer to use mLayout's child
+ // count.
+ if (mLayout != null) {
+ mLayout.removeAndRecycleAllViews(mRecycler);
+ mLayout.removeAndRecycleScrapInt(mRecycler);
+ }
+ // we should clear it here before adapters are swapped to ensure correct callbacks.
+ mRecycler.clear();
+ }
+
+ /**
+ * Replaces the current adapter with the new one and triggers listeners.
+ * @param adapter The new adapter
+ * @param compatibleWithPrevious If true, the new adapter is using the same View Holders and
+ * item types with the current adapter (helps us avoid cache
+ * invalidation).
+ * @param removeAndRecycleViews If true, we'll remove and recycle all existing views. If
+ * compatibleWithPrevious is false, this parameter is ignored.
+ */
+ private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
+ boolean removeAndRecycleViews) {
+ if (mAdapter != null) {
+ mAdapter.unregisterAdapterDataObserver(mObserver);
+ mAdapter.onDetachedFromRecyclerView(this);
+ }
+ if (!compatibleWithPrevious || removeAndRecycleViews) {
+ removeAndRecycleViews();
+ }
+ mAdapterHelper.reset();
+ final Adapter oldAdapter = mAdapter;
+ mAdapter = adapter;
+ if (adapter != null) {
+ adapter.registerAdapterDataObserver(mObserver);
+ adapter.onAttachedToRecyclerView(this);
+ }
+ if (mLayout != null) {
+ mLayout.onAdapterChanged(oldAdapter, mAdapter);
+ }
+ mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
+ mState.mStructureChanged = true;
+ markKnownViewsInvalid();
+ }
+
+ /**
+ * Retrieves the previously set adapter or null if no adapter is set.
+ *
+ * @return The previously set adapter
+ * @see #setAdapter(Adapter)
+ */
+ public Adapter getAdapter() {
+ return mAdapter;
+ }
+
+ /**
+ * Register a listener that will be notified whenever a child view is recycled.
+ *
+ * <p>This listener will be called when a LayoutManager or the RecyclerView decides
+ * that a child view is no longer needed. If an application associates expensive
+ * or heavyweight data with item views, this may be a good place to release
+ * or free those resources.</p>
+ *
+ * @param listener Listener to register, or null to clear
+ */
+ public void setRecyclerListener(RecyclerListener listener) {
+ mRecyclerListener = listener;
+ }
+
+ /**
+ * <p>Return the offset of the RecyclerView's text baseline from the its top
+ * boundary. If the LayoutManager of this RecyclerView does not support baseline alignment,
+ * this method returns -1.</p>
+ *
+ * @return the offset of the baseline within the RecyclerView's bounds or -1
+ * if baseline alignment is not supported
+ */
+ @Override
+ public int getBaseline() {
+ if (mLayout != null) {
+ return mLayout.getBaseline();
+ } else {
+ return super.getBaseline();
+ }
+ }
+
+ /**
+ * Register a listener that will be notified whenever a child view is attached to or detached
+ * from RecyclerView.
+ *
+ * <p>This listener will be called when a LayoutManager or the RecyclerView decides
+ * that a child view is no longer needed. If an application associates expensive
+ * or heavyweight data with item views, this may be a good place to release
+ * or free those resources.</p>
+ *
+ * @param listener Listener to register
+ */
+ public void addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) {
+ if (mOnChildAttachStateListeners == null) {
+ mOnChildAttachStateListeners = new ArrayList<>();
+ }
+ mOnChildAttachStateListeners.add(listener);
+ }
+
+ /**
+ * Removes the provided listener from child attached state listeners list.
+ *
+ * @param listener Listener to unregister
+ */
+ public void removeOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) {
+ if (mOnChildAttachStateListeners == null) {
+ return;
+ }
+ mOnChildAttachStateListeners.remove(listener);
+ }
+
+ /**
+ * Removes all listeners that were added via
+ * {@link #addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener)}.
+ */
+ public void clearOnChildAttachStateChangeListeners() {
+ if (mOnChildAttachStateListeners != null) {
+ mOnChildAttachStateListeners.clear();
+ }
+ }
+
+ /**
+ * Set the {@link LayoutManager} that this RecyclerView will use.
+ *
+ * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView}
+ * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom
+ * layout arrangements for child views. These arrangements are controlled by the
+ * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p>
+ *
+ * <p>Several default strategies are provided for common uses such as lists and grids.</p>
+ *
+ * @param layout LayoutManager to use
+ */
+ public void setLayoutManager(LayoutManager layout) {
+ if (layout == mLayout) {
+ return;
+ }
+ stopScroll();
+ // TODO We should do this switch a dispatchLayout pass and animate children. There is a good
+ // chance that LayoutManagers will re-use views.
+ if (mLayout != null) {
+ // end all running animations
+ if (mItemAnimator != null) {
+ mItemAnimator.endAnimations();
+ }
+ mLayout.removeAndRecycleAllViews(mRecycler);
+ mLayout.removeAndRecycleScrapInt(mRecycler);
+ mRecycler.clear();
+
+ if (mIsAttached) {
+ mLayout.dispatchDetachedFromWindow(this, mRecycler);
+ }
+ mLayout.setRecyclerView(null);
+ mLayout = null;
+ } else {
+ mRecycler.clear();
+ }
+ // this is just a defensive measure for faulty item animators.
+ mChildHelper.removeAllViewsUnfiltered();
+ mLayout = layout;
+ if (layout != null) {
+ if (layout.mRecyclerView != null) {
+ throw new IllegalArgumentException("LayoutManager " + layout
+ + " is already attached to a RecyclerView: " + layout.mRecyclerView);
+ }
+ mLayout.setRecyclerView(this);
+ if (mIsAttached) {
+ mLayout.dispatchAttachedToWindow(this);
+ }
+ }
+ mRecycler.updateViewCacheSize();
+ requestLayout();
+ }
+
+ /**
+ * Set a {@link OnFlingListener} for this {@link RecyclerView}.
+ * <p>
+ * If the {@link OnFlingListener} is set then it will receive
+ * calls to {@link #fling(int,int)} and will be able to intercept them.
+ *
+ * @param onFlingListener The {@link OnFlingListener} instance.
+ */
+ public void setOnFlingListener(@Nullable OnFlingListener onFlingListener) {
+ mOnFlingListener = onFlingListener;
+ }
+
+ /**
+ * Get the current {@link OnFlingListener} from this {@link RecyclerView}.
+ *
+ * @return The {@link OnFlingListener} instance currently set (can be null).
+ */
+ @Nullable
+ public OnFlingListener getOnFlingListener() {
+ return mOnFlingListener;
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ SavedState state = new SavedState(super.onSaveInstanceState());
+ if (mPendingSavedState != null) {
+ state.copyFrom(mPendingSavedState);
+ } else if (mLayout != null) {
+ state.mLayoutState = mLayout.onSaveInstanceState();
+ } else {
+ state.mLayoutState = null;
+ }
+
+ return state;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ mPendingSavedState = (SavedState) state;
+ super.onRestoreInstanceState(mPendingSavedState.getSuperState());
+ if (mLayout != null && mPendingSavedState.mLayoutState != null) {
+ mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState);
+ }
+ }
+
+ /**
+ * Override to prevent freezing of any views created by the adapter.
+ */
+ @Override
+ protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
+ dispatchFreezeSelfOnly(container);
+ }
+
+ /**
+ * Override to prevent thawing of any views created by the adapter.
+ */
+ @Override
+ protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+ dispatchThawSelfOnly(container);
+ }
+
+ /**
+ * Adds a view to the animatingViews list.
+ * mAnimatingViews holds the child views that are currently being kept around
+ * purely for the purpose of being animated out of view. They are drawn as a regular
+ * part of the child list of the RecyclerView, but they are invisible to the LayoutManager
+ * as they are managed separately from the regular child views.
+ * @param viewHolder The ViewHolder to be removed
+ */
+ private void addAnimatingView(ViewHolder viewHolder) {
+ final View view = viewHolder.itemView;
+ final boolean alreadyParented = view.getParent() == this;
+ mRecycler.unscrapView(getChildViewHolder(view));
+ if (viewHolder.isTmpDetached()) {
+ // re-attach
+ mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
+ } else if (!alreadyParented) {
+ mChildHelper.addView(view, true);
+ } else {
+ mChildHelper.hide(view);
+ }
+ }
+
+ /**
+ * Removes a view from the animatingViews list.
+ * @param view The view to be removed
+ * @see #addAnimatingView(RecyclerView.ViewHolder)
+ * @return true if an animating view is removed
+ */
+ boolean removeAnimatingView(View view) {
+ eatRequestLayout();
+ final boolean removed = mChildHelper.removeViewIfHidden(view);
+ if (removed) {
+ final ViewHolder viewHolder = getChildViewHolderInt(view);
+ mRecycler.unscrapView(viewHolder);
+ mRecycler.recycleViewHolderInternal(viewHolder);
+ if (DEBUG) {
+ Log.d(TAG, "after removing animated view: " + view + ", " + this);
+ }
+ }
+ // only clear request eaten flag if we removed the view.
+ resumeRequestLayout(!removed);
+ return removed;
+ }
+
+ /**
+ * Return the {@link LayoutManager} currently responsible for
+ * layout policy for this RecyclerView.
+ *
+ * @return The currently bound LayoutManager
+ */
+ public LayoutManager getLayoutManager() {
+ return mLayout;
+ }
+
+ /**
+ * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null;
+ * if no pool is set for this view a new one will be created. See
+ * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information.
+ *
+ * @return The pool used to store recycled item views for reuse.
+ * @see #setRecycledViewPool(RecycledViewPool)
+ */
+ public RecycledViewPool getRecycledViewPool() {
+ return mRecycler.getRecycledViewPool();
+ }
+
+ /**
+ * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views.
+ * This can be useful if you have multiple RecyclerViews with adapters that use the same
+ * view types, for example if you have several data sets with the same kinds of item views
+ * displayed by a {@link android.support.v4.view.ViewPager ViewPager}.
+ *
+ * @param pool Pool to set. If this parameter is null a new pool will be created and used.
+ */
+ public void setRecycledViewPool(RecycledViewPool pool) {
+ mRecycler.setRecycledViewPool(pool);
+ }
+
+ /**
+ * Sets a new {@link ViewCacheExtension} to be used by the Recycler.
+ *
+ * @param extension ViewCacheExtension to be used or null if you want to clear the existing one.
+ *
+ * @see {@link ViewCacheExtension#getViewForPositionAndType(Recycler, int, int)}
+ */
+ public void setViewCacheExtension(ViewCacheExtension extension) {
+ mRecycler.setViewCacheExtension(extension);
+ }
+
+ /**
+ * Set the number of offscreen views to retain before adding them to the potentially shared
+ * {@link #getRecycledViewPool() recycled view pool}.
+ *
+ * <p>The offscreen view cache stays aware of changes in the attached adapter, allowing
+ * a LayoutManager to reuse those views unmodified without needing to return to the adapter
+ * to rebind them.</p>
+ *
+ * @param size Number of views to cache offscreen before returning them to the general
+ * recycled view pool
+ */
+ public void setItemViewCacheSize(int size) {
+ mRecycler.setViewCacheSize(size);
+ }
+
+ /**
+ * Return the current scrolling state of the RecyclerView.
+ *
+ * @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or
+ * {@link #SCROLL_STATE_SETTLING}
+ */
+ public int getScrollState() {
+ return mScrollState;
+ }
+
+ void setScrollState(int state) {
+ if (state == mScrollState) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState,
+ new Exception());
+ }
+ mScrollState = state;
+ if (state != SCROLL_STATE_SETTLING) {
+ stopScrollersInternal();
+ }
+ dispatchOnScrollStateChanged(state);
+ }
+
+ /**
+ * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
+ * affect both measurement and drawing of individual item views.
+ *
+ * <p>Item decorations are ordered. Decorations placed earlier in the list will
+ * be run/queried/drawn first for their effects on item views. Padding added to views
+ * will be nested; a padding added by an earlier decoration will mean further
+ * item decorations in the list will be asked to draw/pad within the previous decoration's
+ * given area.</p>
+ *
+ * @param decor Decoration to add
+ * @param index Position in the decoration chain to insert this decoration at. If this value
+ * is negative the decoration will be added at the end.
+ */
+ public void addItemDecoration(ItemDecoration decor, int index) {
+ if (mLayout != null) {
+ mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or"
+ + " layout");
+ }
+ if (mItemDecorations.isEmpty()) {
+ setWillNotDraw(false);
+ }
+ if (index < 0) {
+ mItemDecorations.add(decor);
+ } else {
+ mItemDecorations.add(index, decor);
+ }
+ markItemDecorInsetsDirty();
+ requestLayout();
+ }
+
+ /**
+ * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
+ * affect both measurement and drawing of individual item views.
+ *
+ * <p>Item decorations are ordered. Decorations placed earlier in the list will
+ * be run/queried/drawn first for their effects on item views. Padding added to views
+ * will be nested; a padding added by an earlier decoration will mean further
+ * item decorations in the list will be asked to draw/pad within the previous decoration's
+ * given area.</p>
+ *
+ * @param decor Decoration to add
+ */
+ public void addItemDecoration(ItemDecoration decor) {
+ addItemDecoration(decor, -1);
+ }
+
+ /**
+ * Remove an {@link ItemDecoration} from this RecyclerView.
+ *
+ * <p>The given decoration will no longer impact the measurement and drawing of
+ * item views.</p>
+ *
+ * @param decor Decoration to remove
+ * @see #addItemDecoration(ItemDecoration)
+ */
+ public void removeItemDecoration(ItemDecoration decor) {
+ if (mLayout != null) {
+ mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll or"
+ + " layout");
+ }
+ mItemDecorations.remove(decor);
+ if (mItemDecorations.isEmpty()) {
+ setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);
+ }
+ markItemDecorInsetsDirty();
+ requestLayout();
+ }
+
+ /**
+ * Sets the {@link ChildDrawingOrderCallback} to be used for drawing children.
+ * <p>
+ * See {@link ViewGroup#getChildDrawingOrder(int, int)} for details. Calling this method will
+ * always call {@link ViewGroup#setChildrenDrawingOrderEnabled(boolean)}. The parameter will be
+ * true if childDrawingOrderCallback is not null, false otherwise.
+ * <p>
+ * Note that child drawing order may be overridden by View's elevation.
+ *
+ * @param childDrawingOrderCallback The ChildDrawingOrderCallback to be used by the drawing
+ * system.
+ */
+ public void setChildDrawingOrderCallback(ChildDrawingOrderCallback childDrawingOrderCallback) {
+ if (childDrawingOrderCallback == mChildDrawingOrderCallback) {
+ return;
+ }
+ mChildDrawingOrderCallback = childDrawingOrderCallback;
+ setChildrenDrawingOrderEnabled(mChildDrawingOrderCallback != null);
+ }
+
+ /**
+ * Set a listener that will be notified of any changes in scroll state or position.
+ *
+ * @param listener Listener to set or null to clear
+ *
+ * @deprecated Use {@link #addOnScrollListener(OnScrollListener)} and
+ * {@link #removeOnScrollListener(OnScrollListener)}
+ */
+ @Deprecated
+ public void setOnScrollListener(OnScrollListener listener) {
+ mScrollListener = listener;
+ }
+
+ /**
+ * Add a listener that will be notified of any changes in scroll state or position.
+ *
+ * <p>Components that add a listener should take care to remove it when finished.
+ * Other components that take ownership of a view may call {@link #clearOnScrollListeners()}
+ * to remove all attached listeners.</p>
+ *
+ * @param listener listener to set or null to clear
+ */
+ public void addOnScrollListener(OnScrollListener listener) {
+ if (mScrollListeners == null) {
+ mScrollListeners = new ArrayList<>();
+ }
+ mScrollListeners.add(listener);
+ }
+
+ /**
+ * Remove a listener that was notified of any changes in scroll state or position.
+ *
+ * @param listener listener to set or null to clear
+ */
+ public void removeOnScrollListener(OnScrollListener listener) {
+ if (mScrollListeners != null) {
+ mScrollListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Remove all secondary listener that were notified of any changes in scroll state or position.
+ */
+ public void clearOnScrollListeners() {
+ if (mScrollListeners != null) {
+ mScrollListeners.clear();
+ }
+ }
+
+ /**
+ * Convenience method to scroll to a certain position.
+ *
+ * RecyclerView does not implement scrolling logic, rather forwards the call to
+ * {@link com.android.internal.widget.RecyclerView.LayoutManager#scrollToPosition(int)}
+ * @param position Scroll to this adapter position
+ * @see com.android.internal.widget.RecyclerView.LayoutManager#scrollToPosition(int)
+ */
+ public void scrollToPosition(int position) {
+ if (mLayoutFrozen) {
+ return;
+ }
+ stopScroll();
+ if (mLayout == null) {
+ Log.e(TAG, "Cannot scroll to position a LayoutManager set. "
+ + "Call setLayoutManager with a non-null argument.");
+ return;
+ }
+ mLayout.scrollToPosition(position);
+ awakenScrollBars();
+ }
+
+ void jumpToPositionForSmoothScroller(int position) {
+ if (mLayout == null) {
+ return;
+ }
+ mLayout.scrollToPosition(position);
+ awakenScrollBars();
+ }
+
+ /**
+ * Starts a smooth scroll to an adapter position.
+ * <p>
+ * To support smooth scrolling, you must override
+ * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a
+ * {@link SmoothScroller}.
+ * <p>
+ * {@link LayoutManager} is responsible for creating the actual scroll action. If you want to
+ * provide a custom smooth scroll logic, override
+ * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your
+ * LayoutManager.
+ *
+ * @param position The adapter position to scroll to
+ * @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int)
+ */
+ public void smoothScrollToPosition(int position) {
+ if (mLayoutFrozen) {
+ return;
+ }
+ if (mLayout == null) {
+ Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. "
+ + "Call setLayoutManager with a non-null argument.");
+ return;
+ }
+ mLayout.smoothScrollToPosition(this, mState, position);
+ }
+
+ @Override
+ public void scrollTo(int x, int y) {
+ Log.w(TAG, "RecyclerView does not support scrolling to an absolute position. "
+ + "Use scrollToPosition instead");
+ }
+
+ @Override
+ public void scrollBy(int x, int y) {
+ if (mLayout == null) {
+ Log.e(TAG, "Cannot scroll without a LayoutManager set. "
+ + "Call setLayoutManager with a non-null argument.");
+ return;
+ }
+ if (mLayoutFrozen) {
+ return;
+ }
+ final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
+ final boolean canScrollVertical = mLayout.canScrollVertically();
+ if (canScrollHorizontal || canScrollVertical) {
+ scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0, null);
+ }
+ }
+
+ /**
+ * Helper method reflect data changes to the state.
+ * <p>
+ * Adapter changes during a scroll may trigger a crash because scroll assumes no data change
+ * but data actually changed.
+ * <p>
+ * This method consumes all deferred changes to avoid that case.
+ */
+ void consumePendingUpdateOperations() {
+ if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) {
+ Trace.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
+ dispatchLayout();
+ Trace.endSection();
+ return;
+ }
+ if (!mAdapterHelper.hasPendingUpdates()) {
+ return;
+ }
+
+ // if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any
+ // of the visible items is affected and if not, just ignore the change.
+ if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper
+ .hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE
+ | AdapterHelper.UpdateOp.MOVE)) {
+ Trace.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG);
+ eatRequestLayout();
+ onEnterLayoutOrScroll();
+ mAdapterHelper.preProcess();
+ if (!mLayoutRequestEaten) {
+ if (hasUpdatedView()) {
+ dispatchLayout();
+ } else {
+ // no need to layout, clean state
+ mAdapterHelper.consumePostponedUpdates();
+ }
+ }
+ resumeRequestLayout(true);
+ onExitLayoutOrScroll();
+ Trace.endSection();
+ } else if (mAdapterHelper.hasPendingUpdates()) {
+ Trace.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
+ dispatchLayout();
+ Trace.endSection();
+ }
+ }
+
+ /**
+ * @return True if an existing view holder needs to be updated
+ */
+ private boolean hasUpdatedView() {
+ final int childCount = mChildHelper.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+ if (holder == null || holder.shouldIgnore()) {
+ continue;
+ }
+ if (holder.isUpdated()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Does not perform bounds checking. Used by internal methods that have already validated input.
+ * <p>
+ * It also reports any unused scroll request to the related EdgeEffect.
+ *
+ * @param x The amount of horizontal scroll request
+ * @param y The amount of vertical scroll request
+ * @param ev The originating MotionEvent, or null if not from a touch event.
+ *
+ * @return Whether any scroll was consumed in either direction.
+ */
+ boolean scrollByInternal(int x, int y, MotionEvent ev) {
+ int unconsumedX = 0, unconsumedY = 0;
+ int consumedX = 0, consumedY = 0;
+
+ consumePendingUpdateOperations();
+ if (mAdapter != null) {
+ eatRequestLayout();
+ onEnterLayoutOrScroll();
+ Trace.beginSection(TRACE_SCROLL_TAG);
+ if (x != 0) {
+ consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
+ unconsumedX = x - consumedX;
+ }
+ if (y != 0) {
+ consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
+ unconsumedY = y - consumedY;
+ }
+ Trace.endSection();
+ repositionShadowingViews();
+ onExitLayoutOrScroll();
+ resumeRequestLayout(false);
+ }
+ if (!mItemDecorations.isEmpty()) {
+ invalidate();
+ }
+
+ if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset)) {
+ // Update the last touch co-ords, taking any scroll offset into account
+ mLastTouchX -= mScrollOffset[0];
+ mLastTouchY -= mScrollOffset[1];
+ if (ev != null) {
+ ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
+ }
+ mNestedOffsets[0] += mScrollOffset[0];
+ mNestedOffsets[1] += mScrollOffset[1];
+ } else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
+ if (ev != null) {
+ pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);
+ }
+ considerReleasingGlowsOnScroll(x, y);
+ }
+ if (consumedX != 0 || consumedY != 0) {
+ dispatchOnScrolled(consumedX, consumedY);
+ }
+ if (!awakenScrollBars()) {
+ invalidate();
+ }
+ return consumedX != 0 || consumedY != 0;
+ }
+
+ /**
+ * <p>Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal
+ * range. This value is used to compute the length of the thumb within the scrollbar's track.
+ * </p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the units used by
+ * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollExtent()}.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * <p>If you want to support scroll bars, override
+ * {@link RecyclerView.LayoutManager#computeHorizontalScrollOffset(RecyclerView.State)} in your
+ * LayoutManager. </p>
+ *
+ * @return The horizontal offset of the scrollbar's thumb
+ * @see com.android.internal.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset
+ * (RecyclerView.State)
+ */
+ @Override
+ public int computeHorizontalScrollOffset() {
+ if (mLayout == null) {
+ return 0;
+ }
+ return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState) : 0;
+ }
+
+ /**
+ * <p>Compute the horizontal extent of the horizontal scrollbar's thumb within the
+ * horizontal range. This value is used to compute the length of the thumb within the
+ * scrollbar's track.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the units used by
+ * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollOffset()}.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * <p>If you want to support scroll bars, override
+ * {@link RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)} in your
+ * LayoutManager.</p>
+ *
+ * @return The horizontal extent of the scrollbar's thumb
+ * @see RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)
+ */
+ @Override
+ public int computeHorizontalScrollExtent() {
+ if (mLayout == null) {
+ return 0;
+ }
+ return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0;
+ }
+
+ /**
+ * <p>Compute the horizontal range that the horizontal scrollbar represents.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the units used by
+ * {@link #computeHorizontalScrollExtent()} and {@link #computeHorizontalScrollOffset()}.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * <p>If you want to support scroll bars, override
+ * {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your
+ * LayoutManager.</p>
+ *
+ * @return The total horizontal range represented by the vertical scrollbar
+ * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)
+ */
+ @Override
+ public int computeHorizontalScrollRange() {
+ if (mLayout == null) {
+ return 0;
+ }
+ return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0;
+ }
+
+ /**
+ * <p>Compute the vertical offset of the vertical scrollbar's thumb within the vertical range.
+ * This value is used to compute the length of the thumb within the scrollbar's track. </p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the units used by
+ * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * <p>If you want to support scroll bars, override
+ * {@link RecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)} in your
+ * LayoutManager.</p>
+ *
+ * @return The vertical offset of the scrollbar's thumb
+ * @see com.android.internal.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset
+ * (RecyclerView.State)
+ */
+ @Override
+ public int computeVerticalScrollOffset() {
+ if (mLayout == null) {
+ return 0;
+ }
+ return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0;
+ }
+
+ /**
+ * <p>Compute the vertical extent of the vertical scrollbar's thumb within the vertical range.
+ * This value is used to compute the length of the thumb within the scrollbar's track.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the units used by
+ * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollOffset()}.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * <p>If you want to support scroll bars, override
+ * {@link RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)} in your
+ * LayoutManager.</p>
+ *
+ * @return The vertical extent of the scrollbar's thumb
+ * @see RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)
+ */
+ @Override
+ public int computeVerticalScrollExtent() {
+ if (mLayout == null) {
+ return 0;
+ }
+ return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0;
+ }
+
+ /**
+ * <p>Compute the vertical range that the vertical scrollbar represents.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the units used by
+ * {@link #computeVerticalScrollExtent()} and {@link #computeVerticalScrollOffset()}.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * <p>If you want to support scroll bars, override
+ * {@link RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)} in your
+ * LayoutManager.</p>
+ *
+ * @return The total vertical range represented by the vertical scrollbar
+ * @see RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)
+ */
+ @Override
+ public int computeVerticalScrollRange() {
+ if (mLayout == null) {
+ return 0;
+ }
+ return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0;
+ }
+
+
+ void eatRequestLayout() {
+ mEatRequestLayout++;
+ if (mEatRequestLayout == 1 && !mLayoutFrozen) {
+ mLayoutRequestEaten = false;
+ }
+ }
+
+ void resumeRequestLayout(boolean performLayoutChildren) {
+ if (mEatRequestLayout < 1) {
+ //noinspection PointlessBooleanExpression
+ if (DEBUG) {
+ throw new IllegalStateException("invalid eat request layout count");
+ }
+ mEatRequestLayout = 1;
+ }
+ if (!performLayoutChildren) {
+ // Reset the layout request eaten counter.
+ // This is necessary since eatRequest calls can be nested in which case the other
+ // call will override the inner one.
+ // for instance:
+ // eat layout for process adapter updates
+ // eat layout for dispatchLayout
+ // a bunch of req layout calls arrive
+
+ mLayoutRequestEaten = false;
+ }
+ if (mEatRequestLayout == 1) {
+ // when layout is frozen we should delay dispatchLayout()
+ if (performLayoutChildren && mLayoutRequestEaten && !mLayoutFrozen
+ && mLayout != null && mAdapter != null) {
+ dispatchLayout();
+ }
+ if (!mLayoutFrozen) {
+ mLayoutRequestEaten = false;
+ }
+ }
+ mEatRequestLayout--;
+ }
+
+ /**
+ * Enable or disable layout and scroll. After <code>setLayoutFrozen(true)</code> is called,
+ * Layout requests will be postponed until <code>setLayoutFrozen(false)</code> is called;
+ * child views are not updated when RecyclerView is frozen, {@link #smoothScrollBy(int, int)},
+ * {@link #scrollBy(int, int)}, {@link #scrollToPosition(int)} and
+ * {@link #smoothScrollToPosition(int)} are dropped; TouchEvents and GenericMotionEvents are
+ * dropped; {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} will not be
+ * called.
+ *
+ * <p>
+ * <code>setLayoutFrozen(true)</code> does not prevent app from directly calling {@link
+ * LayoutManager#scrollToPosition(int)}, {@link LayoutManager#smoothScrollToPosition(
+ * RecyclerView, State, int)}.
+ * <p>
+ * {@link #setAdapter(Adapter)} and {@link #swapAdapter(Adapter, boolean)} will automatically
+ * stop frozen.
+ * <p>
+ * Note: Running ItemAnimator is not stopped automatically, it's caller's
+ * responsibility to call ItemAnimator.end().
+ *
+ * @param frozen true to freeze layout and scroll, false to re-enable.
+ */
+ public void setLayoutFrozen(boolean frozen) {
+ if (frozen != mLayoutFrozen) {
+ assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll");
+ if (!frozen) {
+ mLayoutFrozen = false;
+ if (mLayoutRequestEaten && mLayout != null && mAdapter != null) {
+ requestLayout();
+ }
+ mLayoutRequestEaten = false;
+ } else {
+ final long now = SystemClock.uptimeMillis();
+ MotionEvent cancelEvent = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+ onTouchEvent(cancelEvent);
+ mLayoutFrozen = true;
+ mIgnoreMotionEventTillDown = true;
+ stopScroll();
+ }
+ }
+ }
+
+ /**
+ * Returns true if layout and scroll are frozen.
+ *
+ * @return true if layout and scroll are frozen
+ * @see #setLayoutFrozen(boolean)
+ */
+ public boolean isLayoutFrozen() {
+ return mLayoutFrozen;
+ }
+
+ /**
+ * Animate a scroll by the given amount of pixels along either axis.
+ *
+ * @param dx Pixels to scroll horizontally
+ * @param dy Pixels to scroll vertically
+ */
+ public void smoothScrollBy(int dx, int dy) {
+ smoothScrollBy(dx, dy, null);
+ }
+
+ /**
+ * Animate a scroll by the given amount of pixels along either axis.
+ *
+ * @param dx Pixels to scroll horizontally
+ * @param dy Pixels to scroll vertically
+ * @param interpolator {@link Interpolator} to be used for scrolling. If it is
+ * {@code null}, RecyclerView is going to use the default interpolator.
+ */
+ public void smoothScrollBy(int dx, int dy, Interpolator interpolator) {
+ if (mLayout == null) {
+ Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. "
+ + "Call setLayoutManager with a non-null argument.");
+ return;
+ }
+ if (mLayoutFrozen) {
+ return;
+ }
+ if (!mLayout.canScrollHorizontally()) {
+ dx = 0;
+ }
+ if (!mLayout.canScrollVertically()) {
+ dy = 0;
+ }
+ if (dx != 0 || dy != 0) {
+ mViewFlinger.smoothScrollBy(dx, dy, interpolator);
+ }
+ }
+
+ /**
+ * Begin a standard fling with an initial velocity along each axis in pixels per second.
+ * If the velocity given is below the system-defined minimum this method will return false
+ * and no fling will occur.
+ *
+ * @param velocityX Initial horizontal velocity in pixels per second
+ * @param velocityY Initial vertical velocity in pixels per second
+ * @return true if the fling was started, false if the velocity was too low to fling or
+ * LayoutManager does not support scrolling in the axis fling is issued.
+ *
+ * @see LayoutManager#canScrollVertically()
+ * @see LayoutManager#canScrollHorizontally()
+ */
+ public boolean fling(int velocityX, int velocityY) {
+ if (mLayout == null) {
+ Log.e(TAG, "Cannot fling without a LayoutManager set. "
+ + "Call setLayoutManager with a non-null argument.");
+ return false;
+ }
+ if (mLayoutFrozen) {
+ return false;
+ }
+
+ final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
+ final boolean canScrollVertical = mLayout.canScrollVertically();
+
+ if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) {
+ velocityX = 0;
+ }
+ if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) {
+ velocityY = 0;
+ }
+ if (velocityX == 0 && velocityY == 0) {
+ // If we don't have any velocity, return false
+ return false;
+ }
+
+ if (!dispatchNestedPreFling(velocityX, velocityY)) {
+ final boolean canScroll = canScrollHorizontal || canScrollVertical;
+ dispatchNestedFling(velocityX, velocityY, canScroll);
+
+ if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) {
+ return true;
+ }
+
+ if (canScroll) {
+ velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
+ velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
+ mViewFlinger.fling(velocityX, velocityY);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Stop any current scroll in progress, such as one started by
+ * {@link #smoothScrollBy(int, int)}, {@link #fling(int, int)} or a touch-initiated fling.
+ */
+ public void stopScroll() {
+ setScrollState(SCROLL_STATE_IDLE);
+ stopScrollersInternal();
+ }
+
+ /**
+ * Similar to {@link #stopScroll()} but does not set the state.
+ */
+ private void stopScrollersInternal() {
+ mViewFlinger.stop();
+ if (mLayout != null) {
+ mLayout.stopSmoothScroller();
+ }
+ }
+
+ /**
+ * Returns the minimum velocity to start a fling.
+ *
+ * @return The minimum velocity to start a fling
+ */
+ public int getMinFlingVelocity() {
+ return mMinFlingVelocity;
+ }
+
+
+ /**
+ * Returns the maximum fling velocity used by this RecyclerView.
+ *
+ * @return The maximum fling velocity used by this RecyclerView.
+ */
+ public int getMaxFlingVelocity() {
+ return mMaxFlingVelocity;
+ }
+
+ /**
+ * Apply a pull to relevant overscroll glow effects
+ */
+ private void pullGlows(float x, float overscrollX, float y, float overscrollY) {
+ boolean invalidate = false;
+ if (overscrollX < 0) {
+ ensureLeftGlow();
+ mLeftGlow.onPull(-overscrollX / getWidth(), 1f - y / getHeight());
+ invalidate = true;
+ } else if (overscrollX > 0) {
+ ensureRightGlow();
+ mRightGlow.onPull(overscrollX / getWidth(), y / getHeight());
+ invalidate = true;
+ }
+
+ if (overscrollY < 0) {
+ ensureTopGlow();
+ mTopGlow.onPull(-overscrollY / getHeight(), x / getWidth());
+ invalidate = true;
+ } else if (overscrollY > 0) {
+ ensureBottomGlow();
+ mBottomGlow.onPull(overscrollY / getHeight(), 1f - x / getWidth());
+ invalidate = true;
+ }
+
+ if (invalidate || overscrollX != 0 || overscrollY != 0) {
+ postInvalidateOnAnimation();
+ }
+ }
+
+ private void releaseGlows() {
+ boolean needsInvalidate = false;
+ if (mLeftGlow != null) {
+ mLeftGlow.onRelease();
+ needsInvalidate = true;
+ }
+ if (mTopGlow != null) {
+ mTopGlow.onRelease();
+ needsInvalidate = true;
+ }
+ if (mRightGlow != null) {
+ mRightGlow.onRelease();
+ needsInvalidate = true;
+ }
+ if (mBottomGlow != null) {
+ mBottomGlow.onRelease();
+ needsInvalidate = true;
+ }
+ if (needsInvalidate) {
+ postInvalidateOnAnimation();
+ }
+ }
+
+ void considerReleasingGlowsOnScroll(int dx, int dy) {
+ boolean needsInvalidate = false;
+ if (mLeftGlow != null && !mLeftGlow.isFinished() && dx > 0) {
+ mLeftGlow.onRelease();
+ needsInvalidate = true;
+ }
+ if (mRightGlow != null && !mRightGlow.isFinished() && dx < 0) {
+ mRightGlow.onRelease();
+ needsInvalidate = true;
+ }
+ if (mTopGlow != null && !mTopGlow.isFinished() && dy > 0) {
+ mTopGlow.onRelease();
+ needsInvalidate = true;
+ }
+ if (mBottomGlow != null && !mBottomGlow.isFinished() && dy < 0) {
+ mBottomGlow.onRelease();
+ needsInvalidate = true;
+ }
+ if (needsInvalidate) {
+ postInvalidateOnAnimation();
+ }
+ }
+
+ void absorbGlows(int velocityX, int velocityY) {
+ if (velocityX < 0) {
+ ensureLeftGlow();
+ mLeftGlow.onAbsorb(-velocityX);
+ } else if (velocityX > 0) {
+ ensureRightGlow();
+ mRightGlow.onAbsorb(velocityX);
+ }
+
+ if (velocityY < 0) {
+ ensureTopGlow();
+ mTopGlow.onAbsorb(-velocityY);
+ } else if (velocityY > 0) {
+ ensureBottomGlow();
+ mBottomGlow.onAbsorb(velocityY);
+ }
+
+ if (velocityX != 0 || velocityY != 0) {
+ postInvalidateOnAnimation();
+ }
+ }
+
+ void ensureLeftGlow() {
+ if (mLeftGlow != null) {
+ return;
+ }
+ mLeftGlow = new EdgeEffect(getContext());
+ if (mClipToPadding) {
+ mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
+ getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
+ } else {
+ mLeftGlow.setSize(getMeasuredHeight(), getMeasuredWidth());
+ }
+ }
+
+ void ensureRightGlow() {
+ if (mRightGlow != null) {
+ return;
+ }
+ mRightGlow = new EdgeEffect(getContext());
+ if (mClipToPadding) {
+ mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
+ getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
+ } else {
+ mRightGlow.setSize(getMeasuredHeight(), getMeasuredWidth());
+ }
+ }
+
+ void ensureTopGlow() {
+ if (mTopGlow != null) {
+ return;
+ }
+ mTopGlow = new EdgeEffect(getContext());
+ if (mClipToPadding) {
+ mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
+ getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
+ } else {
+ mTopGlow.setSize(getMeasuredWidth(), getMeasuredHeight());
+ }
+
+ }
+
+ void ensureBottomGlow() {
+ if (mBottomGlow != null) {
+ return;
+ }
+ mBottomGlow = new EdgeEffect(getContext());
+ if (mClipToPadding) {
+ mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
+ getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
+ } else {
+ mBottomGlow.setSize(getMeasuredWidth(), getMeasuredHeight());
+ }
+ }
+
+ void invalidateGlows() {
+ mLeftGlow = mRightGlow = mTopGlow = mBottomGlow = null;
+ }
+
+ /**
+ * Since RecyclerView is a collection ViewGroup that includes virtual children (items that are
+ * in the Adapter but not visible in the UI), it employs a more involved focus search strategy
+ * that differs from other ViewGroups.
+ * <p>
+ * It first does a focus search within the RecyclerView. If this search finds a View that is in
+ * the focus direction with respect to the currently focused View, RecyclerView returns that
+ * child as the next focus target. When it cannot find such child, it calls
+ * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} to layout more Views
+ * in the focus search direction. If LayoutManager adds a View that matches the
+ * focus search criteria, it will be returned as the focus search result. Otherwise,
+ * RecyclerView will call parent to handle the focus search like a regular ViewGroup.
+ * <p>
+ * When the direction is {@link View#FOCUS_FORWARD} or {@link View#FOCUS_BACKWARD}, a View that
+ * is not in the focus direction is still valid focus target which may not be the desired
+ * behavior if the Adapter has more children in the focus direction. To handle this case,
+ * RecyclerView converts the focus direction to an absolute direction and makes a preliminary
+ * focus search in that direction. If there are no Views to gain focus, it will call
+ * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} before running a
+ * focus search with the original (relative) direction. This allows RecyclerView to provide
+ * better candidates to the focus search while still allowing the view system to take focus from
+ * the RecyclerView and give it to a more suitable child if such child exists.
+ *
+ * @param focused The view that currently has focus
+ * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD},
+ * {@link View#FOCUS_BACKWARD} or 0 for not applicable.
+ *
+ * @return A new View that can be the next focus after the focused View
+ */
+ @Override
+ public View focusSearch(View focused, int direction) {
+ View result = mLayout.onInterceptFocusSearch(focused, direction);
+ if (result != null) {
+ return result;
+ }
+ final boolean canRunFocusFailure = mAdapter != null && mLayout != null
+ && !isComputingLayout() && !mLayoutFrozen;
+
+ final FocusFinder ff = FocusFinder.getInstance();
+ if (canRunFocusFailure
+ && (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD)) {
+ // convert direction to absolute direction and see if we have a view there and if not
+ // tell LayoutManager to add if it can.
+ boolean needsFocusFailureLayout = false;
+ if (mLayout.canScrollVertically()) {
+ final int absDir =
+ direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP;
+ final View found = ff.findNextFocus(this, focused, absDir);
+ needsFocusFailureLayout = found == null;
+ if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) {
+ // Workaround for broken FOCUS_BACKWARD in API 15 and older devices.
+ direction = absDir;
+ }
+ }
+ if (!needsFocusFailureLayout && mLayout.canScrollHorizontally()) {
+ boolean rtl = mLayout.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl
+ ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
+ final View found = ff.findNextFocus(this, focused, absDir);
+ needsFocusFailureLayout = found == null;
+ if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) {
+ // Workaround for broken FOCUS_BACKWARD in API 15 and older devices.
+ direction = absDir;
+ }
+ }
+ if (needsFocusFailureLayout) {
+ consumePendingUpdateOperations();
+ final View focusedItemView = findContainingItemView(focused);
+ if (focusedItemView == null) {
+ // panic, focused view is not a child anymore, cannot call super.
+ return null;
+ }
+ eatRequestLayout();
+ mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
+ resumeRequestLayout(false);
+ }
+ result = ff.findNextFocus(this, focused, direction);
+ } else {
+ result = ff.findNextFocus(this, focused, direction);
+ if (result == null && canRunFocusFailure) {
+ consumePendingUpdateOperations();
+ final View focusedItemView = findContainingItemView(focused);
+ if (focusedItemView == null) {
+ // panic, focused view is not a child anymore, cannot call super.
+ return null;
+ }
+ eatRequestLayout();
+ result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
+ resumeRequestLayout(false);
+ }
+ }
+ return isPreferredNextFocus(focused, result, direction)
+ ? result : super.focusSearch(focused, direction);
+ }
+
+ /**
+ * Checks if the new focus candidate is a good enough candidate such that RecyclerView will
+ * assign it as the next focus View instead of letting view hierarchy decide.
+ * A good candidate means a View that is aligned in the focus direction wrt the focused View
+ * and is not the RecyclerView itself.
+ * When this method returns false, RecyclerView will let the parent make the decision so the
+ * same View may still get the focus as a result of that search.
+ */
+ private boolean isPreferredNextFocus(View focused, View next, int direction) {
+ if (next == null || next == this) {
+ return false;
+ }
+ if (focused == null) {
+ return true;
+ }
+
+ if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) {
+ final boolean rtl = mLayout.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ final int absHorizontal = (direction == View.FOCUS_FORWARD) ^ rtl
+ ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
+ if (isPreferredNextFocusAbsolute(focused, next, absHorizontal)) {
+ return true;
+ }
+ if (direction == View.FOCUS_FORWARD) {
+ return isPreferredNextFocusAbsolute(focused, next, View.FOCUS_DOWN);
+ } else {
+ return isPreferredNextFocusAbsolute(focused, next, View.FOCUS_UP);
+ }
+ } else {
+ return isPreferredNextFocusAbsolute(focused, next, direction);
+ }
+
+ }
+
+ /**
+ * Logic taken from FocusSearch#isCandidate
+ */
+ private boolean isPreferredNextFocusAbsolute(View focused, View next, int direction) {
+ mTempRect.set(0, 0, focused.getWidth(), focused.getHeight());
+ mTempRect2.set(0, 0, next.getWidth(), next.getHeight());
+ offsetDescendantRectToMyCoords(focused, mTempRect);
+ offsetDescendantRectToMyCoords(next, mTempRect2);
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ return (mTempRect.right > mTempRect2.right
+ || mTempRect.left >= mTempRect2.right)
+ && mTempRect.left > mTempRect2.left;
+ case View.FOCUS_RIGHT:
+ return (mTempRect.left < mTempRect2.left
+ || mTempRect.right <= mTempRect2.left)
+ && mTempRect.right < mTempRect2.right;
+ case View.FOCUS_UP:
+ return (mTempRect.bottom > mTempRect2.bottom
+ || mTempRect.top >= mTempRect2.bottom)
+ && mTempRect.top > mTempRect2.top;
+ case View.FOCUS_DOWN:
+ return (mTempRect.top < mTempRect2.top
+ || mTempRect.bottom <= mTempRect2.top)
+ && mTempRect.bottom < mTempRect2.bottom;
+ }
+ throw new IllegalArgumentException("direction must be absolute. received:" + direction);
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ if (!mLayout.onRequestChildFocus(this, mState, child, focused) && focused != null) {
+ mTempRect.set(0, 0, focused.getWidth(), focused.getHeight());
+
+ // get item decor offsets w/o refreshing. If they are invalid, there will be another
+ // layout pass to fix them, then it is LayoutManager's responsibility to keep focused
+ // View in viewport.
+ final ViewGroup.LayoutParams focusedLayoutParams = focused.getLayoutParams();
+ if (focusedLayoutParams instanceof LayoutParams) {
+ // if focused child has item decors, use them. Otherwise, ignore.
+ final LayoutParams lp = (LayoutParams) focusedLayoutParams;
+ if (!lp.mInsetsDirty) {
+ final Rect insets = lp.mDecorInsets;
+ mTempRect.left -= insets.left;
+ mTempRect.right += insets.right;
+ mTempRect.top -= insets.top;
+ mTempRect.bottom += insets.bottom;
+ }
+ }
+
+ offsetDescendantRectToMyCoords(focused, mTempRect);
+ offsetRectIntoDescendantCoords(child, mTempRect);
+ requestChildRectangleOnScreen(child, mTempRect, !mFirstLayoutComplete);
+ }
+ super.requestChildFocus(child, focused);
+ }
+
+ @Override
+ public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
+ return mLayout.requestChildRectangleOnScreen(this, child, rect, immediate);
+ }
+
+ @Override
+ public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+ if (mLayout == null || !mLayout.onAddFocusables(this, views, direction, focusableMode)) {
+ super.addFocusables(views, direction, focusableMode);
+ }
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ if (isComputingLayout()) {
+ // if we are in the middle of a layout calculation, don't let any child take focus.
+ // RV will handle it after layout calculation is finished.
+ return false;
+ }
+ return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mLayoutOrScrollCounter = 0;
+ mIsAttached = true;
+ mFirstLayoutComplete = mFirstLayoutComplete && !isLayoutRequested();
+ if (mLayout != null) {
+ mLayout.dispatchAttachedToWindow(this);
+ }
+ mPostedAnimatorRunner = false;
+
+ if (ALLOW_THREAD_GAP_WORK) {
+ // Register with gap worker
+ mGapWorker = GapWorker.sGapWorker.get();
+ if (mGapWorker == null) {
+ mGapWorker = new GapWorker();
+
+ // break 60 fps assumption if data from display appears valid
+ // NOTE: we only do this query once, statically, because it's very expensive (> 1ms)
+ Display display = getDisplay();
+ float refreshRate = 60.0f;
+ if (!isInEditMode() && display != null) {
+ float displayRefreshRate = display.getRefreshRate();
+ if (displayRefreshRate >= 30.0f) {
+ refreshRate = displayRefreshRate;
+ }
+ }
+ mGapWorker.mFrameIntervalNs = (long) (1000000000 / refreshRate);
+ GapWorker.sGapWorker.set(mGapWorker);
+ }
+ mGapWorker.add(this);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mItemAnimator != null) {
+ mItemAnimator.endAnimations();
+ }
+ stopScroll();
+ mIsAttached = false;
+ if (mLayout != null) {
+ mLayout.dispatchDetachedFromWindow(this, mRecycler);
+ }
+ mPendingAccessibilityImportanceChange.clear();
+ removeCallbacks(mItemAnimatorRunner);
+ mViewInfoStore.onDetach();
+
+ if (ALLOW_THREAD_GAP_WORK) {
+ // Unregister with gap worker
+ mGapWorker.remove(this);
+ mGapWorker = null;
+ }
+ }
+
+ /**
+ * Returns true if RecyclerView is attached to window.
+ */
+ // @override
+ public boolean isAttachedToWindow() {
+ return mIsAttached;
+ }
+
+ /**
+ * Checks if RecyclerView is in the middle of a layout or scroll and throws an
+ * {@link IllegalStateException} if it <b>is not</b>.
+ *
+ * @param message The message for the exception. Can be null.
+ * @see #assertNotInLayoutOrScroll(String)
+ */
+ void assertInLayoutOrScroll(String message) {
+ if (!isComputingLayout()) {
+ if (message == null) {
+ throw new IllegalStateException("Cannot call this method unless RecyclerView is "
+ + "computing a layout or scrolling");
+ }
+ throw new IllegalStateException(message);
+
+ }
+ }
+
+ /**
+ * Checks if RecyclerView is in the middle of a layout or scroll and throws an
+ * {@link IllegalStateException} if it <b>is</b>.
+ *
+ * @param message The message for the exception. Can be null.
+ * @see #assertInLayoutOrScroll(String)
+ */
+ void assertNotInLayoutOrScroll(String message) {
+ if (isComputingLayout()) {
+ if (message == null) {
+ throw new IllegalStateException("Cannot call this method while RecyclerView is "
+ + "computing a layout or scrolling");
+ }
+ throw new IllegalStateException(message);
+ }
+ if (mDispatchScrollCounter > 0) {
+ Log.w(TAG, "Cannot call this method in a scroll callback. Scroll callbacks might be run"
+ + " during a measure & layout pass where you cannot change the RecyclerView"
+ + " data. Any method call that might change the structure of the RecyclerView"
+ + " or the adapter contents should be postponed to the next frame.",
+ new IllegalStateException(""));
+ }
+ }
+
+ /**
+ * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched
+ * to child views or this view's standard scrolling behavior.
+ *
+ * <p>Client code may use listeners to implement item manipulation behavior. Once a listener
+ * returns true from
+ * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its
+ * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called
+ * for each incoming MotionEvent until the end of the gesture.</p>
+ *
+ * @param listener Listener to add
+ * @see SimpleOnItemTouchListener
+ */
+ public void addOnItemTouchListener(OnItemTouchListener listener) {
+ mOnItemTouchListeners.add(listener);
+ }
+
+ /**
+ * Remove an {@link OnItemTouchListener}. It will no longer be able to intercept touch events.
+ *
+ * @param listener Listener to remove
+ */
+ public void removeOnItemTouchListener(OnItemTouchListener listener) {
+ mOnItemTouchListeners.remove(listener);
+ if (mActiveOnItemTouchListener == listener) {
+ mActiveOnItemTouchListener = null;
+ }
+ }
+
+ private boolean dispatchOnItemTouchIntercept(MotionEvent e) {
+ final int action = e.getAction();
+ if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) {
+ mActiveOnItemTouchListener = null;
+ }
+
+ final int listenerCount = mOnItemTouchListeners.size();
+ for (int i = 0; i < listenerCount; i++) {
+ final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
+ if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
+ mActiveOnItemTouchListener = listener;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean dispatchOnItemTouch(MotionEvent e) {
+ final int action = e.getAction();
+ if (mActiveOnItemTouchListener != null) {
+ if (action == MotionEvent.ACTION_DOWN) {
+ // Stale state from a previous gesture, we're starting a new one. Clear it.
+ mActiveOnItemTouchListener = null;
+ } else {
+ mActiveOnItemTouchListener.onTouchEvent(this, e);
+ if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+ // Clean up for the next gesture.
+ mActiveOnItemTouchListener = null;
+ }
+ return true;
+ }
+ }
+
+ // Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept
+ // as called from onInterceptTouchEvent; skip it.
+ if (action != MotionEvent.ACTION_DOWN) {
+ final int listenerCount = mOnItemTouchListeners.size();
+ for (int i = 0; i < listenerCount; i++) {
+ final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
+ if (listener.onInterceptTouchEvent(this, e)) {
+ mActiveOnItemTouchListener = listener;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent e) {
+ if (mLayoutFrozen) {
+ // When layout is frozen, RV does not intercept the motion event.
+ // A child view e.g. a button may still get the click.
+ return false;
+ }
+ if (dispatchOnItemTouchIntercept(e)) {
+ cancelTouch();
+ return true;
+ }
+
+ if (mLayout == null) {
+ return false;
+ }
+
+ final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
+ final boolean canScrollVertically = mLayout.canScrollVertically();
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(e);
+
+ final int action = e.getActionMasked();
+ final int actionIndex = e.getActionIndex();
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ if (mIgnoreMotionEventTillDown) {
+ mIgnoreMotionEventTillDown = false;
+ }
+ mScrollPointerId = e.getPointerId(0);
+ mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
+ mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
+
+ if (mScrollState == SCROLL_STATE_SETTLING) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ setScrollState(SCROLL_STATE_DRAGGING);
+ }
+
+ // Clear the nested offsets
+ mNestedOffsets[0] = mNestedOffsets[1] = 0;
+
+ int nestedScrollAxis = View.SCROLL_AXIS_NONE;
+ if (canScrollHorizontally) {
+ nestedScrollAxis |= View.SCROLL_AXIS_HORIZONTAL;
+ }
+ if (canScrollVertically) {
+ nestedScrollAxis |= View.SCROLL_AXIS_VERTICAL;
+ }
+ startNestedScroll(nestedScrollAxis);
+ break;
+
+ case MotionEvent.ACTION_POINTER_DOWN:
+ mScrollPointerId = e.getPointerId(actionIndex);
+ mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
+ mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
+ break;
+
+ case MotionEvent.ACTION_MOVE: {
+ final int index = e.findPointerIndex(mScrollPointerId);
+ if (index < 0) {
+ Log.e(TAG, "Error processing scroll; pointer index for id "
+ + mScrollPointerId + " not found. Did any MotionEvents get skipped?");
+ return false;
+ }
+
+ final int x = (int) (e.getX(index) + 0.5f);
+ final int y = (int) (e.getY(index) + 0.5f);
+ if (mScrollState != SCROLL_STATE_DRAGGING) {
+ final int dx = x - mInitialTouchX;
+ final int dy = y - mInitialTouchY;
+ boolean startScroll = false;
+ if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
+ mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
+ startScroll = true;
+ }
+ if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
+ mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
+ startScroll = true;
+ }
+ if (startScroll) {
+ setScrollState(SCROLL_STATE_DRAGGING);
+ }
+ }
+ } break;
+
+ case MotionEvent.ACTION_POINTER_UP: {
+ onPointerUp(e);
+ } break;
+
+ case MotionEvent.ACTION_UP: {
+ mVelocityTracker.clear();
+ stopNestedScroll();
+ } break;
+
+ case MotionEvent.ACTION_CANCEL: {
+ cancelTouch();
+ }
+ }
+ return mScrollState == SCROLL_STATE_DRAGGING;
+ }
+
+ @Override
+ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ final int listenerCount = mOnItemTouchListeners.size();
+ for (int i = 0; i < listenerCount; i++) {
+ final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
+ listener.onRequestDisallowInterceptTouchEvent(disallowIntercept);
+ }
+ super.requestDisallowInterceptTouchEvent(disallowIntercept);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent e) {
+ if (mLayoutFrozen || mIgnoreMotionEventTillDown) {
+ return false;
+ }
+ if (dispatchOnItemTouch(e)) {
+ cancelTouch();
+ return true;
+ }
+
+ if (mLayout == null) {
+ return false;
+ }
+
+ final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
+ final boolean canScrollVertically = mLayout.canScrollVertically();
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ boolean eventAddedToVelocityTracker = false;
+
+ final MotionEvent vtev = MotionEvent.obtain(e);
+ final int action = e.getActionMasked();
+ final int actionIndex = e.getActionIndex();
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ mNestedOffsets[0] = mNestedOffsets[1] = 0;
+ }
+ vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ mScrollPointerId = e.getPointerId(0);
+ mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
+ mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
+
+ int nestedScrollAxis = View.SCROLL_AXIS_NONE;
+ if (canScrollHorizontally) {
+ nestedScrollAxis |= View.SCROLL_AXIS_HORIZONTAL;
+ }
+ if (canScrollVertically) {
+ nestedScrollAxis |= View.SCROLL_AXIS_VERTICAL;
+ }
+ startNestedScroll(nestedScrollAxis);
+ } break;
+
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ mScrollPointerId = e.getPointerId(actionIndex);
+ mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
+ mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
+ } break;
+
+ case MotionEvent.ACTION_MOVE: {
+ final int index = e.findPointerIndex(mScrollPointerId);
+ if (index < 0) {
+ Log.e(TAG, "Error processing scroll; pointer index for id "
+ + mScrollPointerId + " not found. Did any MotionEvents get skipped?");
+ return false;
+ }
+
+ final int x = (int) (e.getX(index) + 0.5f);
+ final int y = (int) (e.getY(index) + 0.5f);
+ int dx = mLastTouchX - x;
+ int dy = mLastTouchY - y;
+
+ if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
+ dx -= mScrollConsumed[0];
+ dy -= mScrollConsumed[1];
+ vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
+ // Updated the nested offsets
+ mNestedOffsets[0] += mScrollOffset[0];
+ mNestedOffsets[1] += mScrollOffset[1];
+ }
+
+ if (mScrollState != SCROLL_STATE_DRAGGING) {
+ boolean startScroll = false;
+ if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
+ if (dx > 0) {
+ dx -= mTouchSlop;
+ } else {
+ dx += mTouchSlop;
+ }
+ startScroll = true;
+ }
+ if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
+ if (dy > 0) {
+ dy -= mTouchSlop;
+ } else {
+ dy += mTouchSlop;
+ }
+ startScroll = true;
+ }
+ if (startScroll) {
+ setScrollState(SCROLL_STATE_DRAGGING);
+ }
+ }
+
+ if (mScrollState == SCROLL_STATE_DRAGGING) {
+ mLastTouchX = x - mScrollOffset[0];
+ mLastTouchY = y - mScrollOffset[1];
+
+ if (scrollByInternal(
+ canScrollHorizontally ? dx : 0,
+ canScrollVertically ? dy : 0,
+ vtev)) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ }
+ if (mGapWorker != null && (dx != 0 || dy != 0)) {
+ mGapWorker.postFromTraversal(this, dx, dy);
+ }
+ }
+ } break;
+
+ case MotionEvent.ACTION_POINTER_UP: {
+ onPointerUp(e);
+ } break;
+
+ case MotionEvent.ACTION_UP: {
+ mVelocityTracker.addMovement(vtev);
+ eventAddedToVelocityTracker = true;
+ mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
+ final float xvel = canScrollHorizontally
+ ? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;
+ final float yvel = canScrollVertically
+ ? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;
+ if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
+ setScrollState(SCROLL_STATE_IDLE);
+ }
+ resetTouch();
+ } break;
+
+ case MotionEvent.ACTION_CANCEL: {
+ cancelTouch();
+ } break;
+ }
+
+ if (!eventAddedToVelocityTracker) {
+ mVelocityTracker.addMovement(vtev);
+ }
+ vtev.recycle();
+
+ return true;
+ }
+
+ private void resetTouch() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ stopNestedScroll();
+ releaseGlows();
+ }
+
+ private void cancelTouch() {
+ resetTouch();
+ setScrollState(SCROLL_STATE_IDLE);
+ }
+
+ private void onPointerUp(MotionEvent e) {
+ final int actionIndex = e.getActionIndex();
+ if (e.getPointerId(actionIndex) == mScrollPointerId) {
+ // Pick a new pointer to pick up the slack.
+ final int newIndex = actionIndex == 0 ? 1 : 0;
+ mScrollPointerId = e.getPointerId(newIndex);
+ mInitialTouchX = mLastTouchX = (int) (e.getX(newIndex) + 0.5f);
+ mInitialTouchY = mLastTouchY = (int) (e.getY(newIndex) + 0.5f);
+ }
+ }
+
+ // @Override
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ if (mLayout == null) {
+ return false;
+ }
+ if (mLayoutFrozen) {
+ return false;
+ }
+ if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ if (event.getAction() == MotionEvent.ACTION_SCROLL) {
+ final float vScroll, hScroll;
+ if (mLayout.canScrollVertically()) {
+ // Inverse the sign of the vertical scroll to align the scroll orientation
+ // with AbsListView.
+ vScroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+ } else {
+ vScroll = 0f;
+ }
+ if (mLayout.canScrollHorizontally()) {
+ hScroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
+ } else {
+ hScroll = 0f;
+ }
+
+ if (vScroll != 0 || hScroll != 0) {
+ final float scrollFactor = getScrollFactor();
+ scrollByInternal((int) (hScroll * scrollFactor),
+ (int) (vScroll * scrollFactor), event);
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Ported from View.getVerticalScrollFactor.
+ */
+ private float getScrollFactor() {
+ if (mScrollFactor == Float.MIN_VALUE) {
+ TypedValue outValue = new TypedValue();
+ if (getContext().getTheme().resolveAttribute(
+ android.R.attr.listPreferredItemHeight, outValue, true)) {
+ mScrollFactor = outValue.getDimension(
+ getContext().getResources().getDisplayMetrics());
+ } else {
+ return 0; //listPreferredItemHeight is not defined, no generic scrolling
+ }
+ }
+ return mScrollFactor;
+ }
+
+ @Override
+ protected void onMeasure(int widthSpec, int heightSpec) {
+ if (mLayout == null) {
+ defaultOnMeasure(widthSpec, heightSpec);
+ return;
+ }
+ if (mLayout.mAutoMeasure) {
+ final int widthMode = MeasureSpec.getMode(widthSpec);
+ final int heightMode = MeasureSpec.getMode(heightSpec);
+ final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
+ && heightMode == MeasureSpec.EXACTLY;
+ mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+ if (skipMeasure || mAdapter == null) {
+ return;
+ }
+ if (mState.mLayoutStep == State.STEP_START) {
+ dispatchLayoutStep1();
+ }
+ // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
+ // consistency
+ mLayout.setMeasureSpecs(widthSpec, heightSpec);
+ mState.mIsMeasuring = true;
+ dispatchLayoutStep2();
+
+ // now we can get the width and height from the children.
+ mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
+
+ // if RecyclerView has non-exact width and height and if there is at least one child
+ // which also has non-exact width & height, we have to re-measure.
+ if (mLayout.shouldMeasureTwice()) {
+ mLayout.setMeasureSpecs(
+ MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
+ mState.mIsMeasuring = true;
+ dispatchLayoutStep2();
+ // now we can get the width and height from the children.
+ mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
+ }
+ } else {
+ if (mHasFixedSize) {
+ mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+ return;
+ }
+ // custom onMeasure
+ if (mAdapterUpdateDuringMeasure) {
+ eatRequestLayout();
+ onEnterLayoutOrScroll();
+ processAdapterUpdatesAndSetAnimationFlags();
+ onExitLayoutOrScroll();
+
+ if (mState.mRunPredictiveAnimations) {
+ mState.mInPreLayout = true;
+ } else {
+ // consume remaining updates to provide a consistent state with the layout pass.
+ mAdapterHelper.consumeUpdatesInOnePass();
+ mState.mInPreLayout = false;
+ }
+ mAdapterUpdateDuringMeasure = false;
+ resumeRequestLayout(false);
+ }
+
+ if (mAdapter != null) {
+ mState.mItemCount = mAdapter.getItemCount();
+ } else {
+ mState.mItemCount = 0;
+ }
+ eatRequestLayout();
+ mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+ resumeRequestLayout(false);
+ mState.mInPreLayout = false; // clear
+ }
+ }
+
+ /**
+ * Used when onMeasure is called before layout manager is set
+ */
+ void defaultOnMeasure(int widthSpec, int heightSpec) {
+ // calling LayoutManager here is not pretty but that API is already public and it is better
+ // than creating another method since this is internal.
+ final int width = LayoutManager.chooseSize(widthSpec,
+ getPaddingLeft() + getPaddingRight(),
+ getMinimumWidth());
+ final int height = LayoutManager.chooseSize(heightSpec,
+ getPaddingTop() + getPaddingBottom(),
+ getMinimumHeight());
+
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ if (w != oldw || h != oldh) {
+ invalidateGlows();
+ // layout's w/h are updated during measure/layout steps.
+ }
+ }
+
+ /**
+ * Sets the {@link ItemAnimator} that will handle animations involving changes
+ * to the items in this RecyclerView. By default, RecyclerView instantiates and
+ * uses an instance of {@link DefaultItemAnimator}. Whether item animations are
+ * enabled for the RecyclerView depends on the ItemAnimator and whether
+ * the LayoutManager {@link LayoutManager#supportsPredictiveItemAnimations()
+ * supports item animations}.
+ *
+ * @param animator The ItemAnimator being set. If null, no animations will occur
+ * when changes occur to the items in this RecyclerView.
+ */
+ public void setItemAnimator(ItemAnimator animator) {
+ if (mItemAnimator != null) {
+ mItemAnimator.endAnimations();
+ mItemAnimator.setListener(null);
+ }
+ mItemAnimator = animator;
+ if (mItemAnimator != null) {
+ mItemAnimator.setListener(mItemAnimatorListener);
+ }
+ }
+
+ void onEnterLayoutOrScroll() {
+ mLayoutOrScrollCounter++;
+ }
+
+ void onExitLayoutOrScroll() {
+ mLayoutOrScrollCounter--;
+ if (mLayoutOrScrollCounter < 1) {
+ if (DEBUG && mLayoutOrScrollCounter < 0) {
+ throw new IllegalStateException("layout or scroll counter cannot go below zero."
+ + "Some calls are not matching");
+ }
+ mLayoutOrScrollCounter = 0;
+ dispatchContentChangedIfNecessary();
+ dispatchPendingImportantForAccessibilityChanges();
+ }
+ }
+
+ boolean isAccessibilityEnabled() {
+ return mAccessibilityManager != null && mAccessibilityManager.isEnabled();
+ }
+
+ private void dispatchContentChangedIfNecessary() {
+ final int flags = mEatenAccessibilityChangeFlags;
+ mEatenAccessibilityChangeFlags = 0;
+ if (flags != 0 && isAccessibilityEnabled()) {
+ final AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ event.setContentChangeTypes(flags);
+ sendAccessibilityEventUnchecked(event);
+ }
+ }
+
+ /**
+ * Returns whether RecyclerView is currently computing a layout.
+ * <p>
+ * If this method returns true, it means that RecyclerView is in a lockdown state and any
+ * attempt to update adapter contents will result in an exception because adapter contents
+ * cannot be changed while RecyclerView is trying to compute the layout.
+ * <p>
+ * It is very unlikely that your code will be running during this state as it is
+ * called by the framework when a layout traversal happens or RecyclerView starts to scroll
+ * in response to system events (touch, accessibility etc).
+ * <p>
+ * This case may happen if you have some custom logic to change adapter contents in
+ * response to a View callback (e.g. focus change callback) which might be triggered during a
+ * layout calculation. In these cases, you should just postpone the change using a Handler or a
+ * similar mechanism.
+ *
+ * @return <code>true</code> if RecyclerView is currently computing a layout, <code>false</code>
+ * otherwise
+ */
+ public boolean isComputingLayout() {
+ return mLayoutOrScrollCounter > 0;
+ }
+
+ /**
+ * Returns true if an accessibility event should not be dispatched now. This happens when an
+ * accessibility request arrives while RecyclerView does not have a stable state which is very
+ * hard to handle for a LayoutManager. Instead, this method records necessary information about
+ * the event and dispatches a window change event after the critical section is finished.
+ *
+ * @return True if the accessibility event should be postponed.
+ */
+ boolean shouldDeferAccessibilityEvent(AccessibilityEvent event) {
+ if (isComputingLayout()) {
+ int type = 0;
+ if (event != null) {
+ type = event.getContentChangeTypes();
+ }
+ if (type == 0) {
+ type = AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
+ }
+ mEatenAccessibilityChangeFlags |= type;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
+ if (shouldDeferAccessibilityEvent(event)) {
+ return;
+ }
+ super.sendAccessibilityEventUnchecked(event);
+ }
+
+ /**
+ * Gets the current ItemAnimator for this RecyclerView. A null return value
+ * indicates that there is no animator and that item changes will happen without
+ * any animations. By default, RecyclerView instantiates and
+ * uses an instance of {@link DefaultItemAnimator}.
+ *
+ * @return ItemAnimator The current ItemAnimator. If null, no animations will occur
+ * when changes occur to the items in this RecyclerView.
+ */
+ public ItemAnimator getItemAnimator() {
+ return mItemAnimator;
+ }
+
+ /**
+ * Post a runnable to the next frame to run pending item animations. Only the first such
+ * request will be posted, governed by the mPostedAnimatorRunner flag.
+ */
+ void postAnimationRunner() {
+ if (!mPostedAnimatorRunner && mIsAttached) {
+ postOnAnimation(mItemAnimatorRunner);
+ mPostedAnimatorRunner = true;
+ }
+ }
+
+ private boolean predictiveItemAnimationsEnabled() {
+ return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations());
+ }
+
+ /**
+ * Consumes adapter updates and calculates which type of animations we want to run.
+ * Called in onMeasure and dispatchLayout.
+ * <p>
+ * This method may process only the pre-layout state of updates or all of them.
+ */
+ private void processAdapterUpdatesAndSetAnimationFlags() {
+ if (mDataSetHasChangedAfterLayout) {
+ // Processing these items have no value since data set changed unexpectedly.
+ // Instead, we just reset it.
+ mAdapterHelper.reset();
+ mLayout.onItemsChanged(this);
+ }
+ // simple animations are a subset of advanced animations (which will cause a
+ // pre-layout step)
+ // If layout supports predictive animations, pre-process to decide if we want to run them
+ if (predictiveItemAnimationsEnabled()) {
+ mAdapterHelper.preProcess();
+ } else {
+ mAdapterHelper.consumeUpdatesInOnePass();
+ }
+ boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
+ mState.mRunSimpleAnimations = mFirstLayoutComplete
+ && mItemAnimator != null
+ && (mDataSetHasChangedAfterLayout
+ || animationTypeSupported
+ || mLayout.mRequestedSimpleAnimations)
+ && (!mDataSetHasChangedAfterLayout
+ || mAdapter.hasStableIds());
+ mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
+ && animationTypeSupported
+ && !mDataSetHasChangedAfterLayout
+ && predictiveItemAnimationsEnabled();
+ }
+
+ /**
+ * Wrapper around layoutChildren() that handles animating changes caused by layout.
+ * Animations work on the assumption that there are five different kinds of items
+ * in play:
+ * PERSISTENT: items are visible before and after layout
+ * REMOVED: items were visible before layout and were removed by the app
+ * ADDED: items did not exist before layout and were added by the app
+ * DISAPPEARING: items exist in the data set before/after, but changed from
+ * visible to non-visible in the process of layout (they were moved off
+ * screen as a side-effect of other changes)
+ * APPEARING: items exist in the data set before/after, but changed from
+ * non-visible to visible in the process of layout (they were moved on
+ * screen as a side-effect of other changes)
+ * The overall approach figures out what items exist before/after layout and
+ * infers one of the five above states for each of the items. Then the animations
+ * are set up accordingly:
+ * PERSISTENT views are animated via
+ * {@link ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
+ * DISAPPEARING views are animated via
+ * {@link ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
+ * APPEARING views are animated via
+ * {@link ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
+ * and changed views are animated via
+ * {@link ItemAnimator#animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)}.
+ */
+ void dispatchLayout() {
+ if (mAdapter == null) {
+ Log.e(TAG, "No adapter attached; skipping layout");
+ // leave the state in START
+ return;
+ }
+ if (mLayout == null) {
+ Log.e(TAG, "No layout manager attached; skipping layout");
+ // leave the state in START
+ return;
+ }
+ mState.mIsMeasuring = false;
+ if (mState.mLayoutStep == State.STEP_START) {
+ dispatchLayoutStep1();
+ mLayout.setExactMeasureSpecsFrom(this);
+ dispatchLayoutStep2();
+ } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
+ || mLayout.getHeight() != getHeight()) {
+ // First 2 steps are done in onMeasure but looks like we have to run again due to
+ // changed size.
+ mLayout.setExactMeasureSpecsFrom(this);
+ dispatchLayoutStep2();
+ } else {
+ // always make sure we sync them (to ensure mode is exact)
+ mLayout.setExactMeasureSpecsFrom(this);
+ }
+ dispatchLayoutStep3();
+ }
+
+ private void saveFocusInfo() {
+ View child = null;
+ if (mPreserveFocusAfterLayout && hasFocus() && mAdapter != null) {
+ child = getFocusedChild();
+ }
+
+ final ViewHolder focusedVh = child == null ? null : findContainingViewHolder(child);
+ if (focusedVh == null) {
+ resetFocusInfo();
+ } else {
+ mState.mFocusedItemId = mAdapter.hasStableIds() ? focusedVh.getItemId() : NO_ID;
+ // mFocusedItemPosition should hold the current adapter position of the previously
+ // focused item. If the item is removed, we store the previous adapter position of the
+ // removed item.
+ mState.mFocusedItemPosition = mDataSetHasChangedAfterLayout ? NO_POSITION
+ : (focusedVh.isRemoved() ? focusedVh.mOldPosition
+ : focusedVh.getAdapterPosition());
+ mState.mFocusedSubChildId = getDeepestFocusedViewWithId(focusedVh.itemView);
+ }
+ }
+
+ private void resetFocusInfo() {
+ mState.mFocusedItemId = NO_ID;
+ mState.mFocusedItemPosition = NO_POSITION;
+ mState.mFocusedSubChildId = View.NO_ID;
+ }
+
+ /**
+ * Finds the best view candidate to request focus on using mFocusedItemPosition index of the
+ * previously focused item. It first traverses the adapter forward to find a focusable candidate
+ * and if no such candidate is found, it reverses the focus search direction for the items
+ * before the mFocusedItemPosition'th index;
+ * @return The best candidate to request focus on, or null if no such candidate exists. Null
+ * indicates all the existing adapter items are unfocusable.
+ */
+ @Nullable
+ private View findNextViewToFocus() {
+ int startFocusSearchIndex = mState.mFocusedItemPosition != -1 ? mState.mFocusedItemPosition
+ : 0;
+ ViewHolder nextFocus;
+ final int itemCount = mState.getItemCount();
+ for (int i = startFocusSearchIndex; i < itemCount; i++) {
+ nextFocus = findViewHolderForAdapterPosition(i);
+ if (nextFocus == null) {
+ break;
+ }
+ if (nextFocus.itemView.hasFocusable()) {
+ return nextFocus.itemView;
+ }
+ }
+ final int limit = Math.min(itemCount, startFocusSearchIndex);
+ for (int i = limit - 1; i >= 0; i--) {
+ nextFocus = findViewHolderForAdapterPosition(i);
+ if (nextFocus == null) {
+ return null;
+ }
+ if (nextFocus.itemView.hasFocusable()) {
+ return nextFocus.itemView;
+ }
+ }
+ return null;
+ }
+
+ private void recoverFocusFromState() {
+ if (!mPreserveFocusAfterLayout || mAdapter == null || !hasFocus()
+ || getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS
+ || (getDescendantFocusability() == FOCUS_BEFORE_DESCENDANTS && isFocused())) {
+ // No-op if either of these cases happens:
+ // 1. RV has no focus, or 2. RV blocks focus to its children, or 3. RV takes focus
+ // before its children and is focused (i.e. it already stole the focus away from its
+ // descendants).
+ return;
+ }
+ // only recover focus if RV itself has the focus or the focused view is hidden
+ if (!isFocused()) {
+ final View focusedChild = getFocusedChild();
+ if (IGNORE_DETACHED_FOCUSED_CHILD
+ && (focusedChild.getParent() == null || !focusedChild.hasFocus())) {
+ // Special handling of API 15-. A focused child can be invalid because mFocus is not
+ // cleared when the child is detached (mParent = null),
+ // This happens because clearFocus on API 15- does not invalidate mFocus of its
+ // parent when this child is detached.
+ // For API 16+, this is not an issue because requestFocus takes care of clearing the
+ // prior detached focused child. For API 15- the problem happens in 2 cases because
+ // clearChild does not call clearChildFocus on RV: 1. setFocusable(false) is called
+ // for the current focused item which calls clearChild or 2. when the prior focused
+ // child is removed, removeDetachedView called in layout step 3 which calls
+ // clearChild. We should ignore this invalid focused child in all our calculations
+ // for the next view to receive focus, and apply the focus recovery logic instead.
+ if (mChildHelper.getChildCount() == 0) {
+ // No children left. Request focus on the RV itself since one of its children
+ // was holding focus previously.
+ requestFocus();
+ return;
+ }
+ } else if (!mChildHelper.isHidden(focusedChild)) {
+ // If the currently focused child is hidden, apply the focus recovery logic.
+ // Otherwise return, i.e. the currently (unhidden) focused child is good enough :/.
+ return;
+ }
+ }
+ ViewHolder focusTarget = null;
+ // RV first attempts to locate the previously focused item to request focus on using
+ // mFocusedItemId. If such an item no longer exists, it then makes a best-effort attempt to
+ // find the next best candidate to request focus on based on mFocusedItemPosition.
+ if (mState.mFocusedItemId != NO_ID && mAdapter.hasStableIds()) {
+ focusTarget = findViewHolderForItemId(mState.mFocusedItemId);
+ }
+ View viewToFocus = null;
+ if (focusTarget == null || mChildHelper.isHidden(focusTarget.itemView)
+ || !focusTarget.itemView.hasFocusable()) {
+ if (mChildHelper.getChildCount() > 0) {
+ // At this point, RV has focus and either of these conditions are true:
+ // 1. There's no previously focused item either because RV received focused before
+ // layout, or the previously focused item was removed, or RV doesn't have stable IDs
+ // 2. Previous focus child is hidden, or 3. Previous focused child is no longer
+ // focusable. In either of these cases, we make sure that RV still passes down the
+ // focus to one of its focusable children using a best-effort algorithm.
+ viewToFocus = findNextViewToFocus();
+ }
+ } else {
+ // looks like the focused item has been replaced with another view that represents the
+ // same item in the adapter. Request focus on that.
+ viewToFocus = focusTarget.itemView;
+ }
+
+ if (viewToFocus != null) {
+ if (mState.mFocusedSubChildId != NO_ID) {
+ View child = viewToFocus.findViewById(mState.mFocusedSubChildId);
+ if (child != null && child.isFocusable()) {
+ viewToFocus = child;
+ }
+ }
+ viewToFocus.requestFocus();
+ }
+ }
+
+ private int getDeepestFocusedViewWithId(View view) {
+ int lastKnownId = view.getId();
+ while (!view.isFocused() && view instanceof ViewGroup && view.hasFocus()) {
+ view = ((ViewGroup) view).getFocusedChild();
+ final int id = view.getId();
+ if (id != View.NO_ID) {
+ lastKnownId = view.getId();
+ }
+ }
+ return lastKnownId;
+ }
+
+ /**
+ * The first step of a layout where we;
+ * - process adapter updates
+ * - decide which animation should run
+ * - save information about current views
+ * - If necessary, run predictive layout and save its information
+ */
+ private void dispatchLayoutStep1() {
+ mState.assertLayoutStep(State.STEP_START);
+ mState.mIsMeasuring = false;
+ eatRequestLayout();
+ mViewInfoStore.clear();
+ onEnterLayoutOrScroll();
+ processAdapterUpdatesAndSetAnimationFlags();
+ saveFocusInfo();
+ mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
+ mItemsAddedOrRemoved = mItemsChanged = false;
+ mState.mInPreLayout = mState.mRunPredictiveAnimations;
+ mState.mItemCount = mAdapter.getItemCount();
+ findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
+
+ if (mState.mRunSimpleAnimations) {
+ // Step 0: Find out where all non-removed items are, pre-layout
+ int count = mChildHelper.getChildCount();
+ for (int i = 0; i < count; ++i) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+ if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
+ continue;
+ }
+ final ItemHolderInfo animationInfo = mItemAnimator
+ .recordPreLayoutInformation(mState, holder,
+ ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
+ holder.getUnmodifiedPayloads());
+ mViewInfoStore.addToPreLayout(holder, animationInfo);
+ if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
+ && !holder.shouldIgnore() && !holder.isInvalid()) {
+ long key = getChangedHolderKey(holder);
+ // This is NOT the only place where a ViewHolder is added to old change holders
+ // list. There is another case where:
+ // * A VH is currently hidden but not deleted
+ // * The hidden item is changed in the adapter
+ // * Layout manager decides to layout the item in the pre-Layout pass (step1)
+ // When this case is detected, RV will un-hide that view and add to the old
+ // change holders list.
+ mViewInfoStore.addToOldChangeHolders(key, holder);
+ }
+ }
+ }
+ if (mState.mRunPredictiveAnimations) {
+ // Step 1: run prelayout: This will use the old positions of items. The layout manager
+ // is expected to layout everything, even removed items (though not to add removed
+ // items back to the container). This gives the pre-layout position of APPEARING views
+ // which come into existence as part of the real layout.
+
+ // Save old positions so that LayoutManager can run its mapping logic.
+ saveOldPositions();
+ final boolean didStructureChange = mState.mStructureChanged;
+ mState.mStructureChanged = false;
+ // temporarily disable flag because we are asking for previous layout
+ mLayout.onLayoutChildren(mRecycler, mState);
+ mState.mStructureChanged = didStructureChange;
+
+ for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
+ final View child = mChildHelper.getChildAt(i);
+ final ViewHolder viewHolder = getChildViewHolderInt(child);
+ if (viewHolder.shouldIgnore()) {
+ continue;
+ }
+ if (!mViewInfoStore.isInPreLayout(viewHolder)) {
+ int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
+ boolean wasHidden = viewHolder
+ .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
+ if (!wasHidden) {
+ flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
+ }
+ final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
+ mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
+ if (wasHidden) {
+ recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
+ } else {
+ mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
+ }
+ }
+ }
+ // we don't process disappearing list because they may re-appear in post layout pass.
+ clearOldPositions();
+ } else {
+ clearOldPositions();
+ }
+ onExitLayoutOrScroll();
+ resumeRequestLayout(false);
+ mState.mLayoutStep = State.STEP_LAYOUT;
+ }
+
+ /**
+ * The second layout step where we do the actual layout of the views for the final state.
+ * This step might be run multiple times if necessary (e.g. measure).
+ */
+ private void dispatchLayoutStep2() {
+ eatRequestLayout();
+ onEnterLayoutOrScroll();
+ mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
+ mAdapterHelper.consumeUpdatesInOnePass();
+ mState.mItemCount = mAdapter.getItemCount();
+ mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
+
+ // Step 2: Run layout
+ mState.mInPreLayout = false;
+ mLayout.onLayoutChildren(mRecycler, mState);
+
+ mState.mStructureChanged = false;
+ mPendingSavedState = null;
+
+ // onLayoutChildren may have caused client code to disable item animations; re-check
+ mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
+ mState.mLayoutStep = State.STEP_ANIMATIONS;
+ onExitLayoutOrScroll();
+ resumeRequestLayout(false);
+ }
+
+ /**
+ * The final step of the layout where we save the information about views for animations,
+ * trigger animations and do any necessary cleanup.
+ */
+ private void dispatchLayoutStep3() {
+ mState.assertLayoutStep(State.STEP_ANIMATIONS);
+ eatRequestLayout();
+ onEnterLayoutOrScroll();
+ mState.mLayoutStep = State.STEP_START;
+ if (mState.mRunSimpleAnimations) {
+ // Step 3: Find out where things are now, and process change animations.
+ // traverse list in reverse because we may call animateChange in the loop which may
+ // remove the target view holder.
+ for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
+ ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+ if (holder.shouldIgnore()) {
+ continue;
+ }
+ long key = getChangedHolderKey(holder);
+ final ItemHolderInfo animationInfo = mItemAnimator
+ .recordPostLayoutInformation(mState, holder);
+ ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
+ if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
+ // run a change animation
+
+ // If an Item is CHANGED but the updated version is disappearing, it creates
+ // a conflicting case.
+ // Since a view that is marked as disappearing is likely to be going out of
+ // bounds, we run a change animation. Both views will be cleaned automatically
+ // once their animations finish.
+ // On the other hand, if it is the same view holder instance, we run a
+ // disappearing animation instead because we are not going to rebind the updated
+ // VH unless it is enforced by the layout manager.
+ final boolean oldDisappearing = mViewInfoStore.isDisappearing(
+ oldChangeViewHolder);
+ final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
+ if (oldDisappearing && oldChangeViewHolder == holder) {
+ // run disappear animation instead of change
+ mViewInfoStore.addToPostLayout(holder, animationInfo);
+ } else {
+ final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
+ oldChangeViewHolder);
+ // we add and remove so that any post info is merged.
+ mViewInfoStore.addToPostLayout(holder, animationInfo);
+ ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
+ if (preInfo == null) {
+ handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
+ } else {
+ animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
+ oldDisappearing, newDisappearing);
+ }
+ }
+ } else {
+ mViewInfoStore.addToPostLayout(holder, animationInfo);
+ }
+ }
+
+ // Step 4: Process view info lists and trigger animations
+ mViewInfoStore.process(mViewInfoProcessCallback);
+ }
+
+ mLayout.removeAndRecycleScrapInt(mRecycler);
+ mState.mPreviousLayoutItemCount = mState.mItemCount;
+ mDataSetHasChangedAfterLayout = false;
+ mState.mRunSimpleAnimations = false;
+
+ mState.mRunPredictiveAnimations = false;
+ mLayout.mRequestedSimpleAnimations = false;
+ if (mRecycler.mChangedScrap != null) {
+ mRecycler.mChangedScrap.clear();
+ }
+ if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {
+ // Initial prefetch has expanded cache, so reset until next prefetch.
+ // This prevents initial prefetches from expanding the cache permanently.
+ mLayout.mPrefetchMaxCountObserved = 0;
+ mLayout.mPrefetchMaxObservedInInitialPrefetch = false;
+ mRecycler.updateViewCacheSize();
+ }
+
+ mLayout.onLayoutCompleted(mState);
+ onExitLayoutOrScroll();
+ resumeRequestLayout(false);
+ mViewInfoStore.clear();
+ if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
+ dispatchOnScrolled(0, 0);
+ }
+ recoverFocusFromState();
+ resetFocusInfo();
+ }
+
+ /**
+ * This handles the case where there is an unexpected VH missing in the pre-layout map.
+ * <p>
+ * We might be able to detect the error in the application which will help the developer to
+ * resolve the issue.
+ * <p>
+ * If it is not an expected error, we at least print an error to notify the developer and ignore
+ * the animation.
+ *
+ * https://code.google.com/p/android/issues/detail?id=193958
+ *
+ * @param key The change key
+ * @param holder Current ViewHolder
+ * @param oldChangeViewHolder Changed ViewHolder
+ */
+ private void handleMissingPreInfoForChangeError(long key,
+ ViewHolder holder, ViewHolder oldChangeViewHolder) {
+ // check if two VH have the same key, if so, print that as an error
+ final int childCount = mChildHelper.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View view = mChildHelper.getChildAt(i);
+ ViewHolder other = getChildViewHolderInt(view);
+ if (other == holder) {
+ continue;
+ }
+ final long otherKey = getChangedHolderKey(other);
+ if (otherKey == key) {
+ if (mAdapter != null && mAdapter.hasStableIds()) {
+ throw new IllegalStateException("Two different ViewHolders have the same stable"
+ + " ID. Stable IDs in your adapter MUST BE unique and SHOULD NOT"
+ + " change.\n ViewHolder 1:" + other + " \n View Holder 2:" + holder);
+ } else {
+ throw new IllegalStateException("Two different ViewHolders have the same change"
+ + " ID. This might happen due to inconsistent Adapter update events or"
+ + " if the LayoutManager lays out the same View multiple times."
+ + "\n ViewHolder 1:" + other + " \n View Holder 2:" + holder);
+ }
+ }
+ }
+ // Very unlikely to happen but if it does, notify the developer.
+ Log.e(TAG, "Problem while matching changed view holders with the new"
+ + "ones. The pre-layout information for the change holder " + oldChangeViewHolder
+ + " cannot be found but it is necessary for " + holder);
+ }
+
+ /**
+ * Records the animation information for a view holder that was bounced from hidden list. It
+ * also clears the bounce back flag.
+ */
+ void recordAnimationInfoIfBouncedHiddenView(ViewHolder viewHolder,
+ ItemHolderInfo animationInfo) {
+ // looks like this view bounced back from hidden list!
+ viewHolder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
+ if (mState.mTrackOldChangeHolders && viewHolder.isUpdated()
+ && !viewHolder.isRemoved() && !viewHolder.shouldIgnore()) {
+ long key = getChangedHolderKey(viewHolder);
+ mViewInfoStore.addToOldChangeHolders(key, viewHolder);
+ }
+ mViewInfoStore.addToPreLayout(viewHolder, animationInfo);
+ }
+
+ private void findMinMaxChildLayoutPositions(int[] into) {
+ final int count = mChildHelper.getChildCount();
+ if (count == 0) {
+ into[0] = NO_POSITION;
+ into[1] = NO_POSITION;
+ return;
+ }
+ int minPositionPreLayout = Integer.MAX_VALUE;
+ int maxPositionPreLayout = Integer.MIN_VALUE;
+ for (int i = 0; i < count; ++i) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+ if (holder.shouldIgnore()) {
+ continue;
+ }
+ final int pos = holder.getLayoutPosition();
+ if (pos < minPositionPreLayout) {
+ minPositionPreLayout = pos;
+ }
+ if (pos > maxPositionPreLayout) {
+ maxPositionPreLayout = pos;
+ }
+ }
+ into[0] = minPositionPreLayout;
+ into[1] = maxPositionPreLayout;
+ }
+
+ private boolean didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout) {
+ findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
+ return mMinMaxLayoutPositions[0] != minPositionPreLayout
+ || mMinMaxLayoutPositions[1] != maxPositionPreLayout;
+ }
+
+ @Override
+ protected void removeDetachedView(View child, boolean animate) {
+ ViewHolder vh = getChildViewHolderInt(child);
+ if (vh != null) {
+ if (vh.isTmpDetached()) {
+ vh.clearTmpDetachFlag();
+ } else if (!vh.shouldIgnore()) {
+ throw new IllegalArgumentException("Called removeDetachedView with a view which"
+ + " is not flagged as tmp detached." + vh);
+ }
+ }
+ dispatchChildDetached(child);
+ super.removeDetachedView(child, animate);
+ }
+
+ /**
+ * Returns a unique key to be used while handling change animations.
+ * It might be child's position or stable id depending on the adapter type.
+ */
+ long getChangedHolderKey(ViewHolder holder) {
+ return mAdapter.hasStableIds() ? holder.getItemId() : holder.mPosition;
+ }
+
+ void animateAppearance(@NonNull ViewHolder itemHolder,
+ @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
+ itemHolder.setIsRecyclable(false);
+ if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
+ postAnimationRunner();
+ }
+ }
+
+ void animateDisappearance(@NonNull ViewHolder holder,
+ @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
+ addAnimatingView(holder);
+ holder.setIsRecyclable(false);
+ if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
+ postAnimationRunner();
+ }
+ }
+
+ private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder,
+ @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo,
+ boolean oldHolderDisappearing, boolean newHolderDisappearing) {
+ oldHolder.setIsRecyclable(false);
+ if (oldHolderDisappearing) {
+ addAnimatingView(oldHolder);
+ }
+ if (oldHolder != newHolder) {
+ if (newHolderDisappearing) {
+ addAnimatingView(newHolder);
+ }
+ oldHolder.mShadowedHolder = newHolder;
+ // old holder should disappear after animation ends
+ addAnimatingView(oldHolder);
+ mRecycler.unscrapView(oldHolder);
+ newHolder.setIsRecyclable(false);
+ newHolder.mShadowingHolder = oldHolder;
+ }
+ if (mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) {
+ postAnimationRunner();
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ Trace.beginSection(TRACE_ON_LAYOUT_TAG);
+ dispatchLayout();
+ Trace.endSection();
+ mFirstLayoutComplete = true;
+ }
+
+ @Override
+ public void requestLayout() {
+ if (mEatRequestLayout == 0 && !mLayoutFrozen) {
+ super.requestLayout();
+ } else {
+ mLayoutRequestEaten = true;
+ }
+ }
+
+ void markItemDecorInsetsDirty() {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = mChildHelper.getUnfilteredChildAt(i);
+ ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
+ }
+ mRecycler.markItemDecorInsetsDirty();
+ }
+
+ @Override
+ public void draw(Canvas c) {
+ super.draw(c);
+
+ final int count = mItemDecorations.size();
+ for (int i = 0; i < count; i++) {
+ mItemDecorations.get(i).onDrawOver(c, this, mState);
+ }
+ // TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we
+ // need find children closest to edges. Not sure if it is worth the effort.
+ boolean needsInvalidate = false;
+ if (mLeftGlow != null && !mLeftGlow.isFinished()) {
+ final int restore = c.save();
+ final int padding = mClipToPadding ? getPaddingBottom() : 0;
+ c.rotate(270);
+ c.translate(-getHeight() + padding, 0);
+ needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c);
+ c.restoreToCount(restore);
+ }
+ if (mTopGlow != null && !mTopGlow.isFinished()) {
+ final int restore = c.save();
+ if (mClipToPadding) {
+ c.translate(getPaddingLeft(), getPaddingTop());
+ }
+ needsInvalidate |= mTopGlow != null && mTopGlow.draw(c);
+ c.restoreToCount(restore);
+ }
+ if (mRightGlow != null && !mRightGlow.isFinished()) {
+ final int restore = c.save();
+ final int width = getWidth();
+ final int padding = mClipToPadding ? getPaddingTop() : 0;
+ c.rotate(90);
+ c.translate(-padding, -width);
+ needsInvalidate |= mRightGlow != null && mRightGlow.draw(c);
+ c.restoreToCount(restore);
+ }
+ if (mBottomGlow != null && !mBottomGlow.isFinished()) {
+ final int restore = c.save();
+ c.rotate(180);
+ if (mClipToPadding) {
+ c.translate(-getWidth() + getPaddingRight(), -getHeight() + getPaddingBottom());
+ } else {
+ c.translate(-getWidth(), -getHeight());
+ }
+ needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c);
+ c.restoreToCount(restore);
+ }
+
+ // If some views are animating, ItemDecorators are likely to move/change with them.
+ // Invalidate RecyclerView to re-draw decorators. This is still efficient because children's
+ // display lists are not invalidated.
+ if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0
+ && mItemAnimator.isRunning()) {
+ needsInvalidate = true;
+ }
+
+ if (needsInvalidate) {
+ postInvalidateOnAnimation();
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ super.onDraw(c);
+
+ final int count = mItemDecorations.size();
+ for (int i = 0; i < count; i++) {
+ mItemDecorations.get(i).onDraw(c, this, mState);
+ }
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p);
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+ if (mLayout == null) {
+ throw new IllegalStateException("RecyclerView has no LayoutManager");
+ }
+ return mLayout.generateDefaultLayoutParams();
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ if (mLayout == null) {
+ throw new IllegalStateException("RecyclerView has no LayoutManager");
+ }
+ return mLayout.generateLayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ if (mLayout == null) {
+ throw new IllegalStateException("RecyclerView has no LayoutManager");
+ }
+ return mLayout.generateLayoutParams(p);
+ }
+
+ /**
+ * Returns true if RecyclerView is currently running some animations.
+ * <p>
+ * If you want to be notified when animations are finished, use
+ * {@link ItemAnimator#isRunning(ItemAnimator.ItemAnimatorFinishedListener)}.
+ *
+ * @return True if there are some item animations currently running or waiting to be started.
+ */
+ public boolean isAnimating() {
+ return mItemAnimator != null && mItemAnimator.isRunning();
+ }
+
+ void saveOldPositions() {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (DEBUG && holder.mPosition == -1 && !holder.isRemoved()) {
+ throw new IllegalStateException("view holder cannot have position -1 unless it"
+ + " is removed");
+ }
+ if (!holder.shouldIgnore()) {
+ holder.saveOldPosition();
+ }
+ }
+ }
+
+ void clearOldPositions() {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (!holder.shouldIgnore()) {
+ holder.clearOldPosition();
+ }
+ }
+ mRecycler.clearOldPositions();
+ }
+
+ void offsetPositionRecordsForMove(int from, int to) {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ final int start, end, inBetweenOffset;
+ if (from < to) {
+ start = from;
+ end = to;
+ inBetweenOffset = -1;
+ } else {
+ start = to;
+ end = from;
+ inBetweenOffset = 1;
+ }
+
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder == null || holder.mPosition < start || holder.mPosition > end) {
+ continue;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "offsetPositionRecordsForMove attached child " + i + " holder "
+ + holder);
+ }
+ if (holder.mPosition == from) {
+ holder.offsetPosition(to - from, false);
+ } else {
+ holder.offsetPosition(inBetweenOffset, false);
+ }
+
+ mState.mStructureChanged = true;
+ }
+ mRecycler.offsetPositionRecordsForMove(from, to);
+ requestLayout();
+ }
+
+ void offsetPositionRecordsForInsert(int positionStart, int itemCount) {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) {
+ if (DEBUG) {
+ Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder "
+ + holder + " now at position " + (holder.mPosition + itemCount));
+ }
+ holder.offsetPosition(itemCount, false);
+ mState.mStructureChanged = true;
+ }
+ }
+ mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);
+ requestLayout();
+ }
+
+ void offsetPositionRecordsForRemove(int positionStart, int itemCount,
+ boolean applyToPreLayout) {
+ final int positionEnd = positionStart + itemCount;
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder != null && !holder.shouldIgnore()) {
+ if (holder.mPosition >= positionEnd) {
+ if (DEBUG) {
+ Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i
+ + " holder " + holder + " now at position "
+ + (holder.mPosition - itemCount));
+ }
+ holder.offsetPosition(-itemCount, applyToPreLayout);
+ mState.mStructureChanged = true;
+ } else if (holder.mPosition >= positionStart) {
+ if (DEBUG) {
+ Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i
+ + " holder " + holder + " now REMOVED");
+ }
+ holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount,
+ applyToPreLayout);
+ mState.mStructureChanged = true;
+ }
+ }
+ }
+ mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout);
+ requestLayout();
+ }
+
+ /**
+ * Rebind existing views for the given range, or create as needed.
+ *
+ * @param positionStart Adapter position to start at
+ * @param itemCount Number of views that must explicitly be rebound
+ */
+ void viewRangeUpdate(int positionStart, int itemCount, Object payload) {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ final int positionEnd = positionStart + itemCount;
+
+ for (int i = 0; i < childCount; i++) {
+ final View child = mChildHelper.getUnfilteredChildAt(i);
+ final ViewHolder holder = getChildViewHolderInt(child);
+ if (holder == null || holder.shouldIgnore()) {
+ continue;
+ }
+ if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
+ // We re-bind these view holders after pre-processing is complete so that
+ // ViewHolders have their final positions assigned.
+ holder.addFlags(ViewHolder.FLAG_UPDATE);
+ holder.addChangePayload(payload);
+ // lp cannot be null since we get ViewHolder from it.
+ ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
+ }
+ }
+ mRecycler.viewRangeUpdate(positionStart, itemCount);
+ }
+
+ boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
+ return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,
+ viewHolder.getUnmodifiedPayloads());
+ }
+
+
+ /**
+ * Call this method to signal that *all* adapter content has changed (generally, because of
+ * swapAdapter, or notifyDataSetChanged), and that once layout occurs, all attached items should
+ * be discarded or animated. Note that this work is deferred because RecyclerView requires a
+ * layout to resolve non-incremental changes to the data set.
+ *
+ * Attached items are labeled as position unknown, and may no longer be cached.
+ *
+ * It is still possible for items to be prefetched while mDataSetHasChangedAfterLayout == true,
+ * so calling this method *must* be associated with marking the cache invalid, so that the
+ * only valid items that remain in the cache, once layout occurs, are prefetched items.
+ */
+ void setDataSetChangedAfterLayout() {
+ if (mDataSetHasChangedAfterLayout) {
+ return;
+ }
+ mDataSetHasChangedAfterLayout = true;
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder != null && !holder.shouldIgnore()) {
+ holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
+ }
+ }
+ mRecycler.setAdapterPositionsAsUnknown();
+
+ // immediately mark all views as invalid, so prefetched views can be
+ // differentiated from views bound to previous data set - both in children, and cache
+ markKnownViewsInvalid();
+ }
+
+ /**
+ * Mark all known views as invalid. Used in response to a, "the whole world might have changed"
+ * data change event.
+ */
+ void markKnownViewsInvalid() {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder != null && !holder.shouldIgnore()) {
+ holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
+ }
+ }
+ markItemDecorInsetsDirty();
+ mRecycler.markKnownViewsInvalid();
+ }
+
+ /**
+ * Invalidates all ItemDecorations. If RecyclerView has item decorations, calling this method
+ * will trigger a {@link #requestLayout()} call.
+ */
+ public void invalidateItemDecorations() {
+ if (mItemDecorations.size() == 0) {
+ return;
+ }
+ if (mLayout != null) {
+ mLayout.assertNotInLayoutOrScroll("Cannot invalidate item decorations during a scroll"
+ + " or layout");
+ }
+ markItemDecorInsetsDirty();
+ requestLayout();
+ }
+
+ /**
+ * Returns true if the RecyclerView should attempt to preserve currently focused Adapter Item's
+ * focus even if the View representing the Item is replaced during a layout calculation.
+ * <p>
+ * By default, this value is {@code true}.
+ *
+ * @return True if the RecyclerView will try to preserve focused Item after a layout if it loses
+ * focus.
+ *
+ * @see #setPreserveFocusAfterLayout(boolean)
+ */
+ public boolean getPreserveFocusAfterLayout() {
+ return mPreserveFocusAfterLayout;
+ }
+
+ /**
+ * Set whether the RecyclerView should try to keep the same Item focused after a layout
+ * calculation or not.
+ * <p>
+ * Usually, LayoutManagers keep focused views visible before and after layout but sometimes,
+ * views may lose focus during a layout calculation as their state changes or they are replaced
+ * with another view due to type change or animation. In these cases, RecyclerView can request
+ * focus on the new view automatically.
+ *
+ * @param preserveFocusAfterLayout Whether RecyclerView should preserve focused Item during a
+ * layout calculations. Defaults to true.
+ *
+ * @see #getPreserveFocusAfterLayout()
+ */
+ public void setPreserveFocusAfterLayout(boolean preserveFocusAfterLayout) {
+ mPreserveFocusAfterLayout = preserveFocusAfterLayout;
+ }
+
+ /**
+ * Retrieve the {@link ViewHolder} for the given child view.
+ *
+ * @param child Child of this RecyclerView to query for its ViewHolder
+ * @return The child view's ViewHolder
+ */
+ public ViewHolder getChildViewHolder(View child) {
+ final ViewParent parent = child.getParent();
+ if (parent != null && parent != this) {
+ throw new IllegalArgumentException("View " + child + " is not a direct child of "
+ + this);
+ }
+ return getChildViewHolderInt(child);
+ }
+
+ /**
+ * Traverses the ancestors of the given view and returns the item view that contains it and
+ * also a direct child of the RecyclerView. This returned view can be used to get the
+ * ViewHolder by calling {@link #getChildViewHolder(View)}.
+ *
+ * @param view The view that is a descendant of the RecyclerView.
+ *
+ * @return The direct child of the RecyclerView which contains the given view or null if the
+ * provided view is not a descendant of this RecyclerView.
+ *
+ * @see #getChildViewHolder(View)
+ * @see #findContainingViewHolder(View)
+ */
+ @Nullable
+ public View findContainingItemView(View view) {
+ ViewParent parent = view.getParent();
+ while (parent != null && parent != this && parent instanceof View) {
+ view = (View) parent;
+ parent = view.getParent();
+ }
+ return parent == this ? view : null;
+ }
+
+ /**
+ * Returns the ViewHolder that contains the given view.
+ *
+ * @param view The view that is a descendant of the RecyclerView.
+ *
+ * @return The ViewHolder that contains the given view or null if the provided view is not a
+ * descendant of this RecyclerView.
+ */
+ @Nullable
+ public ViewHolder findContainingViewHolder(View view) {
+ View itemView = findContainingItemView(view);
+ return itemView == null ? null : getChildViewHolder(itemView);
+ }
+
+
+ static ViewHolder getChildViewHolderInt(View child) {
+ if (child == null) {
+ return null;
+ }
+ return ((LayoutParams) child.getLayoutParams()).mViewHolder;
+ }
+
+ /**
+ * @deprecated use {@link #getChildAdapterPosition(View)} or
+ * {@link #getChildLayoutPosition(View)}.
+ */
+ @Deprecated
+ public int getChildPosition(View child) {
+ return getChildAdapterPosition(child);
+ }
+
+ /**
+ * Return the adapter position that the given child view corresponds to.
+ *
+ * @param child Child View to query
+ * @return Adapter position corresponding to the given view or {@link #NO_POSITION}
+ */
+ public int getChildAdapterPosition(View child) {
+ final ViewHolder holder = getChildViewHolderInt(child);
+ return holder != null ? holder.getAdapterPosition() : NO_POSITION;
+ }
+
+ /**
+ * Return the adapter position of the given child view as of the latest completed layout pass.
+ * <p>
+ * This position may not be equal to Item's adapter position if there are pending changes
+ * in the adapter which have not been reflected to the layout yet.
+ *
+ * @param child Child View to query
+ * @return Adapter position of the given View as of last layout pass or {@link #NO_POSITION} if
+ * the View is representing a removed item.
+ */
+ public int getChildLayoutPosition(View child) {
+ final ViewHolder holder = getChildViewHolderInt(child);
+ return holder != null ? holder.getLayoutPosition() : NO_POSITION;
+ }
+
+ /**
+ * Return the stable item id that the given child view corresponds to.
+ *
+ * @param child Child View to query
+ * @return Item id corresponding to the given view or {@link #NO_ID}
+ */
+ public long getChildItemId(View child) {
+ if (mAdapter == null || !mAdapter.hasStableIds()) {
+ return NO_ID;
+ }
+ final ViewHolder holder = getChildViewHolderInt(child);
+ return holder != null ? holder.getItemId() : NO_ID;
+ }
+
+ /**
+ * @deprecated use {@link #findViewHolderForLayoutPosition(int)} or
+ * {@link #findViewHolderForAdapterPosition(int)}
+ */
+ @Deprecated
+ public ViewHolder findViewHolderForPosition(int position) {
+ return findViewHolderForPosition(position, false);
+ }
+
+ /**
+ * Return the ViewHolder for the item in the given position of the data set as of the latest
+ * layout pass.
+ * <p>
+ * This method checks only the children of RecyclerView. If the item at the given
+ * <code>position</code> is not laid out, it <em>will not</em> create a new one.
+ * <p>
+ * Note that when Adapter contents change, ViewHolder positions are not updated until the
+ * next layout calculation. If there are pending adapter updates, the return value of this
+ * method may not match your adapter contents. You can use
+ * #{@link ViewHolder#getAdapterPosition()} to get the current adapter position of a ViewHolder.
+ * <p>
+ * When the ItemAnimator is running a change animation, there might be 2 ViewHolders
+ * with the same layout position representing the same Item. In this case, the updated
+ * ViewHolder will be returned.
+ *
+ * @param position The position of the item in the data set of the adapter
+ * @return The ViewHolder at <code>position</code> or null if there is no such item
+ */
+ public ViewHolder findViewHolderForLayoutPosition(int position) {
+ return findViewHolderForPosition(position, false);
+ }
+
+ /**
+ * Return the ViewHolder for the item in the given position of the data set. Unlike
+ * {@link #findViewHolderForLayoutPosition(int)} this method takes into account any pending
+ * adapter changes that may not be reflected to the layout yet. On the other hand, if
+ * {@link Adapter#notifyDataSetChanged()} has been called but the new layout has not been
+ * calculated yet, this method will return <code>null</code> since the new positions of views
+ * are unknown until the layout is calculated.
+ * <p>
+ * This method checks only the children of RecyclerView. If the item at the given
+ * <code>position</code> is not laid out, it <em>will not</em> create a new one.
+ * <p>
+ * When the ItemAnimator is running a change animation, there might be 2 ViewHolders
+ * representing the same Item. In this case, the updated ViewHolder will be returned.
+ *
+ * @param position The position of the item in the data set of the adapter
+ * @return The ViewHolder at <code>position</code> or null if there is no such item
+ */
+ public ViewHolder findViewHolderForAdapterPosition(int position) {
+ if (mDataSetHasChangedAfterLayout) {
+ return null;
+ }
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ // hidden VHs are not preferred but if that is the only one we find, we rather return it
+ ViewHolder hidden = null;
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder != null && !holder.isRemoved()
+ && getAdapterPositionFor(holder) == position) {
+ if (mChildHelper.isHidden(holder.itemView)) {
+ hidden = holder;
+ } else {
+ return holder;
+ }
+ }
+ }
+ return hidden;
+ }
+
+ ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ ViewHolder hidden = null;
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder != null && !holder.isRemoved()) {
+ if (checkNewPosition) {
+ if (holder.mPosition != position) {
+ continue;
+ }
+ } else if (holder.getLayoutPosition() != position) {
+ continue;
+ }
+ if (mChildHelper.isHidden(holder.itemView)) {
+ hidden = holder;
+ } else {
+ return holder;
+ }
+ }
+ }
+ // This method should not query cached views. It creates a problem during adapter updates
+ // when we are dealing with already laid out views. Also, for the public method, it is more
+ // reasonable to return null if position is not laid out.
+ return hidden;
+ }
+
+ /**
+ * Return the ViewHolder for the item with the given id. The RecyclerView must
+ * use an Adapter with {@link Adapter#setHasStableIds(boolean) stableIds} to
+ * return a non-null value.
+ * <p>
+ * This method checks only the children of RecyclerView. If the item with the given
+ * <code>id</code> is not laid out, it <em>will not</em> create a new one.
+ *
+ * When the ItemAnimator is running a change animation, there might be 2 ViewHolders with the
+ * same id. In this case, the updated ViewHolder will be returned.
+ *
+ * @param id The id for the requested item
+ * @return The ViewHolder with the given <code>id</code> or null if there is no such item
+ */
+ public ViewHolder findViewHolderForItemId(long id) {
+ if (mAdapter == null || !mAdapter.hasStableIds()) {
+ return null;
+ }
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ ViewHolder hidden = null;
+ for (int i = 0; i < childCount; i++) {
+ final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+ if (holder != null && !holder.isRemoved() && holder.getItemId() == id) {
+ if (mChildHelper.isHidden(holder.itemView)) {
+ hidden = holder;
+ } else {
+ return holder;
+ }
+ }
+ }
+ return hidden;
+ }
+
+ /**
+ * Find the topmost view under the given point.
+ *
+ * @param x Horizontal position in pixels to search
+ * @param y Vertical position in pixels to search
+ * @return The child view under (x, y) or null if no matching child is found
+ */
+ public View findChildViewUnder(float x, float y) {
+ final int count = mChildHelper.getChildCount();
+ for (int i = count - 1; i >= 0; i--) {
+ final View child = mChildHelper.getChildAt(i);
+ final float translationX = child.getTranslationX();
+ final float translationY = child.getTranslationY();
+ if (x >= child.getLeft() + translationX
+ && x <= child.getRight() + translationX
+ && y >= child.getTop() + translationY
+ && y <= child.getBottom() + translationY) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ return super.drawChild(canvas, child, drawingTime);
+ }
+
+ /**
+ * Offset the bounds of all child views by <code>dy</code> pixels.
+ * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}.
+ *
+ * @param dy Vertical pixel offset to apply to the bounds of all child views
+ */
+ public void offsetChildrenVertical(int dy) {
+ final int childCount = mChildHelper.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ mChildHelper.getChildAt(i).offsetTopAndBottom(dy);
+ }
+ }
+
+ /**
+ * Called when an item view is attached to this RecyclerView.
+ *
+ * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications
+ * of child views as they become attached. This will be called before a
+ * {@link LayoutManager} measures or lays out the view and is a good time to perform these
+ * changes.</p>
+ *
+ * @param child Child view that is now attached to this RecyclerView and its associated window
+ */
+ public void onChildAttachedToWindow(View child) {
+ }
+
+ /**
+ * Called when an item view is detached from this RecyclerView.
+ *
+ * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications
+ * of child views as they become detached. This will be called as a
+ * {@link LayoutManager} fully detaches the child view from the parent and its window.</p>
+ *
+ * @param child Child view that is now detached from this RecyclerView and its associated window
+ */
+ public void onChildDetachedFromWindow(View child) {
+ }
+
+ /**
+ * Offset the bounds of all child views by <code>dx</code> pixels.
+ * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}.
+ *
+ * @param dx Horizontal pixel offset to apply to the bounds of all child views
+ */
+ public void offsetChildrenHorizontal(int dx) {
+ final int childCount = mChildHelper.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ mChildHelper.getChildAt(i).offsetLeftAndRight(dx);
+ }
+ }
+
+ /**
+ * Returns the bounds of the view including its decoration and margins.
+ *
+ * @param view The view element to check
+ * @param outBounds A rect that will receive the bounds of the element including its
+ * decoration and margins.
+ */
+ public void getDecoratedBoundsWithMargins(View view, Rect outBounds) {
+ getDecoratedBoundsWithMarginsInt(view, outBounds);
+ }
+
+ static void getDecoratedBoundsWithMarginsInt(View view, Rect outBounds) {
+ final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ final Rect insets = lp.mDecorInsets;
+ outBounds.set(view.getLeft() - insets.left - lp.leftMargin,
+ view.getTop() - insets.top - lp.topMargin,
+ view.getRight() + insets.right + lp.rightMargin,
+ view.getBottom() + insets.bottom + lp.bottomMargin);
+ }
+
+ Rect getItemDecorInsetsForChild(View child) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (!lp.mInsetsDirty) {
+ return lp.mDecorInsets;
+ }
+
+ if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
+ // changed/invalid items should not be updated until they are rebound.
+ return lp.mDecorInsets;
+ }
+ final Rect insets = lp.mDecorInsets;
+ insets.set(0, 0, 0, 0);
+ final int decorCount = mItemDecorations.size();
+ for (int i = 0; i < decorCount; i++) {
+ mTempRect.set(0, 0, 0, 0);
+ mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
+ insets.left += mTempRect.left;
+ insets.top += mTempRect.top;
+ insets.right += mTempRect.right;
+ insets.bottom += mTempRect.bottom;
+ }
+ lp.mInsetsDirty = false;
+ return insets;
+ }
+
+ /**
+ * Called when the scroll position of this RecyclerView changes. Subclasses should use
+ * this method to respond to scrolling within the adapter's data set instead of an explicit
+ * listener.
+ *
+ * <p>This method will always be invoked before listeners. If a subclass needs to perform
+ * any additional upkeep or bookkeeping after scrolling but before listeners run,
+ * this is a good place to do so.</p>
+ *
+ * <p>This differs from {@link View#onScrollChanged(int, int, int, int)} in that it receives
+ * the distance scrolled in either direction within the adapter's data set instead of absolute
+ * scroll coordinates. Since RecyclerView cannot compute the absolute scroll position from
+ * any arbitrary point in the data set, <code>onScrollChanged</code> will always receive
+ * the current {@link View#getScrollX()} and {@link View#getScrollY()} values which
+ * do not correspond to the data set scroll position. However, some subclasses may choose
+ * to use these fields as special offsets.</p>
+ *
+ * @param dx horizontal distance scrolled in pixels
+ * @param dy vertical distance scrolled in pixels
+ */
+ public void onScrolled(int dx, int dy) {
+ // Do nothing
+ }
+
+ void dispatchOnScrolled(int hresult, int vresult) {
+ mDispatchScrollCounter++;
+ // Pass the current scrollX/scrollY values; no actual change in these properties occurred
+ // but some general-purpose code may choose to respond to changes this way.
+ final int scrollX = getScrollX();
+ final int scrollY = getScrollY();
+ onScrollChanged(scrollX, scrollY, scrollX, scrollY);
+
+ // Pass the real deltas to onScrolled, the RecyclerView-specific method.
+ onScrolled(hresult, vresult);
+
+ // Invoke listeners last. Subclassed view methods always handle the event first.
+ // All internal state is consistent by the time listeners are invoked.
+ if (mScrollListener != null) {
+ mScrollListener.onScrolled(this, hresult, vresult);
+ }
+ if (mScrollListeners != null) {
+ for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
+ mScrollListeners.get(i).onScrolled(this, hresult, vresult);
+ }
+ }
+ mDispatchScrollCounter--;
+ }
+
+ /**
+ * Called when the scroll state of this RecyclerView changes. Subclasses should use this
+ * method to respond to state changes instead of an explicit listener.
+ *
+ * <p>This method will always be invoked before listeners, but after the LayoutManager
+ * responds to the scroll state change.</p>
+ *
+ * @param state the new scroll state, one of {@link #SCROLL_STATE_IDLE},
+ * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}
+ */
+ public void onScrollStateChanged(int state) {
+ // Do nothing
+ }
+
+ void dispatchOnScrollStateChanged(int state) {
+ // Let the LayoutManager go first; this allows it to bring any properties into
+ // a consistent state before the RecyclerView subclass responds.
+ if (mLayout != null) {
+ mLayout.onScrollStateChanged(state);
+ }
+
+ // Let the RecyclerView subclass handle this event next; any LayoutManager property
+ // changes will be reflected by this time.
+ onScrollStateChanged(state);
+
+ // Listeners go last. All other internal state is consistent by this point.
+ if (mScrollListener != null) {
+ mScrollListener.onScrollStateChanged(this, state);
+ }
+ if (mScrollListeners != null) {
+ for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
+ mScrollListeners.get(i).onScrollStateChanged(this, state);
+ }
+ }
+ }
+
+ /**
+ * Returns whether there are pending adapter updates which are not yet applied to the layout.
+ * <p>
+ * If this method returns <code>true</code>, it means that what user is currently seeing may not
+ * reflect them adapter contents (depending on what has changed).
+ * You may use this information to defer or cancel some operations.
+ * <p>
+ * This method returns true if RecyclerView has not yet calculated the first layout after it is
+ * attached to the Window or the Adapter has been replaced.
+ *
+ * @return True if there are some adapter updates which are not yet reflected to layout or false
+ * if layout is up to date.
+ */
+ public boolean hasPendingAdapterUpdates() {
+ return !mFirstLayoutComplete || mDataSetHasChangedAfterLayout
+ || mAdapterHelper.hasPendingUpdates();
+ }
+
+ class ViewFlinger implements Runnable {
+ private int mLastFlingX;
+ private int mLastFlingY;
+ private OverScroller mScroller;
+ Interpolator mInterpolator = sQuinticInterpolator;
+
+
+ // When set to true, postOnAnimation callbacks are delayed until the run method completes
+ private boolean mEatRunOnAnimationRequest = false;
+
+ // Tracks if postAnimationCallback should be re-attached when it is done
+ private boolean mReSchedulePostAnimationCallback = false;
+
+ ViewFlinger() {
+ mScroller = new OverScroller(getContext(), sQuinticInterpolator);
+ }
+
+ @Override
+ public void run() {
+ if (mLayout == null) {
+ stop();
+ return; // no layout, cannot scroll.
+ }
+ disableRunOnAnimationRequests();
+ consumePendingUpdateOperations();
+ // keep a local reference so that if it is changed during onAnimation method, it won't
+ // cause unexpected behaviors
+ final OverScroller scroller = mScroller;
+ final SmoothScroller smoothScroller = mLayout.mSmoothScroller;
+ if (scroller.computeScrollOffset()) {
+ final int x = scroller.getCurrX();
+ final int y = scroller.getCurrY();
+ final int dx = x - mLastFlingX;
+ final int dy = y - mLastFlingY;
+ int hresult = 0;
+ int vresult = 0;
+ mLastFlingX = x;
+ mLastFlingY = y;
+ int overscrollX = 0, overscrollY = 0;
+ if (mAdapter != null) {
+ eatRequestLayout();
+ onEnterLayoutOrScroll();
+ Trace.beginSection(TRACE_SCROLL_TAG);
+ if (dx != 0) {
+ hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
+ overscrollX = dx - hresult;
+ }
+ if (dy != 0) {
+ vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
+ overscrollY = dy - vresult;
+ }
+ Trace.endSection();
+ repositionShadowingViews();
+
+ onExitLayoutOrScroll();
+ resumeRequestLayout(false);
+
+ if (smoothScroller != null && !smoothScroller.isPendingInitialRun()
+ && smoothScroller.isRunning()) {
+ final int adapterSize = mState.getItemCount();
+ if (adapterSize == 0) {
+ smoothScroller.stop();
+ } else if (smoothScroller.getTargetPosition() >= adapterSize) {
+ smoothScroller.setTargetPosition(adapterSize - 1);
+ smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);
+ } else {
+ smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);
+ }
+ }
+ }
+ if (!mItemDecorations.isEmpty()) {
+ invalidate();
+ }
+ if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
+ considerReleasingGlowsOnScroll(dx, dy);
+ }
+ if (overscrollX != 0 || overscrollY != 0) {
+ final int vel = (int) scroller.getCurrVelocity();
+
+ int velX = 0;
+ if (overscrollX != x) {
+ velX = overscrollX < 0 ? -vel : overscrollX > 0 ? vel : 0;
+ }
+
+ int velY = 0;
+ if (overscrollY != y) {
+ velY = overscrollY < 0 ? -vel : overscrollY > 0 ? vel : 0;
+ }
+
+ if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
+ absorbGlows(velX, velY);
+ }
+ if ((velX != 0 || overscrollX == x || scroller.getFinalX() == 0)
+ && (velY != 0 || overscrollY == y || scroller.getFinalY() == 0)) {
+ scroller.abortAnimation();
+ }
+ }
+ if (hresult != 0 || vresult != 0) {
+ dispatchOnScrolled(hresult, vresult);
+ }
+
+ if (!awakenScrollBars()) {
+ invalidate();
+ }
+
+ final boolean fullyConsumedVertical = dy != 0 && mLayout.canScrollVertically()
+ && vresult == dy;
+ final boolean fullyConsumedHorizontal = dx != 0 && mLayout.canScrollHorizontally()
+ && hresult == dx;
+ final boolean fullyConsumedAny = (dx == 0 && dy == 0) || fullyConsumedHorizontal
+ || fullyConsumedVertical;
+
+ if (scroller.isFinished() || !fullyConsumedAny) {
+ setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this.
+ if (ALLOW_THREAD_GAP_WORK) {
+ mPrefetchRegistry.clearPrefetchPositions();
+ }
+ } else {
+ postOnAnimation();
+ if (mGapWorker != null) {
+ mGapWorker.postFromTraversal(RecyclerView.this, dx, dy);
+ }
+ }
+ }
+ // call this after the onAnimation is complete not to have inconsistent callbacks etc.
+ if (smoothScroller != null) {
+ if (smoothScroller.isPendingInitialRun()) {
+ smoothScroller.onAnimation(0, 0);
+ }
+ if (!mReSchedulePostAnimationCallback) {
+ smoothScroller.stop(); //stop if it does not trigger any scroll
+ }
+ }
+ enableRunOnAnimationRequests();
+ }
+
+ private void disableRunOnAnimationRequests() {
+ mReSchedulePostAnimationCallback = false;
+ mEatRunOnAnimationRequest = true;
+ }
+
+ private void enableRunOnAnimationRequests() {
+ mEatRunOnAnimationRequest = false;
+ if (mReSchedulePostAnimationCallback) {
+ postOnAnimation();
+ }
+ }
+
+ void postOnAnimation() {
+ if (mEatRunOnAnimationRequest) {
+ mReSchedulePostAnimationCallback = true;
+ } else {
+ removeCallbacks(this);
+ RecyclerView.this.postOnAnimation(this);
+ }
+ }
+
+ public void fling(int velocityX, int velocityY) {
+ setScrollState(SCROLL_STATE_SETTLING);
+ mLastFlingX = mLastFlingY = 0;
+ mScroller.fling(0, 0, velocityX, velocityY,
+ Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
+ postOnAnimation();
+ }
+
+ public void smoothScrollBy(int dx, int dy) {
+ smoothScrollBy(dx, dy, 0, 0);
+ }
+
+ public void smoothScrollBy(int dx, int dy, int vx, int vy) {
+ smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, vx, vy));
+ }
+
+ private float distanceInfluenceForSnapDuration(float f) {
+ f -= 0.5f; // center the values about 0.
+ f *= 0.3f * Math.PI / 2.0f;
+ return (float) Math.sin(f);
+ }
+
+ private int computeScrollDuration(int dx, int dy, int vx, int vy) {
+ final int absDx = Math.abs(dx);
+ final int absDy = Math.abs(dy);
+ final boolean horizontal = absDx > absDy;
+ final int velocity = (int) Math.sqrt(vx * vx + vy * vy);
+ final int delta = (int) Math.sqrt(dx * dx + dy * dy);
+ final int containerSize = horizontal ? getWidth() : getHeight();
+ final int halfContainerSize = containerSize / 2;
+ final float distanceRatio = Math.min(1.f, 1.f * delta / containerSize);
+ final float distance = halfContainerSize + halfContainerSize
+ * distanceInfluenceForSnapDuration(distanceRatio);
+
+ final int duration;
+ if (velocity > 0) {
+ duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
+ } else {
+ float absDelta = (float) (horizontal ? absDx : absDy);
+ duration = (int) (((absDelta / containerSize) + 1) * 300);
+ }
+ return Math.min(duration, MAX_SCROLL_DURATION);
+ }
+
+ public void smoothScrollBy(int dx, int dy, int duration) {
+ smoothScrollBy(dx, dy, duration, sQuinticInterpolator);
+ }
+
+ public void smoothScrollBy(int dx, int dy, Interpolator interpolator) {
+ smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, 0, 0),
+ interpolator == null ? sQuinticInterpolator : interpolator);
+ }
+
+ public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) {
+ if (mInterpolator != interpolator) {
+ mInterpolator = interpolator;
+ mScroller = new OverScroller(getContext(), interpolator);
+ }
+ setScrollState(SCROLL_STATE_SETTLING);
+ mLastFlingX = mLastFlingY = 0;
+ mScroller.startScroll(0, 0, dx, dy, duration);
+ postOnAnimation();
+ }
+
+ public void stop() {
+ removeCallbacks(this);
+ mScroller.abortAnimation();
+ }
+
+ }
+
+ void repositionShadowingViews() {
+ // Fix up shadow views used by change animations
+ int count = mChildHelper.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View view = mChildHelper.getChildAt(i);
+ ViewHolder holder = getChildViewHolder(view);
+ if (holder != null && holder.mShadowingHolder != null) {
+ View shadowingView = holder.mShadowingHolder.itemView;
+ int left = view.getLeft();
+ int top = view.getTop();
+ if (left != shadowingView.getLeft() || top != shadowingView.getTop()) {
+ shadowingView.layout(left, top,
+ left + shadowingView.getWidth(),
+ top + shadowingView.getHeight());
+ }
+ }
+ }
+ }
+
+ private class RecyclerViewDataObserver extends AdapterDataObserver {
+ RecyclerViewDataObserver() {
+ }
+
+ @Override
+ public void onChanged() {
+ assertNotInLayoutOrScroll(null);
+ mState.mStructureChanged = true;
+
+ setDataSetChangedAfterLayout();
+ if (!mAdapterHelper.hasPendingUpdates()) {
+ requestLayout();
+ }
+ }
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ assertNotInLayoutOrScroll(null);
+ if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
+ triggerUpdateProcessor();
+ }
+ }
+
+ @Override
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ assertNotInLayoutOrScroll(null);
+ if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
+ triggerUpdateProcessor();
+ }
+ }
+
+ @Override
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ assertNotInLayoutOrScroll(null);
+ if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
+ triggerUpdateProcessor();
+ }
+ }
+
+ @Override
+ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+ assertNotInLayoutOrScroll(null);
+ if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
+ triggerUpdateProcessor();
+ }
+ }
+
+ void triggerUpdateProcessor() {
+ if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
+ RecyclerView.this.postOnAnimation(mUpdateChildViewsRunnable);
+ } else {
+ mAdapterUpdateDuringMeasure = true;
+ requestLayout();
+ }
+ }
+ }
+
+ /**
+ * RecycledViewPool lets you share Views between multiple RecyclerViews.
+ * <p>
+ * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
+ * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
+ * <p>
+ * RecyclerView automatically creates a pool for itself if you don't provide one.
+ *
+ */
+ public static class RecycledViewPool {
+ private static final int DEFAULT_MAX_SCRAP = 5;
+
+ /**
+ * Tracks both pooled holders, as well as create/bind timing metadata for the given type.
+ *
+ * Note that this tracks running averages of create/bind time across all RecyclerViews
+ * (and, indirectly, Adapters) that use this pool.
+ *
+ * 1) This enables us to track average create and bind times across multiple adapters. Even
+ * though create (and especially bind) may behave differently for different Adapter
+ * subclasses, sharing the pool is a strong signal that they'll perform similarly, per type.
+ *
+ * 2) If {@link #willBindInTime(int, long, long)} returns false for one view, it will return
+ * false for all other views of its type for the same deadline. This prevents items
+ * constructed by {@link GapWorker} prefetch from being bound to a lower priority prefetch.
+ */
+ static class ScrapData {
+ ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
+ int mMaxScrap = DEFAULT_MAX_SCRAP;
+ long mCreateRunningAverageNs = 0;
+ long mBindRunningAverageNs = 0;
+ }
+ SparseArray<ScrapData> mScrap = new SparseArray<>();
+
+ private int mAttachCount = 0;
+
+ public void clear() {
+ for (int i = 0; i < mScrap.size(); i++) {
+ ScrapData data = mScrap.valueAt(i);
+ data.mScrapHeap.clear();
+ }
+ }
+
+ public void setMaxRecycledViews(int viewType, int max) {
+ ScrapData scrapData = getScrapDataForType(viewType);
+ scrapData.mMaxScrap = max;
+ final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
+ if (scrapHeap != null) {
+ while (scrapHeap.size() > max) {
+ scrapHeap.remove(scrapHeap.size() - 1);
+ }
+ }
+ }
+
+ /**
+ * Returns the current number of Views held by the RecycledViewPool of the given view type.
+ */
+ public int getRecycledViewCount(int viewType) {
+ return getScrapDataForType(viewType).mScrapHeap.size();
+ }
+
+ public ViewHolder getRecycledView(int viewType) {
+ final ScrapData scrapData = mScrap.get(viewType);
+ if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
+ final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
+ return scrapHeap.remove(scrapHeap.size() - 1);
+ }
+ return null;
+ }
+
+ int size() {
+ int count = 0;
+ for (int i = 0; i < mScrap.size(); i++) {
+ ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i).mScrapHeap;
+ if (viewHolders != null) {
+ count += viewHolders.size();
+ }
+ }
+ return count;
+ }
+
+ public void putRecycledView(ViewHolder scrap) {
+ final int viewType = scrap.getItemViewType();
+ final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap;
+ if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
+ return;
+ }
+ if (DEBUG && scrapHeap.contains(scrap)) {
+ throw new IllegalArgumentException("this scrap item already exists");
+ }
+ scrap.resetInternal();
+ scrapHeap.add(scrap);
+ }
+
+ long runningAverage(long oldAverage, long newValue) {
+ if (oldAverage == 0) {
+ return newValue;
+ }
+ return (oldAverage / 4 * 3) + (newValue / 4);
+ }
+
+ void factorInCreateTime(int viewType, long createTimeNs) {
+ ScrapData scrapData = getScrapDataForType(viewType);
+ scrapData.mCreateRunningAverageNs = runningAverage(
+ scrapData.mCreateRunningAverageNs, createTimeNs);
+ }
+
+ void factorInBindTime(int viewType, long bindTimeNs) {
+ ScrapData scrapData = getScrapDataForType(viewType);
+ scrapData.mBindRunningAverageNs = runningAverage(
+ scrapData.mBindRunningAverageNs, bindTimeNs);
+ }
+
+ boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) {
+ long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs;
+ return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs);
+ }
+
+ boolean willBindInTime(int viewType, long approxCurrentNs, long deadlineNs) {
+ long expectedDurationNs = getScrapDataForType(viewType).mBindRunningAverageNs;
+ return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs);
+ }
+
+ void attach(Adapter adapter) {
+ mAttachCount++;
+ }
+
+ void detach() {
+ mAttachCount--;
+ }
+
+
+ /**
+ * Detaches the old adapter and attaches the new one.
+ * <p>
+ * RecycledViewPool will clear its cache if it has only one adapter attached and the new
+ * adapter uses a different ViewHolder than the oldAdapter.
+ *
+ * @param oldAdapter The previous adapter instance. Will be detached.
+ * @param newAdapter The new adapter instance. Will be attached.
+ * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same
+ * ViewHolder and view types.
+ */
+ void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
+ boolean compatibleWithPrevious) {
+ if (oldAdapter != null) {
+ detach();
+ }
+ if (!compatibleWithPrevious && mAttachCount == 0) {
+ clear();
+ }
+ if (newAdapter != null) {
+ attach(newAdapter);
+ }
+ }
+
+ private ScrapData getScrapDataForType(int viewType) {
+ ScrapData scrapData = mScrap.get(viewType);
+ if (scrapData == null) {
+ scrapData = new ScrapData();
+ mScrap.put(viewType, scrapData);
+ }
+ return scrapData;
+ }
+ }
+
+ /**
+ * Utility method for finding an internal RecyclerView, if present
+ */
+ @Nullable
+ static RecyclerView findNestedRecyclerView(@NonNull View view) {
+ if (!(view instanceof ViewGroup)) {
+ return null;
+ }
+ if (view instanceof RecyclerView) {
+ return (RecyclerView) view;
+ }
+ final ViewGroup parent = (ViewGroup) view;
+ final int count = parent.getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = parent.getChildAt(i);
+ final RecyclerView descendant = findNestedRecyclerView(child);
+ if (descendant != null) {
+ return descendant;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Utility method for clearing holder's internal RecyclerView, if present
+ */
+ static void clearNestedRecyclerViewIfNotNested(@NonNull ViewHolder holder) {
+ if (holder.mNestedRecyclerView != null) {
+ View item = holder.mNestedRecyclerView.get();
+ while (item != null) {
+ if (item == holder.itemView) {
+ return; // match found, don't need to clear
+ }
+
+ ViewParent parent = item.getParent();
+ if (parent instanceof View) {
+ item = (View) parent;
+ } else {
+ item = null;
+ }
+ }
+ holder.mNestedRecyclerView = null; // not nested
+ }
+ }
+
+ /**
+ * Time base for deadline-aware work scheduling. Overridable for testing.
+ *
+ * Will return 0 to avoid cost of System.nanoTime where deadline-aware work scheduling
+ * isn't relevant.
+ */
+ long getNanoTime() {
+ if (ALLOW_THREAD_GAP_WORK) {
+ return System.nanoTime();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * A Recycler is responsible for managing scrapped or detached item views for reuse.
+ *
+ * <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but
+ * that has been marked for removal or reuse.</p>
+ *
+ * <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for
+ * an adapter's data set representing the data at a given position or item ID.
+ * If the view to be reused is considered "dirty" the adapter will be asked to rebind it.
+ * If not, the view can be quickly reused by the LayoutManager with no further work.
+ * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout}
+ * may be repositioned by a LayoutManager without remeasurement.</p>
+ */
+ public final class Recycler {
+ final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
+ ArrayList<ViewHolder> mChangedScrap = null;
+
+ final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
+
+ private final List<ViewHolder>
+ mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
+
+ private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
+ int mViewCacheMax = DEFAULT_CACHE_SIZE;
+
+ RecycledViewPool mRecyclerPool;
+
+ private ViewCacheExtension mViewCacheExtension;
+
+ static final int DEFAULT_CACHE_SIZE = 2;
+
+ /**
+ * Clear scrap views out of this recycler. Detached views contained within a
+ * recycled view pool will remain.
+ */
+ public void clear() {
+ mAttachedScrap.clear();
+ recycleAndClearCachedViews();
+ }
+
+ /**
+ * Set the maximum number of detached, valid views we should retain for later use.
+ *
+ * @param viewCount Number of views to keep before sending views to the shared pool
+ */
+ public void setViewCacheSize(int viewCount) {
+ mRequestedCacheMax = viewCount;
+ updateViewCacheSize();
+ }
+
+ void updateViewCacheSize() {
+ int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0;
+ mViewCacheMax = mRequestedCacheMax + extraCache;
+
+ // first, try the views that can be recycled
+ for (int i = mCachedViews.size() - 1;
+ i >= 0 && mCachedViews.size() > mViewCacheMax; i--) {
+ recycleCachedViewAt(i);
+ }
+ }
+
+ /**
+ * Returns an unmodifiable list of ViewHolders that are currently in the scrap list.
+ *
+ * @return List of ViewHolders in the scrap list.
+ */
+ public List<ViewHolder> getScrapList() {
+ return mUnmodifiableAttachedScrap;
+ }
+
+ /**
+ * Helper method for getViewForPosition.
+ * <p>
+ * Checks whether a given view holder can be used for the provided position.
+ *
+ * @param holder ViewHolder
+ * @return true if ViewHolder matches the provided position, false otherwise
+ */
+ boolean validateViewHolderForOffsetPosition(ViewHolder holder) {
+ // if it is a removed holder, nothing to verify since we cannot ask adapter anymore
+ // if it is not removed, verify the type and id.
+ if (holder.isRemoved()) {
+ if (DEBUG && !mState.isPreLayout()) {
+ throw new IllegalStateException("should not receive a removed view unless it"
+ + " is pre layout");
+ }
+ return mState.isPreLayout();
+ }
+ if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) {
+ throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder "
+ + "adapter position" + holder);
+ }
+ if (!mState.isPreLayout()) {
+ // don't check type if it is pre-layout.
+ final int type = mAdapter.getItemViewType(holder.mPosition);
+ if (type != holder.getItemViewType()) {
+ return false;
+ }
+ }
+ if (mAdapter.hasStableIds()) {
+ return holder.getItemId() == mAdapter.getItemId(holder.mPosition);
+ }
+ return true;
+ }
+
+ /**
+ * Attempts to bind view, and account for relevant timing information. If
+ * deadlineNs != FOREVER_NS, this method may fail to bind, and return false.
+ *
+ * @param holder Holder to be bound.
+ * @param offsetPosition Position of item to be bound.
+ * @param position Pre-layout position of item to be bound.
+ * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should
+ * complete. If FOREVER_NS is passed, this method will not fail to
+ * bind the holder.
+ * @return
+ */
+ private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition,
+ int position, long deadlineNs) {
+ holder.mOwnerRecyclerView = RecyclerView.this;
+ final int viewType = holder.getItemViewType();
+ long startBindNs = getNanoTime();
+ if (deadlineNs != FOREVER_NS
+ && !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
+ // abort - we have a deadline we can't meet
+ return false;
+ }
+ mAdapter.bindViewHolder(holder, offsetPosition);
+ long endBindNs = getNanoTime();
+ mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
+ attachAccessibilityDelegate(holder.itemView);
+ if (mState.isPreLayout()) {
+ holder.mPreLayoutPosition = position;
+ }
+ return true;
+ }
+
+ /**
+ * Binds the given View to the position. The View can be a View previously retrieved via
+ * {@link #getViewForPosition(int)} or created by
+ * {@link Adapter#onCreateViewHolder(ViewGroup, int)}.
+ * <p>
+ * Generally, a LayoutManager should acquire its views via {@link #getViewForPosition(int)}
+ * and let the RecyclerView handle caching. This is a helper method for LayoutManager who
+ * wants to handle its own recycling logic.
+ * <p>
+ * Note that, {@link #getViewForPosition(int)} already binds the View to the position so
+ * you don't need to call this method unless you want to bind this View to another position.
+ *
+ * @param view The view to update.
+ * @param position The position of the item to bind to this View.
+ */
+ public void bindViewToPosition(View view, int position) {
+ ViewHolder holder = getChildViewHolderInt(view);
+ if (holder == null) {
+ throw new IllegalArgumentException("The view does not have a ViewHolder. You cannot"
+ + " pass arbitrary views to this method, they should be created by the "
+ + "Adapter");
+ }
+ final int offsetPosition = mAdapterHelper.findPositionOffset(position);
+ if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
+ throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ + "position " + position + "(offset:" + offsetPosition + ")."
+ + "state:" + mState.getItemCount());
+ }
+ tryBindViewHolderByDeadline(holder, offsetPosition, position, FOREVER_NS);
+
+ final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
+ final LayoutParams rvLayoutParams;
+ if (lp == null) {
+ rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
+ holder.itemView.setLayoutParams(rvLayoutParams);
+ } else if (!checkLayoutParams(lp)) {
+ rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
+ holder.itemView.setLayoutParams(rvLayoutParams);
+ } else {
+ rvLayoutParams = (LayoutParams) lp;
+ }
+
+ rvLayoutParams.mInsetsDirty = true;
+ rvLayoutParams.mViewHolder = holder;
+ rvLayoutParams.mPendingInvalidate = holder.itemView.getParent() == null;
+ }
+
+ /**
+ * RecyclerView provides artificial position range (item count) in pre-layout state and
+ * automatically maps these positions to {@link Adapter} positions when
+ * {@link #getViewForPosition(int)} or {@link #bindViewToPosition(View, int)} is called.
+ * <p>
+ * Usually, LayoutManager does not need to worry about this. However, in some cases, your
+ * LayoutManager may need to call some custom component with item positions in which
+ * case you need the actual adapter position instead of the pre layout position. You
+ * can use this method to convert a pre-layout position to adapter (post layout) position.
+ * <p>
+ * Note that if the provided position belongs to a deleted ViewHolder, this method will
+ * return -1.
+ * <p>
+ * Calling this method in post-layout state returns the same value back.
+ *
+ * @param position The pre-layout position to convert. Must be greater or equal to 0 and
+ * less than {@link State#getItemCount()}.
+ */
+ public int convertPreLayoutPositionToPostLayout(int position) {
+ if (position < 0 || position >= mState.getItemCount()) {
+ throw new IndexOutOfBoundsException("invalid position " + position + ". State "
+ + "item count is " + mState.getItemCount());
+ }
+ if (!mState.isPreLayout()) {
+ return position;
+ }
+ return mAdapterHelper.findPositionOffset(position);
+ }
+
+ /**
+ * Obtain a view initialized for the given position.
+ *
+ * This method should be used by {@link LayoutManager} implementations to obtain
+ * views to represent data from an {@link Adapter}.
+ * <p>
+ * The Recycler may reuse a scrap or detached view from a shared pool if one is
+ * available for the correct view type. If the adapter has not indicated that the
+ * data at the given position has changed, the Recycler will attempt to hand back
+ * a scrap view that was previously initialized for that data without rebinding.
+ *
+ * @param position Position to obtain a view for
+ * @return A view representing the data at <code>position</code> from <code>adapter</code>
+ */
+ public View getViewForPosition(int position) {
+ return getViewForPosition(position, false);
+ }
+
+ View getViewForPosition(int position, boolean dryRun) {
+ return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
+ }
+
+ /**
+ * Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
+ * cache, the RecycledViewPool, or creating it directly.
+ * <p>
+ * If a deadlineNs other than {@link #FOREVER_NS} is passed, this method early return
+ * rather than constructing or binding a ViewHolder if it doesn't think it has time.
+ * If a ViewHolder must be constructed and not enough time remains, null is returned. If a
+ * ViewHolder is aquired and must be bound but not enough time remains, an unbound holder is
+ * returned. Use {@link ViewHolder#isBound()} on the returned object to check for this.
+ *
+ * @param position Position of ViewHolder to be returned.
+ * @param dryRun True if the ViewHolder should not be removed from scrap/cache/
+ * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should
+ * complete. If FOREVER_NS is passed, this method will not fail to
+ * create/bind the holder if needed.
+ *
+ * @return ViewHolder for requested position
+ */
+ @Nullable
+ ViewHolder tryGetViewHolderForPositionByDeadline(int position,
+ boolean dryRun, long deadlineNs) {
+ if (position < 0 || position >= mState.getItemCount()) {
+ throw new IndexOutOfBoundsException("Invalid item position " + position
+ + "(" + position + "). Item count:" + mState.getItemCount());
+ }
+ boolean fromScrapOrHiddenOrCache = false;
+ ViewHolder holder = null;
+ // 0) If there is a changed scrap, try to find from there
+ if (mState.isPreLayout()) {
+ holder = getChangedScrapViewForPosition(position);
+ fromScrapOrHiddenOrCache = holder != null;
+ }
+ // 1) Find by position from scrap/hidden list/cache
+ if (holder == null) {
+ holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
+ if (holder != null) {
+ if (!validateViewHolderForOffsetPosition(holder)) {
+ // recycle holder (and unscrap if relevant) since it can't be used
+ if (!dryRun) {
+ // we would like to recycle this but need to make sure it is not used by
+ // animation logic etc.
+ holder.addFlags(ViewHolder.FLAG_INVALID);
+ if (holder.isScrap()) {
+ removeDetachedView(holder.itemView, false);
+ holder.unScrap();
+ } else if (holder.wasReturnedFromScrap()) {
+ holder.clearReturnedFromScrapFlag();
+ }
+ recycleViewHolderInternal(holder);
+ }
+ holder = null;
+ } else {
+ fromScrapOrHiddenOrCache = true;
+ }
+ }
+ }
+ if (holder == null) {
+ final int offsetPosition = mAdapterHelper.findPositionOffset(position);
+ if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
+ throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ + "position " + position + "(offset:" + offsetPosition + ")."
+ + "state:" + mState.getItemCount());
+ }
+
+ final int type = mAdapter.getItemViewType(offsetPosition);
+ // 2) Find from scrap/cache via stable ids, if exists
+ if (mAdapter.hasStableIds()) {
+ holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
+ type, dryRun);
+ if (holder != null) {
+ // update position
+ holder.mPosition = offsetPosition;
+ fromScrapOrHiddenOrCache = true;
+ }
+ }
+ if (holder == null && mViewCacheExtension != null) {
+ // We are NOT sending the offsetPosition because LayoutManager does not
+ // know it.
+ final View view = mViewCacheExtension
+ .getViewForPositionAndType(this, position, type);
+ if (view != null) {
+ holder = getChildViewHolder(view);
+ if (holder == null) {
+ throw new IllegalArgumentException("getViewForPositionAndType returned"
+ + " a view which does not have a ViewHolder");
+ } else if (holder.shouldIgnore()) {
+ throw new IllegalArgumentException("getViewForPositionAndType returned"
+ + " a view that is ignored. You must call stopIgnoring before"
+ + " returning this view.");
+ }
+ }
+ }
+ if (holder == null) { // fallback to pool
+ if (DEBUG) {
+ Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ + position + ") fetching from shared pool");
+ }
+ holder = getRecycledViewPool().getRecycledView(type);
+ if (holder != null) {
+ holder.resetInternal();
+ if (FORCE_INVALIDATE_DISPLAY_LIST) {
+ invalidateDisplayListInt(holder);
+ }
+ }
+ }
+ if (holder == null) {
+ long start = getNanoTime();
+ if (deadlineNs != FOREVER_NS
+ && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
+ // abort - we have a deadline we can't meet
+ return null;
+ }
+ holder = mAdapter.createViewHolder(RecyclerView.this, type);
+ if (ALLOW_THREAD_GAP_WORK) {
+ // only bother finding nested RV if prefetching
+ RecyclerView innerView = findNestedRecyclerView(holder.itemView);
+ if (innerView != null) {
+ holder.mNestedRecyclerView = new WeakReference<>(innerView);
+ }
+ }
+
+ long end = getNanoTime();
+ mRecyclerPool.factorInCreateTime(type, end - start);
+ if (DEBUG) {
+ Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
+ }
+ }
+ }
+
+ // This is very ugly but the only place we can grab this information
+ // before the View is rebound and returned to the LayoutManager for post layout ops.
+ // We don't need this in pre-layout since the VH is not updated by the LM.
+ if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
+ .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
+ holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
+ if (mState.mRunSimpleAnimations) {
+ int changeFlags = ItemAnimator
+ .buildAdapterChangeFlagsForAnimations(holder);
+ changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
+ final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
+ holder, changeFlags, holder.getUnmodifiedPayloads());
+ recordAnimationInfoIfBouncedHiddenView(holder, info);
+ }
+ }
+
+ boolean bound = false;
+ if (mState.isPreLayout() && holder.isBound()) {
+ // do not update unless we absolutely have to.
+ holder.mPreLayoutPosition = position;
+ } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
+ if (DEBUG && holder.isRemoved()) {
+ throw new IllegalStateException("Removed holder should be bound and it should"
+ + " come here only in pre-layout. Holder: " + holder);
+ }
+ final int offsetPosition = mAdapterHelper.findPositionOffset(position);
+ bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
+ }
+
+ final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
+ final LayoutParams rvLayoutParams;
+ if (lp == null) {
+ rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
+ holder.itemView.setLayoutParams(rvLayoutParams);
+ } else if (!checkLayoutParams(lp)) {
+ rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
+ holder.itemView.setLayoutParams(rvLayoutParams);
+ } else {
+ rvLayoutParams = (LayoutParams) lp;
+ }
+ rvLayoutParams.mViewHolder = holder;
+ rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
+ return holder;
+ }
+
+ private void attachAccessibilityDelegate(View itemView) {
+ if (isAccessibilityEnabled()) {
+ if (itemView.getImportantForAccessibility()
+ == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ itemView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
+ if (itemView.getAccessibilityDelegate() == null) {
+ itemView.setAccessibilityDelegate(mAccessibilityDelegate.getItemDelegate());
+ }
+ }
+ }
+
+ private void invalidateDisplayListInt(ViewHolder holder) {
+ if (holder.itemView instanceof ViewGroup) {
+ invalidateDisplayListInt((ViewGroup) holder.itemView, false);
+ }
+ }
+
+ private void invalidateDisplayListInt(ViewGroup viewGroup, boolean invalidateThis) {
+ for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) {
+ final View view = viewGroup.getChildAt(i);
+ if (view instanceof ViewGroup) {
+ invalidateDisplayListInt((ViewGroup) view, true);
+ }
+ }
+ if (!invalidateThis) {
+ return;
+ }
+ // we need to force it to become invisible
+ if (viewGroup.getVisibility() == View.INVISIBLE) {
+ viewGroup.setVisibility(View.VISIBLE);
+ viewGroup.setVisibility(View.INVISIBLE);
+ } else {
+ final int visibility = viewGroup.getVisibility();
+ viewGroup.setVisibility(View.INVISIBLE);
+ viewGroup.setVisibility(visibility);
+ }
+ }
+
+ /**
+ * Recycle a detached view. The specified view will be added to a pool of views
+ * for later rebinding and reuse.
+ *
+ * <p>A view must be fully detached (removed from parent) before it may be recycled. If the
+ * View is scrapped, it will be removed from scrap list.</p>
+ *
+ * @param view Removed view for recycling
+ * @see LayoutManager#removeAndRecycleView(View, Recycler)
+ */
+ public void recycleView(View view) {
+ // This public recycle method tries to make view recycle-able since layout manager
+ // intended to recycle this view (e.g. even if it is in scrap or change cache)
+ ViewHolder holder = getChildViewHolderInt(view);
+ if (holder.isTmpDetached()) {
+ removeDetachedView(view, false);
+ }
+ if (holder.isScrap()) {
+ holder.unScrap();
+ } else if (holder.wasReturnedFromScrap()) {
+ holder.clearReturnedFromScrapFlag();
+ }
+ recycleViewHolderInternal(holder);
+ }
+
+ /**
+ * Internally, use this method instead of {@link #recycleView(android.view.View)} to
+ * catch potential bugs.
+ * @param view
+ */
+ void recycleViewInternal(View view) {
+ recycleViewHolderInternal(getChildViewHolderInt(view));
+ }
+
+ void recycleAndClearCachedViews() {
+ final int count = mCachedViews.size();
+ for (int i = count - 1; i >= 0; i--) {
+ recycleCachedViewAt(i);
+ }
+ mCachedViews.clear();
+ if (ALLOW_THREAD_GAP_WORK) {
+ mPrefetchRegistry.clearPrefetchPositions();
+ }
+ }
+
+ /**
+ * Recycles a cached view and removes the view from the list. Views are added to cache
+ * if and only if they are recyclable, so this method does not check it again.
+ * <p>
+ * A small exception to this rule is when the view does not have an animator reference
+ * but transient state is true (due to animations created outside ItemAnimator). In that
+ * case, adapter may choose to recycle it. From RecyclerView's perspective, the view is
+ * still recyclable since Adapter wants to do so.
+ *
+ * @param cachedViewIndex The index of the view in cached views list
+ */
+ void recycleCachedViewAt(int cachedViewIndex) {
+ if (DEBUG) {
+ Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
+ }
+ ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
+ if (DEBUG) {
+ Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
+ }
+ addViewHolderToRecycledViewPool(viewHolder, true);
+ mCachedViews.remove(cachedViewIndex);
+ }
+
+ /**
+ * internal implementation checks if view is scrapped or attached and throws an exception
+ * if so.
+ * Public version un-scraps before calling recycle.
+ */
+ void recycleViewHolderInternal(ViewHolder holder) {
+ if (holder.isScrap() || holder.itemView.getParent() != null) {
+ throw new IllegalArgumentException(
+ "Scrapped or attached views may not be recycled. isScrap:"
+ + holder.isScrap() + " isAttached:"
+ + (holder.itemView.getParent() != null));
+ }
+
+ if (holder.isTmpDetached()) {
+ throw new IllegalArgumentException("Tmp detached view should be removed "
+ + "from RecyclerView before it can be recycled: " + holder);
+ }
+
+ if (holder.shouldIgnore()) {
+ throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"
+ + " should first call stopIgnoringView(view) before calling recycle.");
+ }
+ //noinspection unchecked
+ final boolean transientStatePreventsRecycling = holder
+ .doesTransientStatePreventRecycling();
+ final boolean forceRecycle = mAdapter != null
+ && transientStatePreventsRecycling
+ && mAdapter.onFailedToRecycleView(holder);
+ boolean cached = false;
+ boolean recycled = false;
+ if (DEBUG && mCachedViews.contains(holder)) {
+ throw new IllegalArgumentException("cached view received recycle internal? "
+ + holder);
+ }
+ if (forceRecycle || holder.isRecyclable()) {
+ if (mViewCacheMax > 0
+ && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
+ | ViewHolder.FLAG_REMOVED
+ | ViewHolder.FLAG_UPDATE
+ | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
+ // Retire oldest cached view
+ int cachedViewSize = mCachedViews.size();
+ if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
+ recycleCachedViewAt(0);
+ cachedViewSize--;
+ }
+
+ int targetCacheIndex = cachedViewSize;
+ if (ALLOW_THREAD_GAP_WORK
+ && cachedViewSize > 0
+ && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
+ // when adding the view, skip past most recently prefetched views
+ int cacheIndex = cachedViewSize - 1;
+ while (cacheIndex >= 0) {
+ int cachedPos = mCachedViews.get(cacheIndex).mPosition;
+ if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
+ break;
+ }
+ cacheIndex--;
+ }
+ targetCacheIndex = cacheIndex + 1;
+ }
+ mCachedViews.add(targetCacheIndex, holder);
+ cached = true;
+ }
+ if (!cached) {
+ addViewHolderToRecycledViewPool(holder, true);
+ recycled = true;
+ }
+ } else {
+ // NOTE: A view can fail to be recycled when it is scrolled off while an animation
+ // runs. In this case, the item is eventually recycled by
+ // ItemAnimatorRestoreListener#onAnimationFinished.
+
+ // TODO: consider cancelling an animation when an item is removed scrollBy,
+ // to return it to the pool faster
+ if (DEBUG) {
+ Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
+ + "re-visit here. We are still removing it from animation lists");
+ }
+ }
+ // even if the holder is not removed, we still call this method so that it is removed
+ // from view holder lists.
+ mViewInfoStore.removeViewHolder(holder);
+ if (!cached && !recycled && transientStatePreventsRecycling) {
+ holder.mOwnerRecyclerView = null;
+ }
+ }
+
+ /**
+ * Prepares the ViewHolder to be removed/recycled, and inserts it into the RecycledViewPool.
+ *
+ * Pass false to dispatchRecycled for views that have not been bound.
+ *
+ * @param holder Holder to be added to the pool.
+ * @param dispatchRecycled True to dispatch View recycled callbacks.
+ */
+ void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
+ clearNestedRecyclerViewIfNotNested(holder);
+ holder.itemView.setAccessibilityDelegate(null);
+ if (dispatchRecycled) {
+ dispatchViewRecycled(holder);
+ }
+ holder.mOwnerRecyclerView = null;
+ getRecycledViewPool().putRecycledView(holder);
+ }
+
+ /**
+ * Used as a fast path for unscrapping and recycling a view during a bulk operation.
+ * The caller must call {@link #clearScrap()} when it's done to update the recycler's
+ * internal bookkeeping.
+ */
+ void quickRecycleScrapView(View view) {
+ final ViewHolder holder = getChildViewHolderInt(view);
+ holder.mScrapContainer = null;
+ holder.mInChangeScrap = false;
+ holder.clearReturnedFromScrapFlag();
+ recycleViewHolderInternal(holder);
+ }
+
+ /**
+ * Mark an attached view as scrap.
+ *
+ * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible
+ * for rebinding and reuse. Requests for a view for a given position may return a
+ * reused or rebound scrap view instance.</p>
+ *
+ * @param view View to scrap
+ */
+ void scrapView(View view) {
+ final ViewHolder holder = getChildViewHolderInt(view);
+ if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
+ || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
+ if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
+ throw new IllegalArgumentException("Called scrap view with an invalid view."
+ + " Invalid views cannot be reused from scrap, they should rebound from"
+ + " recycler pool.");
+ }
+ holder.setScrapContainer(this, false);
+ mAttachedScrap.add(holder);
+ } else {
+ if (mChangedScrap == null) {
+ mChangedScrap = new ArrayList<ViewHolder>();
+ }
+ holder.setScrapContainer(this, true);
+ mChangedScrap.add(holder);
+ }
+ }
+
+ /**
+ * Remove a previously scrapped view from the pool of eligible scrap.
+ *
+ * <p>This view will no longer be eligible for reuse until re-scrapped or
+ * until it is explicitly removed and recycled.</p>
+ */
+ void unscrapView(ViewHolder holder) {
+ if (holder.mInChangeScrap) {
+ mChangedScrap.remove(holder);
+ } else {
+ mAttachedScrap.remove(holder);
+ }
+ holder.mScrapContainer = null;
+ holder.mInChangeScrap = false;
+ holder.clearReturnedFromScrapFlag();
+ }
+
+ int getScrapCount() {
+ return mAttachedScrap.size();
+ }
+
+ View getScrapViewAt(int index) {
+ return mAttachedScrap.get(index).itemView;
+ }
+
+ void clearScrap() {
+ mAttachedScrap.clear();
+ if (mChangedScrap != null) {
+ mChangedScrap.clear();
+ }
+ }
+
+ ViewHolder getChangedScrapViewForPosition(int position) {
+ // If pre-layout, check the changed scrap for an exact match.
+ final int changedScrapSize;
+ if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
+ return null;
+ }
+ // find by position
+ for (int i = 0; i < changedScrapSize; i++) {
+ final ViewHolder holder = mChangedScrap.get(i);
+ if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
+ holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
+ return holder;
+ }
+ }
+ // find by id
+ if (mAdapter.hasStableIds()) {
+ final int offsetPosition = mAdapterHelper.findPositionOffset(position);
+ if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
+ final long id = mAdapter.getItemId(offsetPosition);
+ for (int i = 0; i < changedScrapSize; i++) {
+ final ViewHolder holder = mChangedScrap.get(i);
+ if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
+ holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
+ return holder;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a view for the position either from attach scrap, hidden children, or cache.
+ *
+ * @param position Item position
+ * @param dryRun Does a dry run, finds the ViewHolder but does not remove
+ * @return a ViewHolder that can be re-used for this position.
+ */
+ ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
+ final int scrapCount = mAttachedScrap.size();
+
+ // Try first for an exact, non-invalid match from scrap.
+ for (int i = 0; i < scrapCount; i++) {
+ final ViewHolder holder = mAttachedScrap.get(i);
+ if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
+ && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
+ holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
+ return holder;
+ }
+ }
+
+ if (!dryRun) {
+ View view = mChildHelper.findHiddenNonRemovedView(position);
+ if (view != null) {
+ // This View is good to be used. We just need to unhide, detach and move to the
+ // scrap list.
+ final ViewHolder vh = getChildViewHolderInt(view);
+ mChildHelper.unhide(view);
+ int layoutIndex = mChildHelper.indexOfChild(view);
+ if (layoutIndex == RecyclerView.NO_POSITION) {
+ throw new IllegalStateException("layout index should not be -1 after "
+ + "unhiding a view:" + vh);
+ }
+ mChildHelper.detachViewFromParent(layoutIndex);
+ scrapView(view);
+ vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
+ | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
+ return vh;
+ }
+ }
+
+ // Search in our first-level recycled view cache.
+ final int cacheSize = mCachedViews.size();
+ for (int i = 0; i < cacheSize; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ // invalid view holders may be in cache if adapter has stable ids as they can be
+ // retrieved via getScrapOrCachedViewForId
+ if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
+ if (!dryRun) {
+ mCachedViews.remove(i);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
+ + ") found match in cache: " + holder);
+ }
+ return holder;
+ }
+ }
+ return null;
+ }
+
+ ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
+ // Look in our attached views first
+ final int count = mAttachedScrap.size();
+ for (int i = count - 1; i >= 0; i--) {
+ final ViewHolder holder = mAttachedScrap.get(i);
+ if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
+ if (type == holder.getItemViewType()) {
+ holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
+ if (holder.isRemoved()) {
+ // this might be valid in two cases:
+ // > item is removed but we are in pre-layout pass
+ // >> do nothing. return as is. make sure we don't rebind
+ // > item is removed then added to another position and we are in
+ // post layout.
+ // >> remove removed and invalid flags, add update flag to rebind
+ // because item was invisible to us and we don't know what happened in
+ // between.
+ if (!mState.isPreLayout()) {
+ holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
+ | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
+ }
+ }
+ return holder;
+ } else if (!dryRun) {
+ // if we are running animations, it is actually better to keep it in scrap
+ // but this would force layout manager to lay it out which would be bad.
+ // Recycle this scrap. Type mismatch.
+ mAttachedScrap.remove(i);
+ removeDetachedView(holder.itemView, false);
+ quickRecycleScrapView(holder.itemView);
+ }
+ }
+ }
+
+ // Search the first-level cache
+ final int cacheSize = mCachedViews.size();
+ for (int i = cacheSize - 1; i >= 0; i--) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder.getItemId() == id) {
+ if (type == holder.getItemViewType()) {
+ if (!dryRun) {
+ mCachedViews.remove(i);
+ }
+ return holder;
+ } else if (!dryRun) {
+ recycleCachedViewAt(i);
+ return null;
+ }
+ }
+ }
+ return null;
+ }
+
+ void dispatchViewRecycled(ViewHolder holder) {
+ if (mRecyclerListener != null) {
+ mRecyclerListener.onViewRecycled(holder);
+ }
+ if (mAdapter != null) {
+ mAdapter.onViewRecycled(holder);
+ }
+ if (mState != null) {
+ mViewInfoStore.removeViewHolder(holder);
+ }
+ if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder);
+ }
+
+ void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
+ boolean compatibleWithPrevious) {
+ clear();
+ getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious);
+ }
+
+ void offsetPositionRecordsForMove(int from, int to) {
+ final int start, end, inBetweenOffset;
+ if (from < to) {
+ start = from;
+ end = to;
+ inBetweenOffset = -1;
+ } else {
+ start = to;
+ end = from;
+ inBetweenOffset = 1;
+ }
+ final int cachedCount = mCachedViews.size();
+ for (int i = 0; i < cachedCount; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder == null || holder.mPosition < start || holder.mPosition > end) {
+ continue;
+ }
+ if (holder.mPosition == from) {
+ holder.offsetPosition(to - from, false);
+ } else {
+ holder.offsetPosition(inBetweenOffset, false);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "offsetPositionRecordsForMove cached child " + i + " holder "
+ + holder);
+ }
+ }
+ }
+
+ void offsetPositionRecordsForInsert(int insertedAt, int count) {
+ final int cachedCount = mCachedViews.size();
+ for (int i = 0; i < cachedCount; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder != null && holder.mPosition >= insertedAt) {
+ if (DEBUG) {
+ Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder "
+ + holder + " now at position " + (holder.mPosition + count));
+ }
+ holder.offsetPosition(count, true);
+ }
+ }
+ }
+
+ /**
+ * @param removedFrom Remove start index
+ * @param count Remove count
+ * @param applyToPreLayout If true, changes will affect ViewHolder's pre-layout position, if
+ * false, they'll be applied before the second layout pass
+ */
+ void offsetPositionRecordsForRemove(int removedFrom, int count, boolean applyToPreLayout) {
+ final int removedEnd = removedFrom + count;
+ final int cachedCount = mCachedViews.size();
+ for (int i = cachedCount - 1; i >= 0; i--) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder != null) {
+ if (holder.mPosition >= removedEnd) {
+ if (DEBUG) {
+ Log.d(TAG, "offsetPositionRecordsForRemove cached " + i
+ + " holder " + holder + " now at position "
+ + (holder.mPosition - count));
+ }
+ holder.offsetPosition(-count, applyToPreLayout);
+ } else if (holder.mPosition >= removedFrom) {
+ // Item for this view was removed. Dump it from the cache.
+ holder.addFlags(ViewHolder.FLAG_REMOVED);
+ recycleCachedViewAt(i);
+ }
+ }
+ }
+ }
+
+ void setViewCacheExtension(ViewCacheExtension extension) {
+ mViewCacheExtension = extension;
+ }
+
+ void setRecycledViewPool(RecycledViewPool pool) {
+ if (mRecyclerPool != null) {
+ mRecyclerPool.detach();
+ }
+ mRecyclerPool = pool;
+ if (pool != null) {
+ mRecyclerPool.attach(getAdapter());
+ }
+ }
+
+ RecycledViewPool getRecycledViewPool() {
+ if (mRecyclerPool == null) {
+ mRecyclerPool = new RecycledViewPool();
+ }
+ return mRecyclerPool;
+ }
+
+ void viewRangeUpdate(int positionStart, int itemCount) {
+ final int positionEnd = positionStart + itemCount;
+ final int cachedCount = mCachedViews.size();
+ for (int i = cachedCount - 1; i >= 0; i--) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder == null) {
+ continue;
+ }
+
+ final int pos = holder.getLayoutPosition();
+ if (pos >= positionStart && pos < positionEnd) {
+ holder.addFlags(ViewHolder.FLAG_UPDATE);
+ recycleCachedViewAt(i);
+ // cached views should not be flagged as changed because this will cause them
+ // to animate when they are returned from cache.
+ }
+ }
+ }
+
+ void setAdapterPositionsAsUnknown() {
+ final int cachedCount = mCachedViews.size();
+ for (int i = 0; i < cachedCount; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder != null) {
+ holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
+ }
+ }
+ }
+
+ void markKnownViewsInvalid() {
+ if (mAdapter != null && mAdapter.hasStableIds()) {
+ final int cachedCount = mCachedViews.size();
+ for (int i = 0; i < cachedCount; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ if (holder != null) {
+ holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
+ holder.addChangePayload(null);
+ }
+ }
+ } else {
+ // we cannot re-use cached views in this case. Recycle them all
+ recycleAndClearCachedViews();
+ }
+ }
+
+ void clearOldPositions() {
+ final int cachedCount = mCachedViews.size();
+ for (int i = 0; i < cachedCount; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ holder.clearOldPosition();
+ }
+ final int scrapCount = mAttachedScrap.size();
+ for (int i = 0; i < scrapCount; i++) {
+ mAttachedScrap.get(i).clearOldPosition();
+ }
+ if (mChangedScrap != null) {
+ final int changedScrapCount = mChangedScrap.size();
+ for (int i = 0; i < changedScrapCount; i++) {
+ mChangedScrap.get(i).clearOldPosition();
+ }
+ }
+ }
+
+ void markItemDecorInsetsDirty() {
+ final int cachedCount = mCachedViews.size();
+ for (int i = 0; i < cachedCount; i++) {
+ final ViewHolder holder = mCachedViews.get(i);
+ LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams();
+ if (layoutParams != null) {
+ layoutParams.mInsetsDirty = true;
+ }
+ }
+ }
+ }
+
+ /**
+ * ViewCacheExtension is a helper class to provide an additional layer of view caching that can
+ * be controlled by the developer.
+ * <p>
+ * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and
+ * first level cache to find a matching View. If it cannot find a suitable View, Recycler will
+ * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking
+ * {@link RecycledViewPool}.
+ * <p>
+ * Note that, Recycler never sends Views to this method to be cached. It is developers
+ * responsibility to decide whether they want to keep their Views in this custom cache or let
+ * the default recycling policy handle it.
+ */
+ public abstract static class ViewCacheExtension {
+
+ /**
+ * Returns a View that can be binded to the given Adapter position.
+ * <p>
+ * This method should <b>not</b> create a new View. Instead, it is expected to return
+ * an already created View that can be re-used for the given type and position.
+ * If the View is marked as ignored, it should first call
+ * {@link LayoutManager#stopIgnoringView(View)} before returning the View.
+ * <p>
+ * RecyclerView will re-bind the returned View to the position if necessary.
+ *
+ * @param recycler The Recycler that can be used to bind the View
+ * @param position The adapter position
+ * @param type The type of the View, defined by adapter
+ * @return A View that is bound to the given position or NULL if there is no View to re-use
+ * @see LayoutManager#ignoreView(View)
+ */
+ public abstract View getViewForPositionAndType(Recycler recycler, int position, int type);
+ }
+
+ /**
+ * Base class for an Adapter
+ *
+ * <p>Adapters provide a binding from an app-specific data set to views that are displayed
+ * within a {@link RecyclerView}.</p>
+ *
+ * @param <VH> A class that extends ViewHolder that will be used by the adapter.
+ */
+ public abstract static class Adapter<VH extends ViewHolder> {
+ private final AdapterDataObservable mObservable = new AdapterDataObservable();
+ private boolean mHasStableIds = false;
+
+ /**
+ * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent
+ * an item.
+ * <p>
+ * This new ViewHolder should be constructed with a new View that can represent the items
+ * of the given type. You can either create a new View manually or inflate it from an XML
+ * layout file.
+ * <p>
+ * The new ViewHolder will be used to display items of the adapter using
+ * {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display
+ * different items in the data set, it is a good idea to cache references to sub views of
+ * the View to avoid unnecessary {@link View#findViewById(int)} calls.
+ *
+ * @param parent The ViewGroup into which the new View will be added after it is bound to
+ * an adapter position.
+ * @param viewType The view type of the new View.
+ *
+ * @return A new ViewHolder that holds a View of the given view type.
+ * @see #getItemViewType(int)
+ * @see #onBindViewHolder(ViewHolder, int)
+ */
+ public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
+
+ /**
+ * Called by RecyclerView to display the data at the specified position. This method should
+ * update the contents of the {@link ViewHolder#itemView} to reflect the item at the given
+ * position.
+ * <p>
+ * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
+ * again if the position of the item changes in the data set unless the item itself is
+ * invalidated or the new position cannot be determined. For this reason, you should only
+ * use the <code>position</code> parameter while acquiring the related data item inside
+ * this method and should not keep a copy of it. If you need the position of an item later
+ * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
+ * have the updated adapter position.
+ *
+ * Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can
+ * handle efficient partial bind.
+ *
+ * @param holder The ViewHolder which should be updated to represent the contents of the
+ * item at the given position in the data set.
+ * @param position The position of the item within the adapter's data set.
+ */
+ public abstract void onBindViewHolder(VH holder, int position);
+
+ /**
+ * Called by RecyclerView to display the data at the specified position. This method
+ * should update the contents of the {@link ViewHolder#itemView} to reflect the item at
+ * the given position.
+ * <p>
+ * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
+ * again if the position of the item changes in the data set unless the item itself is
+ * invalidated or the new position cannot be determined. For this reason, you should only
+ * use the <code>position</code> parameter while acquiring the related data item inside
+ * this method and should not keep a copy of it. If you need the position of an item later
+ * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
+ * have the updated adapter position.
+ * <p>
+ * Partial bind vs full bind:
+ * <p>
+ * The payloads parameter is a merge list from {@link #notifyItemChanged(int, Object)} or
+ * {@link #notifyItemRangeChanged(int, int, Object)}. If the payloads list is not empty,
+ * the ViewHolder is currently bound to old data and Adapter may run an efficient partial
+ * update using the payload info. If the payload is empty, Adapter must run a full bind.
+ * Adapter should not assume that the payload passed in notify methods will be received by
+ * onBindViewHolder(). For example when the view is not attached to the screen, the
+ * payload in notifyItemChange() will be simply dropped.
+ *
+ * @param holder The ViewHolder which should be updated to represent the contents of the
+ * item at the given position in the data set.
+ * @param position The position of the item within the adapter's data set.
+ * @param payloads A non-null list of merged payloads. Can be empty list if requires full
+ * update.
+ */
+ public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
+ onBindViewHolder(holder, position);
+ }
+
+ /**
+ * This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new
+ * {@link ViewHolder} and initializes some private fields to be used by RecyclerView.
+ *
+ * @see #onCreateViewHolder(ViewGroup, int)
+ */
+ public final VH createViewHolder(ViewGroup parent, int viewType) {
+ Trace.beginSection(TRACE_CREATE_VIEW_TAG);
+ final VH holder = onCreateViewHolder(parent, viewType);
+ holder.mItemViewType = viewType;
+ Trace.endSection();
+ return holder;
+ }
+
+ /**
+ * This method internally calls {@link #onBindViewHolder(ViewHolder, int)} to update the
+ * {@link ViewHolder} contents with the item at the given position and also sets up some
+ * private fields to be used by RecyclerView.
+ *
+ * @see #onBindViewHolder(ViewHolder, int)
+ */
+ public final void bindViewHolder(VH holder, int position) {
+ holder.mPosition = position;
+ if (hasStableIds()) {
+ holder.mItemId = getItemId(position);
+ }
+ holder.setFlags(ViewHolder.FLAG_BOUND,
+ ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
+ | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
+ Trace.beginSection(TRACE_BIND_VIEW_TAG);
+ onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
+ holder.clearPayload();
+ final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
+ if (layoutParams instanceof RecyclerView.LayoutParams) {
+ ((LayoutParams) layoutParams).mInsetsDirty = true;
+ }
+ Trace.endSection();
+ }
+
+ /**
+ * Return the view type of the item at <code>position</code> for the purposes
+ * of view recycling.
+ *
+ * <p>The default implementation of this method returns 0, making the assumption of
+ * a single view type for the adapter. Unlike ListView adapters, types need not
+ * be contiguous. Consider using id resources to uniquely identify item view types.
+ *
+ * @param position position to query
+ * @return integer value identifying the type of the view needed to represent the item at
+ * <code>position</code>. Type codes need not be contiguous.
+ */
+ public int getItemViewType(int position) {
+ return 0;
+ }
+
+ /**
+ * Indicates whether each item in the data set can be represented with a unique identifier
+ * of type {@link java.lang.Long}.
+ *
+ * @param hasStableIds Whether items in data set have unique identifiers or not.
+ * @see #hasStableIds()
+ * @see #getItemId(int)
+ */
+ public void setHasStableIds(boolean hasStableIds) {
+ if (hasObservers()) {
+ throw new IllegalStateException("Cannot change whether this adapter has "
+ + "stable IDs while the adapter has registered observers.");
+ }
+ mHasStableIds = hasStableIds;
+ }
+
+ /**
+ * Return the stable ID for the item at <code>position</code>. If {@link #hasStableIds()}
+ * would return false this method should return {@link #NO_ID}. The default implementation
+ * of this method returns {@link #NO_ID}.
+ *
+ * @param position Adapter position to query
+ * @return the stable ID of the item at position
+ */
+ public long getItemId(int position) {
+ return NO_ID;
+ }
+
+ /**
+ * Returns the total number of items in the data set held by the adapter.
+ *
+ * @return The total number of items in this adapter.
+ */
+ public abstract int getItemCount();
+
+ /**
+ * Returns true if this adapter publishes a unique <code>long</code> value that can
+ * act as a key for the item at a given position in the data set. If that item is relocated
+ * in the data set, the ID returned for that item should be the same.
+ *
+ * @return true if this adapter's items have stable IDs
+ */
+ public final boolean hasStableIds() {
+ return mHasStableIds;
+ }
+
+ /**
+ * Called when a view created by this adapter has been recycled.
+ *
+ * <p>A view is recycled when a {@link LayoutManager} decides that it no longer
+ * needs to be attached to its parent {@link RecyclerView}. This can be because it has
+ * fallen out of visibility or a set of cached views represented by views still
+ * attached to the parent RecyclerView. If an item view has large or expensive data
+ * bound to it such as large bitmaps, this may be a good place to release those
+ * resources.</p>
+ * <p>
+ * RecyclerView calls this method right before clearing ViewHolder's internal data and
+ * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information
+ * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get
+ * its adapter position.
+ *
+ * @param holder The ViewHolder for the view being recycled
+ */
+ public void onViewRecycled(VH holder) {
+ }
+
+ /**
+ * Called by the RecyclerView if a ViewHolder created by this Adapter cannot be recycled
+ * due to its transient state. Upon receiving this callback, Adapter can clear the
+ * animation(s) that effect the View's transient state and return <code>true</code> so that
+ * the View can be recycled. Keep in mind that the View in question is already removed from
+ * the RecyclerView.
+ * <p>
+ * In some cases, it is acceptable to recycle a View although it has transient state. Most
+ * of the time, this is a case where the transient state will be cleared in
+ * {@link #onBindViewHolder(ViewHolder, int)} call when View is rebound to a new position.
+ * For this reason, RecyclerView leaves the decision to the Adapter and uses the return
+ * value of this method to decide whether the View should be recycled or not.
+ * <p>
+ * Note that when all animations are created by {@link RecyclerView.ItemAnimator}, you
+ * should never receive this callback because RecyclerView keeps those Views as children
+ * until their animations are complete. This callback is useful when children of the item
+ * views create animations which may not be easy to implement using an {@link ItemAnimator}.
+ * <p>
+ * You should <em>never</em> fix this issue by calling
+ * <code>holder.itemView.setHasTransientState(false);</code> unless you've previously called
+ * <code>holder.itemView.setHasTransientState(true);</code>. Each
+ * <code>View.setHasTransientState(true)</code> call must be matched by a
+ * <code>View.setHasTransientState(false)</code> call, otherwise, the state of the View
+ * may become inconsistent. You should always prefer to end or cancel animations that are
+ * triggering the transient state instead of handling it manually.
+ *
+ * @param holder The ViewHolder containing the View that could not be recycled due to its
+ * transient state.
+ * @return True if the View should be recycled, false otherwise. Note that if this method
+ * returns <code>true</code>, RecyclerView <em>will ignore</em> the transient state of
+ * the View and recycle it regardless. If this method returns <code>false</code>,
+ * RecyclerView will check the View's transient state again before giving a final decision.
+ * Default implementation returns false.
+ */
+ public boolean onFailedToRecycleView(VH holder) {
+ return false;
+ }
+
+ /**
+ * Called when a view created by this adapter has been attached to a window.
+ *
+ * <p>This can be used as a reasonable signal that the view is about to be seen
+ * by the user. If the adapter previously freed any resources in
+ * {@link #onViewDetachedFromWindow(RecyclerView.ViewHolder) onViewDetachedFromWindow}
+ * those resources should be restored here.</p>
+ *
+ * @param holder Holder of the view being attached
+ */
+ public void onViewAttachedToWindow(VH holder) {
+ }
+
+ /**
+ * Called when a view created by this adapter has been detached from its window.
+ *
+ * <p>Becoming detached from the window is not necessarily a permanent condition;
+ * the consumer of an Adapter's views may choose to cache views offscreen while they
+ * are not visible, attaching and detaching them as appropriate.</p>
+ *
+ * @param holder Holder of the view being detached
+ */
+ public void onViewDetachedFromWindow(VH holder) {
+ }
+
+ /**
+ * Returns true if one or more observers are attached to this adapter.
+ *
+ * @return true if this adapter has observers
+ */
+ public final boolean hasObservers() {
+ return mObservable.hasObservers();
+ }
+
+ /**
+ * Register a new observer to listen for data changes.
+ *
+ * <p>The adapter may publish a variety of events describing specific changes.
+ * Not all adapters may support all change types and some may fall back to a generic
+ * {@link com.android.internal.widget.RecyclerView.AdapterDataObserver#onChanged()
+ * "something changed"} event if more specific data is not available.</p>
+ *
+ * <p>Components registering observers with an adapter are responsible for
+ * {@link #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver)
+ * unregistering} those observers when finished.</p>
+ *
+ * @param observer Observer to register
+ *
+ * @see #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver)
+ */
+ public void registerAdapterDataObserver(AdapterDataObserver observer) {
+ mObservable.registerObserver(observer);
+ }
+
+ /**
+ * Unregister an observer currently listening for data changes.
+ *
+ * <p>The unregistered observer will no longer receive events about changes
+ * to the adapter.</p>
+ *
+ * @param observer Observer to unregister
+ *
+ * @see #registerAdapterDataObserver(RecyclerView.AdapterDataObserver)
+ */
+ public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
+ mObservable.unregisterObserver(observer);
+ }
+
+ /**
+ * Called by RecyclerView when it starts observing this Adapter.
+ * <p>
+ * Keep in mind that same adapter may be observed by multiple RecyclerViews.
+ *
+ * @param recyclerView The RecyclerView instance which started observing this adapter.
+ * @see #onDetachedFromRecyclerView(RecyclerView)
+ */
+ public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+ }
+
+ /**
+ * Called by RecyclerView when it stops observing this Adapter.
+ *
+ * @param recyclerView The RecyclerView instance which stopped observing this adapter.
+ * @see #onAttachedToRecyclerView(RecyclerView)
+ */
+ public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
+ }
+
+ /**
+ * Notify any registered observers that the data set has changed.
+ *
+ * <p>There are two different classes of data change events, item changes and structural
+ * changes. Item changes are when a single item has its data updated but no positional
+ * changes have occurred. Structural changes are when items are inserted, removed or moved
+ * within the data set.</p>
+ *
+ * <p>This event does not specify what about the data set has changed, forcing
+ * any observers to assume that all existing items and structure may no longer be valid.
+ * LayoutManagers will be forced to fully rebind and relayout all visible views.</p>
+ *
+ * <p><code>RecyclerView</code> will attempt to synthesize visible structural change events
+ * for adapters that report that they have {@link #hasStableIds() stable IDs} when
+ * this method is used. This can help for the purposes of animation and visual
+ * object persistence but individual item views will still need to be rebound
+ * and relaid out.</p>
+ *
+ * <p>If you are writing an adapter it will always be more efficient to use the more
+ * specific change events if you can. Rely on <code>notifyDataSetChanged()</code>
+ * as a last resort.</p>
+ *
+ * @see #notifyItemChanged(int)
+ * @see #notifyItemInserted(int)
+ * @see #notifyItemRemoved(int)
+ * @see #notifyItemRangeChanged(int, int)
+ * @see #notifyItemRangeInserted(int, int)
+ * @see #notifyItemRangeRemoved(int, int)
+ */
+ public final void notifyDataSetChanged() {
+ mObservable.notifyChanged();
+ }
+
+ /**
+ * Notify any registered observers that the item at <code>position</code> has changed.
+ * Equivalent to calling <code>notifyItemChanged(position, null);</code>.
+ *
+ * <p>This is an item change event, not a structural change event. It indicates that any
+ * reflection of the data at <code>position</code> is out of date and should be updated.
+ * The item at <code>position</code> retains the same identity.</p>
+ *
+ * @param position Position of the item that has changed
+ *
+ * @see #notifyItemRangeChanged(int, int)
+ */
+ public final void notifyItemChanged(int position) {
+ mObservable.notifyItemRangeChanged(position, 1);
+ }
+
+ /**
+ * Notify any registered observers that the item at <code>position</code> has changed with
+ * an optional payload object.
+ *
+ * <p>This is an item change event, not a structural change event. It indicates that any
+ * reflection of the data at <code>position</code> is out of date and should be updated.
+ * The item at <code>position</code> retains the same identity.
+ * </p>
+ *
+ * <p>
+ * Client can optionally pass a payload for partial change. These payloads will be merged
+ * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the
+ * item is already represented by a ViewHolder and it will be rebound to the same
+ * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing
+ * payloads on that item and prevent future payload until
+ * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume
+ * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not
+ * attached, the payload will be simply dropped.
+ *
+ * @param position Position of the item that has changed
+ * @param payload Optional parameter, use null to identify a "full" update
+ *
+ * @see #notifyItemRangeChanged(int, int)
+ */
+ public final void notifyItemChanged(int position, Object payload) {
+ mObservable.notifyItemRangeChanged(position, 1, payload);
+ }
+
+ /**
+ * Notify any registered observers that the <code>itemCount</code> items starting at
+ * position <code>positionStart</code> have changed.
+ * Equivalent to calling <code>notifyItemRangeChanged(position, itemCount, null);</code>.
+ *
+ * <p>This is an item change event, not a structural change event. It indicates that
+ * any reflection of the data in the given position range is out of date and should
+ * be updated. The items in the given range retain the same identity.</p>
+ *
+ * @param positionStart Position of the first item that has changed
+ * @param itemCount Number of items that have changed
+ *
+ * @see #notifyItemChanged(int)
+ */
+ public final void notifyItemRangeChanged(int positionStart, int itemCount) {
+ mObservable.notifyItemRangeChanged(positionStart, itemCount);
+ }
+
+ /**
+ * Notify any registered observers that the <code>itemCount</code> items starting at
+ * position <code>positionStart</code> have changed. An optional payload can be
+ * passed to each changed item.
+ *
+ * <p>This is an item change event, not a structural change event. It indicates that any
+ * reflection of the data in the given position range is out of date and should be updated.
+ * The items in the given range retain the same identity.
+ * </p>
+ *
+ * <p>
+ * Client can optionally pass a payload for partial change. These payloads will be merged
+ * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the
+ * item is already represented by a ViewHolder and it will be rebound to the same
+ * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing
+ * payloads on that item and prevent future payload until
+ * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume
+ * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not
+ * attached, the payload will be simply dropped.
+ *
+ * @param positionStart Position of the first item that has changed
+ * @param itemCount Number of items that have changed
+ * @param payload Optional parameter, use null to identify a "full" update
+ *
+ * @see #notifyItemChanged(int)
+ */
+ public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ mObservable.notifyItemRangeChanged(positionStart, itemCount, payload);
+ }
+
+ /**
+ * Notify any registered observers that the item reflected at <code>position</code>
+ * has been newly inserted. The item previously at <code>position</code> is now at
+ * position <code>position + 1</code>.
+ *
+ * <p>This is a structural change event. Representations of other existing items in the
+ * data set are still considered up to date and will not be rebound, though their
+ * positions may be altered.</p>
+ *
+ * @param position Position of the newly inserted item in the data set
+ *
+ * @see #notifyItemRangeInserted(int, int)
+ */
+ public final void notifyItemInserted(int position) {
+ mObservable.notifyItemRangeInserted(position, 1);
+ }
+
+ /**
+ * Notify any registered observers that the item reflected at <code>fromPosition</code>
+ * has been moved to <code>toPosition</code>.
+ *
+ * <p>This is a structural change event. Representations of other existing items in the
+ * data set are still considered up to date and will not be rebound, though their
+ * positions may be altered.</p>
+ *
+ * @param fromPosition Previous position of the item.
+ * @param toPosition New position of the item.
+ */
+ public final void notifyItemMoved(int fromPosition, int toPosition) {
+ mObservable.notifyItemMoved(fromPosition, toPosition);
+ }
+
+ /**
+ * Notify any registered observers that the currently reflected <code>itemCount</code>
+ * items starting at <code>positionStart</code> have been newly inserted. The items
+ * previously located at <code>positionStart</code> and beyond can now be found starting
+ * at position <code>positionStart + itemCount</code>.
+ *
+ * <p>This is a structural change event. Representations of other existing items in the
+ * data set are still considered up to date and will not be rebound, though their positions
+ * may be altered.</p>
+ *
+ * @param positionStart Position of the first item that was inserted
+ * @param itemCount Number of items inserted
+ *
+ * @see #notifyItemInserted(int)
+ */
+ public final void notifyItemRangeInserted(int positionStart, int itemCount) {
+ mObservable.notifyItemRangeInserted(positionStart, itemCount);
+ }
+
+ /**
+ * Notify any registered observers that the item previously located at <code>position</code>
+ * has been removed from the data set. The items previously located at and after
+ * <code>position</code> may now be found at <code>oldPosition - 1</code>.
+ *
+ * <p>This is a structural change event. Representations of other existing items in the
+ * data set are still considered up to date and will not be rebound, though their positions
+ * may be altered.</p>
+ *
+ * @param position Position of the item that has now been removed
+ *
+ * @see #notifyItemRangeRemoved(int, int)
+ */
+ public final void notifyItemRemoved(int position) {
+ mObservable.notifyItemRangeRemoved(position, 1);
+ }
+
+ /**
+ * Notify any registered observers that the <code>itemCount</code> items previously
+ * located at <code>positionStart</code> have been removed from the data set. The items
+ * previously located at and after <code>positionStart + itemCount</code> may now be found
+ * at <code>oldPosition - itemCount</code>.
+ *
+ * <p>This is a structural change event. Representations of other existing items in the data
+ * set are still considered up to date and will not be rebound, though their positions
+ * may be altered.</p>
+ *
+ * @param positionStart Previous position of the first item that was removed
+ * @param itemCount Number of items removed from the data set
+ */
+ public final void notifyItemRangeRemoved(int positionStart, int itemCount) {
+ mObservable.notifyItemRangeRemoved(positionStart, itemCount);
+ }
+ }
+
+ void dispatchChildDetached(View child) {
+ final ViewHolder viewHolder = getChildViewHolderInt(child);
+ onChildDetachedFromWindow(child);
+ if (mAdapter != null && viewHolder != null) {
+ mAdapter.onViewDetachedFromWindow(viewHolder);
+ }
+ if (mOnChildAttachStateListeners != null) {
+ final int cnt = mOnChildAttachStateListeners.size();
+ for (int i = cnt - 1; i >= 0; i--) {
+ mOnChildAttachStateListeners.get(i).onChildViewDetachedFromWindow(child);
+ }
+ }
+ }
+
+ void dispatchChildAttached(View child) {
+ final ViewHolder viewHolder = getChildViewHolderInt(child);
+ onChildAttachedToWindow(child);
+ if (mAdapter != null && viewHolder != null) {
+ mAdapter.onViewAttachedToWindow(viewHolder);
+ }
+ if (mOnChildAttachStateListeners != null) {
+ final int cnt = mOnChildAttachStateListeners.size();
+ for (int i = cnt - 1; i >= 0; i--) {
+ mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child);
+ }
+ }
+ }
+
+ /**
+ * A <code>LayoutManager</code> is responsible for measuring and positioning item views
+ * within a <code>RecyclerView</code> as well as determining the policy for when to recycle
+ * item views that are no longer visible to the user. By changing the <code>LayoutManager</code>
+ * a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list,
+ * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock
+ * layout managers are provided for general use.
+ * <p/>
+ * If the LayoutManager specifies a default constructor or one with the signature
+ * ({@link Context}, {@link AttributeSet}, {@code int}, {@code int}), RecyclerView will
+ * instantiate and set the LayoutManager when being inflated. Most used properties can
+ * be then obtained from {@link #getProperties(Context, AttributeSet, int, int)}. In case
+ * a LayoutManager specifies both constructors, the non-default constructor will take
+ * precedence.
+ *
+ */
+ public abstract static class LayoutManager {
+ ChildHelper mChildHelper;
+ RecyclerView mRecyclerView;
+
+ @Nullable
+ SmoothScroller mSmoothScroller;
+
+ boolean mRequestedSimpleAnimations = false;
+
+ boolean mIsAttachedToWindow = false;
+
+ boolean mAutoMeasure = false;
+
+ /**
+ * LayoutManager has its own more strict measurement cache to avoid re-measuring a child
+ * if the space that will be given to it is already larger than what it has measured before.
+ */
+ private boolean mMeasurementCacheEnabled = true;
+
+ private boolean mItemPrefetchEnabled = true;
+
+ /**
+ * Written by {@link GapWorker} when prefetches occur to track largest number of view ever
+ * requested by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)} or
+ * {@link #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)} call.
+ *
+ * If expanded by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)},
+ * will be reset upon layout to prevent initial prefetches (often large, since they're
+ * proportional to expected child count) from expanding cache permanently.
+ */
+ int mPrefetchMaxCountObserved;
+
+ /**
+ * If true, mPrefetchMaxCountObserved is only valid until next layout, and should be reset.
+ */
+ boolean mPrefetchMaxObservedInInitialPrefetch;
+
+ /**
+ * These measure specs might be the measure specs that were passed into RecyclerView's
+ * onMeasure method OR fake measure specs created by the RecyclerView.
+ * For example, when a layout is run, RecyclerView always sets these specs to be
+ * EXACTLY because a LayoutManager cannot resize RecyclerView during a layout pass.
+ * <p>
+ * Also, to be able to use the hint in unspecified measure specs, RecyclerView checks the
+ * API level and sets the size to 0 pre-M to avoid any issue that might be caused by
+ * corrupt values. Older platforms have no responsibility to provide a size if they set
+ * mode to unspecified.
+ */
+ private int mWidthMode, mHeightMode;
+ private int mWidth, mHeight;
+
+
+ /**
+ * Interface for LayoutManagers to request items to be prefetched, based on position, with
+ * specified distance from viewport, which indicates priority.
+ *
+ * @see LayoutManager#collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)
+ * @see LayoutManager#collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)
+ */
+ public interface LayoutPrefetchRegistry {
+ /**
+ * Requests an an item to be prefetched, based on position, with a specified distance,
+ * indicating priority.
+ *
+ * @param layoutPosition Position of the item to prefetch.
+ * @param pixelDistance Distance from the current viewport to the bounds of the item,
+ * must be non-negative.
+ */
+ void addPosition(int layoutPosition, int pixelDistance);
+ }
+
+ void setRecyclerView(RecyclerView recyclerView) {
+ if (recyclerView == null) {
+ mRecyclerView = null;
+ mChildHelper = null;
+ mWidth = 0;
+ mHeight = 0;
+ } else {
+ mRecyclerView = recyclerView;
+ mChildHelper = recyclerView.mChildHelper;
+ mWidth = recyclerView.getWidth();
+ mHeight = recyclerView.getHeight();
+ }
+ mWidthMode = MeasureSpec.EXACTLY;
+ mHeightMode = MeasureSpec.EXACTLY;
+ }
+
+ void setMeasureSpecs(int wSpec, int hSpec) {
+ mWidth = MeasureSpec.getSize(wSpec);
+ mWidthMode = MeasureSpec.getMode(wSpec);
+ if (mWidthMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {
+ mWidth = 0;
+ }
+
+ mHeight = MeasureSpec.getSize(hSpec);
+ mHeightMode = MeasureSpec.getMode(hSpec);
+ if (mHeightMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {
+ mHeight = 0;
+ }
+ }
+
+ /**
+ * Called after a layout is calculated during a measure pass when using auto-measure.
+ * <p>
+ * It simply traverses all children to calculate a bounding box then calls
+ * {@link #setMeasuredDimension(Rect, int, int)}. LayoutManagers can override that method
+ * if they need to handle the bounding box differently.
+ * <p>
+ * For example, GridLayoutManager override that method to ensure that even if a column is
+ * empty, the GridLayoutManager still measures wide enough to include it.
+ *
+ * @param widthSpec The widthSpec that was passing into RecyclerView's onMeasure
+ * @param heightSpec The heightSpec that was passing into RecyclerView's onMeasure
+ */
+ void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) {
+ final int count = getChildCount();
+ if (count == 0) {
+ mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
+ return;
+ }
+ int minX = Integer.MAX_VALUE;
+ int minY = Integer.MAX_VALUE;
+ int maxX = Integer.MIN_VALUE;
+ int maxY = Integer.MIN_VALUE;
+
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ final Rect bounds = mRecyclerView.mTempRect;
+ getDecoratedBoundsWithMargins(child, bounds);
+ if (bounds.left < minX) {
+ minX = bounds.left;
+ }
+ if (bounds.right > maxX) {
+ maxX = bounds.right;
+ }
+ if (bounds.top < minY) {
+ minY = bounds.top;
+ }
+ if (bounds.bottom > maxY) {
+ maxY = bounds.bottom;
+ }
+ }
+ mRecyclerView.mTempRect.set(minX, minY, maxX, maxY);
+ setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec);
+ }
+
+ /**
+ * Sets the measured dimensions from the given bounding box of the children and the
+ * measurement specs that were passed into {@link RecyclerView#onMeasure(int, int)}. It is
+ * called after the RecyclerView calls
+ * {@link LayoutManager#onLayoutChildren(Recycler, State)} during a measurement pass.
+ * <p>
+ * This method should call {@link #setMeasuredDimension(int, int)}.
+ * <p>
+ * The default implementation adds the RecyclerView's padding to the given bounding box
+ * then caps the value to be within the given measurement specs.
+ * <p>
+ * This method is only called if the LayoutManager opted into the auto measurement API.
+ *
+ * @param childrenBounds The bounding box of all children
+ * @param wSpec The widthMeasureSpec that was passed into the RecyclerView.
+ * @param hSpec The heightMeasureSpec that was passed into the RecyclerView.
+ *
+ * @see #setAutoMeasureEnabled(boolean)
+ */
+ public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
+ int usedWidth = childrenBounds.width() + getPaddingLeft() + getPaddingRight();
+ int usedHeight = childrenBounds.height() + getPaddingTop() + getPaddingBottom();
+ int width = chooseSize(wSpec, usedWidth, getMinimumWidth());
+ int height = chooseSize(hSpec, usedHeight, getMinimumHeight());
+ setMeasuredDimension(width, height);
+ }
+
+ /**
+ * Calls {@code RecyclerView#requestLayout} on the underlying RecyclerView
+ */
+ public void requestLayout() {
+ if (mRecyclerView != null) {
+ mRecyclerView.requestLayout();
+ }
+ }
+
+ /**
+ * Checks if RecyclerView is in the middle of a layout or scroll and throws an
+ * {@link IllegalStateException} if it <b>is not</b>.
+ *
+ * @param message The message for the exception. Can be null.
+ * @see #assertNotInLayoutOrScroll(String)
+ */
+ public void assertInLayoutOrScroll(String message) {
+ if (mRecyclerView != null) {
+ mRecyclerView.assertInLayoutOrScroll(message);
+ }
+ }
+
+ /**
+ * Chooses a size from the given specs and parameters that is closest to the desired size
+ * and also complies with the spec.
+ *
+ * @param spec The measureSpec
+ * @param desired The preferred measurement
+ * @param min The minimum value
+ *
+ * @return A size that fits to the given specs
+ */
+ public static int chooseSize(int spec, int desired, int min) {
+ final int mode = View.MeasureSpec.getMode(spec);
+ final int size = View.MeasureSpec.getSize(spec);
+ switch (mode) {
+ case View.MeasureSpec.EXACTLY:
+ return size;
+ case View.MeasureSpec.AT_MOST:
+ return Math.min(size, Math.max(desired, min));
+ case View.MeasureSpec.UNSPECIFIED:
+ default:
+ return Math.max(desired, min);
+ }
+ }
+
+ /**
+ * Checks if RecyclerView is in the middle of a layout or scroll and throws an
+ * {@link IllegalStateException} if it <b>is</b>.
+ *
+ * @param message The message for the exception. Can be null.
+ * @see #assertInLayoutOrScroll(String)
+ */
+ public void assertNotInLayoutOrScroll(String message) {
+ if (mRecyclerView != null) {
+ mRecyclerView.assertNotInLayoutOrScroll(message);
+ }
+ }
+
+ /**
+ * Defines whether the layout should be measured by the RecyclerView or the LayoutManager
+ * wants to handle the layout measurements itself.
+ * <p>
+ * This method is usually called by the LayoutManager with value {@code true} if it wants
+ * to support WRAP_CONTENT. If you are using a public LayoutManager but want to customize
+ * the measurement logic, you can call this method with {@code false} and override
+ * {@link LayoutManager#onMeasure(int, int)} to implement your custom measurement logic.
+ * <p>
+ * AutoMeasure is a convenience mechanism for LayoutManagers to easily wrap their content or
+ * handle various specs provided by the RecyclerView's parent.
+ * It works by calling {@link LayoutManager#onLayoutChildren(Recycler, State)} during an
+ * {@link RecyclerView#onMeasure(int, int)} call, then calculating desired dimensions based
+ * on children's positions. It does this while supporting all existing animation
+ * capabilities of the RecyclerView.
+ * <p>
+ * AutoMeasure works as follows:
+ * <ol>
+ * <li>LayoutManager should call {@code setAutoMeasureEnabled(true)} to enable it. All of
+ * the framework LayoutManagers use {@code auto-measure}.</li>
+ * <li>When {@link RecyclerView#onMeasure(int, int)} is called, if the provided specs are
+ * exact, RecyclerView will only call LayoutManager's {@code onMeasure} and return without
+ * doing any layout calculation.</li>
+ * <li>If one of the layout specs is not {@code EXACT}, the RecyclerView will start the
+ * layout process in {@code onMeasure} call. It will process all pending Adapter updates and
+ * decide whether to run a predictive layout or not. If it decides to do so, it will first
+ * call {@link #onLayoutChildren(Recycler, State)} with {@link State#isPreLayout()} set to
+ * {@code true}. At this stage, {@link #getWidth()} and {@link #getHeight()} will still
+ * return the width and height of the RecyclerView as of the last layout calculation.
+ * <p>
+ * After handling the predictive case, RecyclerView will call
+ * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to
+ * {@code true} and {@link State#isPreLayout()} set to {@code false}. The LayoutManager can
+ * access the measurement specs via {@link #getHeight()}, {@link #getHeightMode()},
+ * {@link #getWidth()} and {@link #getWidthMode()}.</li>
+ * <li>After the layout calculation, RecyclerView sets the measured width & height by
+ * calculating the bounding box for the children (+ RecyclerView's padding). The
+ * LayoutManagers can override {@link #setMeasuredDimension(Rect, int, int)} to choose
+ * different values. For instance, GridLayoutManager overrides this value to handle the case
+ * where if it is vertical and has 3 columns but only 2 items, it should still measure its
+ * width to fit 3 items, not 2.</li>
+ * <li>Any following on measure call to the RecyclerView will run
+ * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to
+ * {@code true} and {@link State#isPreLayout()} set to {@code false}. RecyclerView will
+ * take care of which views are actually added / removed / moved / changed for animations so
+ * that the LayoutManager should not worry about them and handle each
+ * {@link #onLayoutChildren(Recycler, State)} call as if it is the last one.
+ * </li>
+ * <li>When measure is complete and RecyclerView's
+ * {@link #onLayout(boolean, int, int, int, int)} method is called, RecyclerView checks
+ * whether it already did layout calculations during the measure pass and if so, it re-uses
+ * that information. It may still decide to call {@link #onLayoutChildren(Recycler, State)}
+ * if the last measure spec was different from the final dimensions or adapter contents
+ * have changed between the measure call and the layout call.</li>
+ * <li>Finally, animations are calculated and run as usual.</li>
+ * </ol>
+ *
+ * @param enabled <code>True</code> if the Layout should be measured by the
+ * RecyclerView, <code>false</code> if the LayoutManager wants
+ * to measure itself.
+ *
+ * @see #setMeasuredDimension(Rect, int, int)
+ * @see #isAutoMeasureEnabled()
+ */
+ public void setAutoMeasureEnabled(boolean enabled) {
+ mAutoMeasure = enabled;
+ }
+
+ /**
+ * Returns whether the LayoutManager uses the automatic measurement API or not.
+ *
+ * @return <code>True</code> if the LayoutManager is measured by the RecyclerView or
+ * <code>false</code> if it measures itself.
+ *
+ * @see #setAutoMeasureEnabled(boolean)
+ */
+ public boolean isAutoMeasureEnabled() {
+ return mAutoMeasure;
+ }
+
+ /**
+ * Returns whether this LayoutManager supports automatic item animations.
+ * A LayoutManager wishing to support item animations should obey certain
+ * rules as outlined in {@link #onLayoutChildren(Recycler, State)}.
+ * The default return value is <code>false</code>, so subclasses of LayoutManager
+ * will not get predictive item animations by default.
+ *
+ * <p>Whether item animations are enabled in a RecyclerView is determined both
+ * by the return value from this method and the
+ * {@link RecyclerView#setItemAnimator(ItemAnimator) ItemAnimator} set on the
+ * RecyclerView itself. If the RecyclerView has a non-null ItemAnimator but this
+ * method returns false, then simple item animations will be enabled, in which
+ * views that are moving onto or off of the screen are simply faded in/out. If
+ * the RecyclerView has a non-null ItemAnimator and this method returns true,
+ * then there will be two calls to {@link #onLayoutChildren(Recycler, State)} to
+ * setup up the information needed to more intelligently predict where appearing
+ * and disappearing views should be animated from/to.</p>
+ *
+ * @return true if predictive item animations should be enabled, false otherwise
+ */
+ public boolean supportsPredictiveItemAnimations() {
+ return false;
+ }
+
+ /**
+ * Sets whether the LayoutManager should be queried for views outside of
+ * its viewport while the UI thread is idle between frames.
+ *
+ * <p>If enabled, the LayoutManager will be queried for items to inflate/bind in between
+ * view system traversals on devices running API 21 or greater. Default value is true.</p>
+ *
+ * <p>On platforms API level 21 and higher, the UI thread is idle between passing a frame
+ * to RenderThread and the starting up its next frame at the next VSync pulse. By
+ * prefetching out of window views in this time period, delays from inflation and view
+ * binding are much less likely to cause jank and stuttering during scrolls and flings.</p>
+ *
+ * <p>While prefetch is enabled, it will have the side effect of expanding the effective
+ * size of the View cache to hold prefetched views.</p>
+ *
+ * @param enabled <code>True</code> if items should be prefetched in between traversals.
+ *
+ * @see #isItemPrefetchEnabled()
+ */
+ public final void setItemPrefetchEnabled(boolean enabled) {
+ if (enabled != mItemPrefetchEnabled) {
+ mItemPrefetchEnabled = enabled;
+ mPrefetchMaxCountObserved = 0;
+ if (mRecyclerView != null) {
+ mRecyclerView.mRecycler.updateViewCacheSize();
+ }
+ }
+ }
+
+ /**
+ * Sets whether the LayoutManager should be queried for views outside of
+ * its viewport while the UI thread is idle between frames.
+ *
+ * @see #setItemPrefetchEnabled(boolean)
+ *
+ * @return true if item prefetch is enabled, false otherwise
+ */
+ public final boolean isItemPrefetchEnabled() {
+ return mItemPrefetchEnabled;
+ }
+
+ /**
+ * Gather all positions from the LayoutManager to be prefetched, given specified momentum.
+ *
+ * <p>If item prefetch is enabled, this method is called in between traversals to gather
+ * which positions the LayoutManager will soon need, given upcoming movement in subsequent
+ * traversals.</p>
+ *
+ * <p>The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for
+ * each item to be prepared, and these positions will have their ViewHolders created and
+ * bound, if there is sufficient time available, in advance of being needed by a
+ * scroll or layout.</p>
+ *
+ * @param dx X movement component.
+ * @param dy Y movement component.
+ * @param state State of RecyclerView
+ * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into.
+ *
+ * @see #isItemPrefetchEnabled()
+ * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)
+ */
+ public void collectAdjacentPrefetchPositions(int dx, int dy, State state,
+ LayoutPrefetchRegistry layoutPrefetchRegistry) {}
+
+ /**
+ * Gather all positions from the LayoutManager to be prefetched in preperation for its
+ * RecyclerView to come on screen, due to the movement of another, containing RecyclerView.
+ *
+ * <p>This method is only called when a RecyclerView is nested in another RecyclerView.</p>
+ *
+ * <p>If item prefetch is enabled for this LayoutManager, as well in another containing
+ * LayoutManager, this method is called in between draw traversals to gather
+ * which positions this LayoutManager will first need, once it appears on the screen.</p>
+ *
+ * <p>For example, if this LayoutManager represents a horizontally scrolling list within a
+ * vertically scrolling LayoutManager, this method would be called when the horizontal list
+ * is about to come onscreen.</p>
+ *
+ * <p>The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for
+ * each item to be prepared, and these positions will have their ViewHolders created and
+ * bound, if there is sufficient time available, in advance of being needed by a
+ * scroll or layout.</p>
+ *
+ * @param adapterItemCount number of items in the associated adapter.
+ * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into.
+ *
+ * @see #isItemPrefetchEnabled()
+ * @see #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)
+ */
+ public void collectInitialPrefetchPositions(int adapterItemCount,
+ LayoutPrefetchRegistry layoutPrefetchRegistry) {}
+
+ void dispatchAttachedToWindow(RecyclerView view) {
+ mIsAttachedToWindow = true;
+ onAttachedToWindow(view);
+ }
+
+ void dispatchDetachedFromWindow(RecyclerView view, Recycler recycler) {
+ mIsAttachedToWindow = false;
+ onDetachedFromWindow(view, recycler);
+ }
+
+ /**
+ * Returns whether LayoutManager is currently attached to a RecyclerView which is attached
+ * to a window.
+ *
+ * @return True if this LayoutManager is controlling a RecyclerView and the RecyclerView
+ * is attached to window.
+ */
+ public boolean isAttachedToWindow() {
+ return mIsAttachedToWindow;
+ }
+
+ /**
+ * Causes the Runnable to execute on the next animation time step.
+ * The runnable will be run on the user interface thread.
+ * <p>
+ * Calling this method when LayoutManager is not attached to a RecyclerView has no effect.
+ *
+ * @param action The Runnable that will be executed.
+ *
+ * @see #removeCallbacks
+ */
+ public void postOnAnimation(Runnable action) {
+ if (mRecyclerView != null) {
+ mRecyclerView.postOnAnimation(action);
+ }
+ }
+
+ /**
+ * Removes the specified Runnable from the message queue.
+ * <p>
+ * Calling this method when LayoutManager is not attached to a RecyclerView has no effect.
+ *
+ * @param action The Runnable to remove from the message handling queue
+ *
+ * @return true if RecyclerView could ask the Handler to remove the Runnable,
+ * false otherwise. When the returned value is true, the Runnable
+ * may or may not have been actually removed from the message queue
+ * (for instance, if the Runnable was not in the queue already.)
+ *
+ * @see #postOnAnimation
+ */
+ public boolean removeCallbacks(Runnable action) {
+ if (mRecyclerView != null) {
+ return mRecyclerView.removeCallbacks(action);
+ }
+ return false;
+ }
+ /**
+ * Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView
+ * is attached to a window.
+ * <p>
+ * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not
+ * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was
+ * not requested on the RecyclerView while it was detached.
+ * <p>
+ * Subclass implementations should always call through to the superclass implementation.
+ *
+ * @param view The RecyclerView this LayoutManager is bound to
+ *
+ * @see #onDetachedFromWindow(RecyclerView, Recycler)
+ */
+ @CallSuper
+ public void onAttachedToWindow(RecyclerView view) {
+ }
+
+ /**
+ * @deprecated
+ * override {@link #onDetachedFromWindow(RecyclerView, Recycler)}
+ */
+ @Deprecated
+ public void onDetachedFromWindow(RecyclerView view) {
+
+ }
+
+ /**
+ * Called when this LayoutManager is detached from its parent RecyclerView or when
+ * its parent RecyclerView is detached from its window.
+ * <p>
+ * LayoutManager should clear all of its View references as another LayoutManager might be
+ * assigned to the RecyclerView.
+ * <p>
+ * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not
+ * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was
+ * not requested on the RecyclerView while it was detached.
+ * <p>
+ * If your LayoutManager has View references that it cleans in on-detach, it should also
+ * call {@link RecyclerView#requestLayout()} to ensure that it is re-laid out when
+ * RecyclerView is re-attached.
+ * <p>
+ * Subclass implementations should always call through to the superclass implementation.
+ *
+ * @param view The RecyclerView this LayoutManager is bound to
+ * @param recycler The recycler to use if you prefer to recycle your children instead of
+ * keeping them around.
+ *
+ * @see #onAttachedToWindow(RecyclerView)
+ */
+ @CallSuper
+ public void onDetachedFromWindow(RecyclerView view, Recycler recycler) {
+ onDetachedFromWindow(view);
+ }
+
+ /**
+ * Check if the RecyclerView is configured to clip child views to its padding.
+ *
+ * @return true if this RecyclerView clips children to its padding, false otherwise
+ */
+ public boolean getClipToPadding() {
+ return mRecyclerView != null && mRecyclerView.mClipToPadding;
+ }
+
+ /**
+ * Lay out all relevant child views from the given adapter.
+ *
+ * The LayoutManager is in charge of the behavior of item animations. By default,
+ * RecyclerView has a non-null {@link #getItemAnimator() ItemAnimator}, and simple
+ * item animations are enabled. This means that add/remove operations on the
+ * adapter will result in animations to add new or appearing items, removed or
+ * disappearing items, and moved items. If a LayoutManager returns false from
+ * {@link #supportsPredictiveItemAnimations()}, which is the default, and runs a
+ * normal layout operation during {@link #onLayoutChildren(Recycler, State)}, the
+ * RecyclerView will have enough information to run those animations in a simple
+ * way. For example, the default ItemAnimator, {@link DefaultItemAnimator}, will
+ * simply fade views in and out, whether they are actually added/removed or whether
+ * they are moved on or off the screen due to other add/remove operations.
+ *
+ * <p>A LayoutManager wanting a better item animation experience, where items can be
+ * animated onto and off of the screen according to where the items exist when they
+ * are not on screen, then the LayoutManager should return true from
+ * {@link #supportsPredictiveItemAnimations()} and add additional logic to
+ * {@link #onLayoutChildren(Recycler, State)}. Supporting predictive animations
+ * means that {@link #onLayoutChildren(Recycler, State)} will be called twice;
+ * once as a "pre" layout step to determine where items would have been prior to
+ * a real layout, and again to do the "real" layout. In the pre-layout phase,
+ * items will remember their pre-layout positions to allow them to be laid out
+ * appropriately. Also, {@link LayoutParams#isItemRemoved() removed} items will
+ * be returned from the scrap to help determine correct placement of other items.
+ * These removed items should not be added to the child list, but should be used
+ * to help calculate correct positioning of other views, including views that
+ * were not previously onscreen (referred to as APPEARING views), but whose
+ * pre-layout offscreen position can be determined given the extra
+ * information about the pre-layout removed views.</p>
+ *
+ * <p>The second layout pass is the real layout in which only non-removed views
+ * will be used. The only additional requirement during this pass is, if
+ * {@link #supportsPredictiveItemAnimations()} returns true, to note which
+ * views exist in the child list prior to layout and which are not there after
+ * layout (referred to as DISAPPEARING views), and to position/layout those views
+ * appropriately, without regard to the actual bounds of the RecyclerView. This allows
+ * the animation system to know the location to which to animate these disappearing
+ * views.</p>
+ *
+ * <p>The default LayoutManager implementations for RecyclerView handle all of these
+ * requirements for animations already. Clients of RecyclerView can either use one
+ * of these layout managers directly or look at their implementations of
+ * onLayoutChildren() to see how they account for the APPEARING and
+ * DISAPPEARING views.</p>
+ *
+ * @param recycler Recycler to use for fetching potentially cached views for a
+ * position
+ * @param state Transient state of RecyclerView
+ */
+ public void onLayoutChildren(Recycler recycler, State state) {
+ Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
+ }
+
+ /**
+ * Called after a full layout calculation is finished. The layout calculation may include
+ * multiple {@link #onLayoutChildren(Recycler, State)} calls due to animations or
+ * layout measurement but it will include only one {@link #onLayoutCompleted(State)} call.
+ * This method will be called at the end of {@link View#layout(int, int, int, int)} call.
+ * <p>
+ * This is a good place for the LayoutManager to do some cleanup like pending scroll
+ * position, saved state etc.
+ *
+ * @param state Transient state of RecyclerView
+ */
+ public void onLayoutCompleted(State state) {
+ }
+
+ /**
+ * Create a default <code>LayoutParams</code> object for a child of the RecyclerView.
+ *
+ * <p>LayoutManagers will often want to use a custom <code>LayoutParams</code> type
+ * to store extra information specific to the layout. Client code should subclass
+ * {@link RecyclerView.LayoutParams} for this purpose.</p>
+ *
+ * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
+ * you must also override
+ * {@link #checkLayoutParams(LayoutParams)},
+ * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
+ * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
+ *
+ * @return A new LayoutParams for a child view
+ */
+ public abstract LayoutParams generateDefaultLayoutParams();
+
+ /**
+ * Determines the validity of the supplied LayoutParams object.
+ *
+ * <p>This should check to make sure that the object is of the correct type
+ * and all values are within acceptable ranges. The default implementation
+ * returns <code>true</code> for non-null params.</p>
+ *
+ * @param lp LayoutParams object to check
+ * @return true if this LayoutParams object is valid, false otherwise
+ */
+ public boolean checkLayoutParams(LayoutParams lp) {
+ return lp != null;
+ }
+
+ /**
+ * Create a LayoutParams object suitable for this LayoutManager, copying relevant
+ * values from the supplied LayoutParams object if possible.
+ *
+ * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
+ * you must also override
+ * {@link #checkLayoutParams(LayoutParams)},
+ * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
+ * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
+ *
+ * @param lp Source LayoutParams object to copy values from
+ * @return a new LayoutParams object
+ */
+ public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+ if (lp instanceof LayoutParams) {
+ return new LayoutParams((LayoutParams) lp);
+ } else if (lp instanceof MarginLayoutParams) {
+ return new LayoutParams((MarginLayoutParams) lp);
+ } else {
+ return new LayoutParams(lp);
+ }
+ }
+
+ /**
+ * Create a LayoutParams object suitable for this LayoutManager from
+ * an inflated layout resource.
+ *
+ * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
+ * you must also override
+ * {@link #checkLayoutParams(LayoutParams)},
+ * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
+ * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
+ *
+ * @param c Context for obtaining styled attributes
+ * @param attrs AttributeSet describing the supplied arguments
+ * @return a new LayoutParams object
+ */
+ public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
+ return new LayoutParams(c, attrs);
+ }
+
+ /**
+ * Scroll horizontally by dx pixels in screen coordinates and return the distance traveled.
+ * The default implementation does nothing and returns 0.
+ *
+ * @param dx distance to scroll by in pixels. X increases as scroll position
+ * approaches the right.
+ * @param recycler Recycler to use for fetching potentially cached views for a
+ * position
+ * @param state Transient state of RecyclerView
+ * @return The actual distance scrolled. The return value will be negative if dx was
+ * negative and scrolling proceeeded in that direction.
+ * <code>Math.abs(result)</code> may be less than dx if a boundary was reached.
+ */
+ public int scrollHorizontallyBy(int dx, Recycler recycler, State state) {
+ return 0;
+ }
+
+ /**
+ * Scroll vertically by dy pixels in screen coordinates and return the distance traveled.
+ * The default implementation does nothing and returns 0.
+ *
+ * @param dy distance to scroll in pixels. Y increases as scroll position
+ * approaches the bottom.
+ * @param recycler Recycler to use for fetching potentially cached views for a
+ * position
+ * @param state Transient state of RecyclerView
+ * @return The actual distance scrolled. The return value will be negative if dy was
+ * negative and scrolling proceeeded in that direction.
+ * <code>Math.abs(result)</code> may be less than dy if a boundary was reached.
+ */
+ public int scrollVerticallyBy(int dy, Recycler recycler, State state) {
+ return 0;
+ }
+
+ /**
+ * Query if horizontal scrolling is currently supported. The default implementation
+ * returns false.
+ *
+ * @return True if this LayoutManager can scroll the current contents horizontally
+ */
+ public boolean canScrollHorizontally() {
+ return false;
+ }
+
+ /**
+ * Query if vertical scrolling is currently supported. The default implementation
+ * returns false.
+ *
+ * @return True if this LayoutManager can scroll the current contents vertically
+ */
+ public boolean canScrollVertically() {
+ return false;
+ }
+
+ /**
+ * Scroll to the specified adapter position.
+ *
+ * Actual position of the item on the screen depends on the LayoutManager implementation.
+ * @param position Scroll to this adapter position.
+ */
+ public void scrollToPosition(int position) {
+ if (DEBUG) {
+ Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract");
+ }
+ }
+
+ /**
+ * <p>Smooth scroll to the specified adapter position.</p>
+ * <p>To support smooth scrolling, override this method, create your {@link SmoothScroller}
+ * instance and call {@link #startSmoothScroll(SmoothScroller)}.
+ * </p>
+ * @param recyclerView The RecyclerView to which this layout manager is attached
+ * @param state Current State of RecyclerView
+ * @param position Scroll to this adapter position.
+ */
+ public void smoothScrollToPosition(RecyclerView recyclerView, State state,
+ int position) {
+ Log.e(TAG, "You must override smoothScrollToPosition to support smooth scrolling");
+ }
+
+ /**
+ * <p>Starts a smooth scroll using the provided SmoothScroller.</p>
+ * <p>Calling this method will cancel any previous smooth scroll request.</p>
+ * @param smoothScroller Instance which defines how smooth scroll should be animated
+ */
+ public void startSmoothScroll(SmoothScroller smoothScroller) {
+ if (mSmoothScroller != null && smoothScroller != mSmoothScroller
+ && mSmoothScroller.isRunning()) {
+ mSmoothScroller.stop();
+ }
+ mSmoothScroller = smoothScroller;
+ mSmoothScroller.start(mRecyclerView, this);
+ }
+
+ /**
+ * @return true if RecycylerView is currently in the state of smooth scrolling.
+ */
+ public boolean isSmoothScrolling() {
+ return mSmoothScroller != null && mSmoothScroller.isRunning();
+ }
+
+
+ /**
+ * Returns the resolved layout direction for this RecyclerView.
+ *
+ * @return {@link android.view.View#LAYOUT_DIRECTION_RTL} if the layout
+ * direction is RTL or returns
+ * {@link android.view.View#LAYOUT_DIRECTION_LTR} if the layout direction
+ * is not RTL.
+ */
+ public int getLayoutDirection() {
+ return mRecyclerView.getLayoutDirection();
+ }
+
+ /**
+ * Ends all animations on the view created by the {@link ItemAnimator}.
+ *
+ * @param view The View for which the animations should be ended.
+ * @see RecyclerView.ItemAnimator#endAnimations()
+ */
+ public void endAnimation(View view) {
+ if (mRecyclerView.mItemAnimator != null) {
+ mRecyclerView.mItemAnimator.endAnimation(getChildViewHolderInt(view));
+ }
+ }
+
+ /**
+ * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view
+ * to the layout that is known to be going away, either because it has been
+ * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the
+ * visible portion of the container but is being laid out in order to inform RecyclerView
+ * in how to animate the item out of view.
+ * <p>
+ * Views added via this method are going to be invisible to LayoutManager after the
+ * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)}
+ * or won't be included in {@link #getChildCount()} method.
+ *
+ * @param child View to add and then remove with animation.
+ */
+ public void addDisappearingView(View child) {
+ addDisappearingView(child, -1);
+ }
+
+ /**
+ * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view
+ * to the layout that is known to be going away, either because it has been
+ * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the
+ * visible portion of the container but is being laid out in order to inform RecyclerView
+ * in how to animate the item out of view.
+ * <p>
+ * Views added via this method are going to be invisible to LayoutManager after the
+ * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)}
+ * or won't be included in {@link #getChildCount()} method.
+ *
+ * @param child View to add and then remove with animation.
+ * @param index Index of the view.
+ */
+ public void addDisappearingView(View child, int index) {
+ addViewInt(child, index, true);
+ }
+
+ /**
+ * Add a view to the currently attached RecyclerView if needed. LayoutManagers should
+ * use this method to add views obtained from a {@link Recycler} using
+ * {@link Recycler#getViewForPosition(int)}.
+ *
+ * @param child View to add
+ */
+ public void addView(View child) {
+ addView(child, -1);
+ }
+
+ /**
+ * Add a view to the currently attached RecyclerView if needed. LayoutManagers should
+ * use this method to add views obtained from a {@link Recycler} using
+ * {@link Recycler#getViewForPosition(int)}.
+ *
+ * @param child View to add
+ * @param index Index to add child at
+ */
+ public void addView(View child, int index) {
+ addViewInt(child, index, false);
+ }
+
+ private void addViewInt(View child, int index, boolean disappearing) {
+ final ViewHolder holder = getChildViewHolderInt(child);
+ if (disappearing || holder.isRemoved()) {
+ // these views will be hidden at the end of the layout pass.
+ mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder);
+ } else {
+ // This may look like unnecessary but may happen if layout manager supports
+ // predictive layouts and adapter removed then re-added the same item.
+ // In this case, added version will be visible in the post layout (because add is
+ // deferred) but RV will still bind it to the same View.
+ // So if a View re-appears in post layout pass, remove it from disappearing list.
+ mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder);
+ }
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (holder.wasReturnedFromScrap() || holder.isScrap()) {
+ if (holder.isScrap()) {
+ holder.unScrap();
+ } else {
+ holder.clearReturnedFromScrapFlag();
+ }
+ mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);
+ if (DISPATCH_TEMP_DETACH) {
+ child.dispatchFinishTemporaryDetach();
+ }
+ } else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child
+ // ensure in correct position
+ int currentIndex = mChildHelper.indexOfChild(child);
+ if (index == -1) {
+ index = mChildHelper.getChildCount();
+ }
+ if (currentIndex == -1) {
+ throw new IllegalStateException("Added View has RecyclerView as parent but"
+ + " view is not a real child. Unfiltered index:"
+ + mRecyclerView.indexOfChild(child));
+ }
+ if (currentIndex != index) {
+ mRecyclerView.mLayout.moveView(currentIndex, index);
+ }
+ } else {
+ mChildHelper.addView(child, index, false);
+ lp.mInsetsDirty = true;
+ if (mSmoothScroller != null && mSmoothScroller.isRunning()) {
+ mSmoothScroller.onChildAttachedToWindow(child);
+ }
+ }
+ if (lp.mPendingInvalidate) {
+ if (DEBUG) {
+ Log.d(TAG, "consuming pending invalidate on child " + lp.mViewHolder);
+ }
+ holder.itemView.invalidate();
+ lp.mPendingInvalidate = false;
+ }
+ }
+
+ /**
+ * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should
+ * use this method to completely remove a child view that is no longer needed.
+ * LayoutManagers should strongly consider recycling removed views using
+ * {@link Recycler#recycleView(android.view.View)}.
+ *
+ * @param child View to remove
+ */
+ public void removeView(View child) {
+ mChildHelper.removeView(child);
+ }
+
+ /**
+ * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should
+ * use this method to completely remove a child view that is no longer needed.
+ * LayoutManagers should strongly consider recycling removed views using
+ * {@link Recycler#recycleView(android.view.View)}.
+ *
+ * @param index Index of the child view to remove
+ */
+ public void removeViewAt(int index) {
+ final View child = getChildAt(index);
+ if (child != null) {
+ mChildHelper.removeViewAt(index);
+ }
+ }
+
+ /**
+ * Remove all views from the currently attached RecyclerView. This will not recycle
+ * any of the affected views; the LayoutManager is responsible for doing so if desired.
+ */
+ public void removeAllViews() {
+ // Only remove non-animating views
+ final int childCount = getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ mChildHelper.removeViewAt(i);
+ }
+ }
+
+ /**
+ * Returns offset of the RecyclerView's text baseline from the its top boundary.
+ *
+ * @return The offset of the RecyclerView's text baseline from the its top boundary; -1 if
+ * there is no baseline.
+ */
+ public int getBaseline() {
+ return -1;
+ }
+
+ /**
+ * Returns the adapter position of the item represented by the given View. This does not
+ * contain any adapter changes that might have happened after the last layout.
+ *
+ * @param view The view to query
+ * @return The adapter position of the item which is rendered by this View.
+ */
+ public int getPosition(View view) {
+ return ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
+ }
+
+ /**
+ * Returns the View type defined by the adapter.
+ *
+ * @param view The view to query
+ * @return The type of the view assigned by the adapter.
+ */
+ public int getItemViewType(View view) {
+ return getChildViewHolderInt(view).getItemViewType();
+ }
+
+ /**
+ * Traverses the ancestors of the given view and returns the item view that contains it
+ * and also a direct child of the LayoutManager.
+ * <p>
+ * Note that this method may return null if the view is a child of the RecyclerView but
+ * not a child of the LayoutManager (e.g. running a disappear animation).
+ *
+ * @param view The view that is a descendant of the LayoutManager.
+ *
+ * @return The direct child of the LayoutManager which contains the given view or null if
+ * the provided view is not a descendant of this LayoutManager.
+ *
+ * @see RecyclerView#getChildViewHolder(View)
+ * @see RecyclerView#findContainingViewHolder(View)
+ */
+ @Nullable
+ public View findContainingItemView(View view) {
+ if (mRecyclerView == null) {
+ return null;
+ }
+ View found = mRecyclerView.findContainingItemView(view);
+ if (found == null) {
+ return null;
+ }
+ if (mChildHelper.isHidden(found)) {
+ return null;
+ }
+ return found;
+ }
+
+ /**
+ * Finds the view which represents the given adapter position.
+ * <p>
+ * This method traverses each child since it has no information about child order.
+ * Override this method to improve performance if your LayoutManager keeps data about
+ * child views.
+ * <p>
+ * If a view is ignored via {@link #ignoreView(View)}, it is also ignored by this method.
+ *
+ * @param position Position of the item in adapter
+ * @return The child view that represents the given position or null if the position is not
+ * laid out
+ */
+ public View findViewByPosition(int position) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ ViewHolder vh = getChildViewHolderInt(child);
+ if (vh == null) {
+ continue;
+ }
+ if (vh.getLayoutPosition() == position && !vh.shouldIgnore()
+ && (mRecyclerView.mState.isPreLayout() || !vh.isRemoved())) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Temporarily detach a child view.
+ *
+ * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange
+ * views currently attached to the RecyclerView. Generally LayoutManager implementations
+ * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}
+ * so that the detached view may be rebound and reused.</p>
+ *
+ * <p>If a LayoutManager uses this method to detach a view, it <em>must</em>
+ * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach}
+ * or {@link #removeDetachedView(android.view.View) fully remove} the detached view
+ * before the LayoutManager entry point method called by RecyclerView returns.</p>
+ *
+ * @param child Child to detach
+ */
+ public void detachView(View child) {
+ final int ind = mChildHelper.indexOfChild(child);
+ if (ind >= 0) {
+ detachViewInternal(ind, child);
+ }
+ }
+
+ /**
+ * Temporarily detach a child view.
+ *
+ * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange
+ * views currently attached to the RecyclerView. Generally LayoutManager implementations
+ * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}
+ * so that the detached view may be rebound and reused.</p>
+ *
+ * <p>If a LayoutManager uses this method to detach a view, it <em>must</em>
+ * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach}
+ * or {@link #removeDetachedView(android.view.View) fully remove} the detached view
+ * before the LayoutManager entry point method called by RecyclerView returns.</p>
+ *
+ * @param index Index of the child to detach
+ */
+ public void detachViewAt(int index) {
+ detachViewInternal(index, getChildAt(index));
+ }
+
+ private void detachViewInternal(int index, View view) {
+ if (DISPATCH_TEMP_DETACH) {
+ view.dispatchStartTemporaryDetach();
+ }
+ mChildHelper.detachViewFromParent(index);
+ }
+
+ /**
+ * Reattach a previously {@link #detachView(android.view.View) detached} view.
+ * This method should not be used to reattach views that were previously
+ * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}.
+ *
+ * @param child Child to reattach
+ * @param index Intended child index for child
+ * @param lp LayoutParams for child
+ */
+ public void attachView(View child, int index, LayoutParams lp) {
+ ViewHolder vh = getChildViewHolderInt(child);
+ if (vh.isRemoved()) {
+ mRecyclerView.mViewInfoStore.addToDisappearedInLayout(vh);
+ } else {
+ mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(vh);
+ }
+ mChildHelper.attachViewToParent(child, index, lp, vh.isRemoved());
+ if (DISPATCH_TEMP_DETACH) {
+ child.dispatchFinishTemporaryDetach();
+ }
+ }
+
+ /**
+ * Reattach a previously {@link #detachView(android.view.View) detached} view.
+ * This method should not be used to reattach views that were previously
+ * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}.
+ *
+ * @param child Child to reattach
+ * @param index Intended child index for child
+ */
+ public void attachView(View child, int index) {
+ attachView(child, index, (LayoutParams) child.getLayoutParams());
+ }
+
+ /**
+ * Reattach a previously {@link #detachView(android.view.View) detached} view.
+ * This method should not be used to reattach views that were previously
+ * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}.
+ *
+ * @param child Child to reattach
+ */
+ public void attachView(View child) {
+ attachView(child, -1);
+ }
+
+ /**
+ * Finish removing a view that was previously temporarily
+ * {@link #detachView(android.view.View) detached}.
+ *
+ * @param child Detached child to remove
+ */
+ public void removeDetachedView(View child) {
+ mRecyclerView.removeDetachedView(child, false);
+ }
+
+ /**
+ * Moves a View from one position to another.
+ *
+ * @param fromIndex The View's initial index
+ * @param toIndex The View's target index
+ */
+ public void moveView(int fromIndex, int toIndex) {
+ View view = getChildAt(fromIndex);
+ if (view == null) {
+ throw new IllegalArgumentException("Cannot move a child from non-existing index:"
+ + fromIndex);
+ }
+ detachViewAt(fromIndex);
+ attachView(view, toIndex);
+ }
+
+ /**
+ * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap.
+ *
+ * <p>Scrapping a view allows it to be rebound and reused to show updated or
+ * different data.</p>
+ *
+ * @param child Child to detach and scrap
+ * @param recycler Recycler to deposit the new scrap view into
+ */
+ public void detachAndScrapView(View child, Recycler recycler) {
+ int index = mChildHelper.indexOfChild(child);
+ scrapOrRecycleView(recycler, index, child);
+ }
+
+ /**
+ * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap.
+ *
+ * <p>Scrapping a view allows it to be rebound and reused to show updated or
+ * different data.</p>
+ *
+ * @param index Index of child to detach and scrap
+ * @param recycler Recycler to deposit the new scrap view into
+ */
+ public void detachAndScrapViewAt(int index, Recycler recycler) {
+ final View child = getChildAt(index);
+ scrapOrRecycleView(recycler, index, child);
+ }
+
+ /**
+ * Remove a child view and recycle it using the given Recycler.
+ *
+ * @param child Child to remove and recycle
+ * @param recycler Recycler to use to recycle child
+ */
+ public void removeAndRecycleView(View child, Recycler recycler) {
+ removeView(child);
+ recycler.recycleView(child);
+ }
+
+ /**
+ * Remove a child view and recycle it using the given Recycler.
+ *
+ * @param index Index of child to remove and recycle
+ * @param recycler Recycler to use to recycle child
+ */
+ public void removeAndRecycleViewAt(int index, Recycler recycler) {
+ final View view = getChildAt(index);
+ removeViewAt(index);
+ recycler.recycleView(view);
+ }
+
+ /**
+ * Return the current number of child views attached to the parent RecyclerView.
+ * This does not include child views that were temporarily detached and/or scrapped.
+ *
+ * @return Number of attached children
+ */
+ public int getChildCount() {
+ return mChildHelper != null ? mChildHelper.getChildCount() : 0;
+ }
+
+ /**
+ * Return the child view at the given index
+ * @param index Index of child to return
+ * @return Child view at index
+ */
+ public View getChildAt(int index) {
+ return mChildHelper != null ? mChildHelper.getChildAt(index) : null;
+ }
+
+ /**
+ * Return the width measurement spec mode of the RecyclerView.
+ * <p>
+ * This value is set only if the LayoutManager opts into the auto measure api via
+ * {@link #setAutoMeasureEnabled(boolean)}.
+ * <p>
+ * When RecyclerView is running a layout, this value is always set to
+ * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
+ *
+ * @return Width measure spec mode.
+ *
+ * @see View.MeasureSpec#getMode(int)
+ * @see View#onMeasure(int, int)
+ */
+ public int getWidthMode() {
+ return mWidthMode;
+ }
+
+ /**
+ * Return the height measurement spec mode of the RecyclerView.
+ * <p>
+ * This value is set only if the LayoutManager opts into the auto measure api via
+ * {@link #setAutoMeasureEnabled(boolean)}.
+ * <p>
+ * When RecyclerView is running a layout, this value is always set to
+ * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
+ *
+ * @return Height measure spec mode.
+ *
+ * @see View.MeasureSpec#getMode(int)
+ * @see View#onMeasure(int, int)
+ */
+ public int getHeightMode() {
+ return mHeightMode;
+ }
+
+ /**
+ * Return the width of the parent RecyclerView
+ *
+ * @return Width in pixels
+ */
+ public int getWidth() {
+ return mWidth;
+ }
+
+ /**
+ * Return the height of the parent RecyclerView
+ *
+ * @return Height in pixels
+ */
+ public int getHeight() {
+ return mHeight;
+ }
+
+ /**
+ * Return the left padding of the parent RecyclerView
+ *
+ * @return Padding in pixels
+ */
+ public int getPaddingLeft() {
+ return mRecyclerView != null ? mRecyclerView.getPaddingLeft() : 0;
+ }
+
+ /**
+ * Return the top padding of the parent RecyclerView
+ *
+ * @return Padding in pixels
+ */
+ public int getPaddingTop() {
+ return mRecyclerView != null ? mRecyclerView.getPaddingTop() : 0;
+ }
+
+ /**
+ * Return the right padding of the parent RecyclerView
+ *
+ * @return Padding in pixels
+ */
+ public int getPaddingRight() {
+ return mRecyclerView != null ? mRecyclerView.getPaddingRight() : 0;
+ }
+
+ /**
+ * Return the bottom padding of the parent RecyclerView
+ *
+ * @return Padding in pixels
+ */
+ public int getPaddingBottom() {
+ return mRecyclerView != null ? mRecyclerView.getPaddingBottom() : 0;
+ }
+
+ /**
+ * Return the start padding of the parent RecyclerView
+ *
+ * @return Padding in pixels
+ */
+ public int getPaddingStart() {
+ return mRecyclerView != null ? mRecyclerView.getPaddingStart() : 0;
+ }
+
+ /**
+ * Return the end padding of the parent RecyclerView
+ *
+ * @return Padding in pixels
+ */
+ public int getPaddingEnd() {
+ return mRecyclerView != null ? mRecyclerView.getPaddingEnd() : 0;
+ }
+
+ /**
+ * Returns true if the RecyclerView this LayoutManager is bound to has focus.
+ *
+ * @return True if the RecyclerView has focus, false otherwise.
+ * @see View#isFocused()
+ */
+ public boolean isFocused() {
+ return mRecyclerView != null && mRecyclerView.isFocused();
+ }
+
+ /**
+ * Returns true if the RecyclerView this LayoutManager is bound to has or contains focus.
+ *
+ * @return true if the RecyclerView has or contains focus
+ * @see View#hasFocus()
+ */
+ public boolean hasFocus() {
+ return mRecyclerView != null && mRecyclerView.hasFocus();
+ }
+
+ /**
+ * Returns the item View which has or contains focus.
+ *
+ * @return A direct child of RecyclerView which has focus or contains the focused child.
+ */
+ public View getFocusedChild() {
+ if (mRecyclerView == null) {
+ return null;
+ }
+ final View focused = mRecyclerView.getFocusedChild();
+ if (focused == null || mChildHelper.isHidden(focused)) {
+ return null;
+ }
+ return focused;
+ }
+
+ /**
+ * Returns the number of items in the adapter bound to the parent RecyclerView.
+ * <p>
+ * Note that this number is not necessarily equal to
+ * {@link State#getItemCount() State#getItemCount()}. In methods where {@link State} is
+ * available, you should use {@link State#getItemCount() State#getItemCount()} instead.
+ * For more details, check the documentation for
+ * {@link State#getItemCount() State#getItemCount()}.
+ *
+ * @return The number of items in the bound adapter
+ * @see State#getItemCount()
+ */
+ public int getItemCount() {
+ final Adapter a = mRecyclerView != null ? mRecyclerView.getAdapter() : null;
+ return a != null ? a.getItemCount() : 0;
+ }
+
+ /**
+ * Offset all child views attached to the parent RecyclerView by dx pixels along
+ * the horizontal axis.
+ *
+ * @param dx Pixels to offset by
+ */
+ public void offsetChildrenHorizontal(int dx) {
+ if (mRecyclerView != null) {
+ mRecyclerView.offsetChildrenHorizontal(dx);
+ }
+ }
+
+ /**
+ * Offset all child views attached to the parent RecyclerView by dy pixels along
+ * the vertical axis.
+ *
+ * @param dy Pixels to offset by
+ */
+ public void offsetChildrenVertical(int dy) {
+ if (mRecyclerView != null) {
+ mRecyclerView.offsetChildrenVertical(dy);
+ }
+ }
+
+ /**
+ * Flags a view so that it will not be scrapped or recycled.
+ * <p>
+ * Scope of ignoring a child is strictly restricted to position tracking, scrapping and
+ * recyling. Methods like {@link #removeAndRecycleAllViews(Recycler)} will ignore the child
+ * whereas {@link #removeAllViews()} or {@link #offsetChildrenHorizontal(int)} will not
+ * ignore the child.
+ * <p>
+ * Before this child can be recycled again, you have to call
+ * {@link #stopIgnoringView(View)}.
+ * <p>
+ * You can call this method only if your LayoutManger is in onLayout or onScroll callback.
+ *
+ * @param view View to ignore.
+ * @see #stopIgnoringView(View)
+ */
+ public void ignoreView(View view) {
+ if (view.getParent() != mRecyclerView || mRecyclerView.indexOfChild(view) == -1) {
+ // checking this because calling this method on a recycled or detached view may
+ // cause loss of state.
+ throw new IllegalArgumentException("View should be fully attached to be ignored");
+ }
+ final ViewHolder vh = getChildViewHolderInt(view);
+ vh.addFlags(ViewHolder.FLAG_IGNORE);
+ mRecyclerView.mViewInfoStore.removeViewHolder(vh);
+ }
+
+ /**
+ * View can be scrapped and recycled again.
+ * <p>
+ * Note that calling this method removes all information in the view holder.
+ * <p>
+ * You can call this method only if your LayoutManger is in onLayout or onScroll callback.
+ *
+ * @param view View to ignore.
+ */
+ public void stopIgnoringView(View view) {
+ final ViewHolder vh = getChildViewHolderInt(view);
+ vh.stopIgnoring();
+ vh.resetInternal();
+ vh.addFlags(ViewHolder.FLAG_INVALID);
+ }
+
+ /**
+ * Temporarily detach and scrap all currently attached child views. Views will be scrapped
+ * into the given Recycler. The Recycler may prefer to reuse scrap views before
+ * other views that were previously recycled.
+ *
+ * @param recycler Recycler to scrap views into
+ */
+ public void detachAndScrapAttachedViews(Recycler recycler) {
+ final int childCount = getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ final View v = getChildAt(i);
+ scrapOrRecycleView(recycler, i, v);
+ }
+ }
+
+ private void scrapOrRecycleView(Recycler recycler, int index, View view) {
+ final ViewHolder viewHolder = getChildViewHolderInt(view);
+ if (viewHolder.shouldIgnore()) {
+ if (DEBUG) {
+ Log.d(TAG, "ignoring view " + viewHolder);
+ }
+ return;
+ }
+ if (viewHolder.isInvalid() && !viewHolder.isRemoved()
+ && !mRecyclerView.mAdapter.hasStableIds()) {
+ removeViewAt(index);
+ recycler.recycleViewHolderInternal(viewHolder);
+ } else {
+ detachViewAt(index);
+ recycler.scrapView(view);
+ mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
+ }
+ }
+
+ /**
+ * Recycles the scrapped views.
+ * <p>
+ * When a view is detached and removed, it does not trigger a ViewGroup invalidate. This is
+ * the expected behavior if scrapped views are used for animations. Otherwise, we need to
+ * call remove and invalidate RecyclerView to ensure UI update.
+ *
+ * @param recycler Recycler
+ */
+ void removeAndRecycleScrapInt(Recycler recycler) {
+ final int scrapCount = recycler.getScrapCount();
+ // Loop backward, recycler might be changed by removeDetachedView()
+ for (int i = scrapCount - 1; i >= 0; i--) {
+ final View scrap = recycler.getScrapViewAt(i);
+ final ViewHolder vh = getChildViewHolderInt(scrap);
+ if (vh.shouldIgnore()) {
+ continue;
+ }
+ // If the scrap view is animating, we need to cancel them first. If we cancel it
+ // here, ItemAnimator callback may recycle it which will cause double recycling.
+ // To avoid this, we mark it as not recycleable before calling the item animator.
+ // Since removeDetachedView calls a user API, a common mistake (ending animations on
+ // the view) may recycle it too, so we guard it before we call user APIs.
+ vh.setIsRecyclable(false);
+ if (vh.isTmpDetached()) {
+ mRecyclerView.removeDetachedView(scrap, false);
+ }
+ if (mRecyclerView.mItemAnimator != null) {
+ mRecyclerView.mItemAnimator.endAnimation(vh);
+ }
+ vh.setIsRecyclable(true);
+ recycler.quickRecycleScrapView(scrap);
+ }
+ recycler.clearScrap();
+ if (scrapCount > 0) {
+ mRecyclerView.invalidate();
+ }
+ }
+
+
+ /**
+ * Measure a child view using standard measurement policy, taking the padding
+ * of the parent RecyclerView and any added item decorations into account.
+ *
+ * <p>If the RecyclerView can be scrolled in either dimension the caller may
+ * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p>
+ *
+ * @param child Child view to measure
+ * @param widthUsed Width in pixels currently consumed by other views, if relevant
+ * @param heightUsed Height in pixels currently consumed by other views, if relevant
+ */
+ public void measureChild(View child, int widthUsed, int heightUsed) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
+ widthUsed += insets.left + insets.right;
+ heightUsed += insets.top + insets.bottom;
+ final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
+ getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
+ canScrollHorizontally());
+ final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
+ getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
+ canScrollVertically());
+ if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
+ child.measure(widthSpec, heightSpec);
+ }
+ }
+
+ /**
+ * RecyclerView internally does its own View measurement caching which should help with
+ * WRAP_CONTENT.
+ * <p>
+ * Use this method if the View is already measured once in this layout pass.
+ */
+ boolean shouldReMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) {
+ return !mMeasurementCacheEnabled
+ || !isMeasurementUpToDate(child.getMeasuredWidth(), widthSpec, lp.width)
+ || !isMeasurementUpToDate(child.getMeasuredHeight(), heightSpec, lp.height);
+ }
+
+ // we may consider making this public
+ /**
+ * RecyclerView internally does its own View measurement caching which should help with
+ * WRAP_CONTENT.
+ * <p>
+ * Use this method if the View is not yet measured and you need to decide whether to
+ * measure this View or not.
+ */
+ boolean shouldMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) {
+ return child.isLayoutRequested()
+ || !mMeasurementCacheEnabled
+ || !isMeasurementUpToDate(child.getWidth(), widthSpec, lp.width)
+ || !isMeasurementUpToDate(child.getHeight(), heightSpec, lp.height);
+ }
+
+ /**
+ * In addition to the View Framework's measurement cache, RecyclerView uses its own
+ * additional measurement cache for its children to avoid re-measuring them when not
+ * necessary. It is on by default but it can be turned off via
+ * {@link #setMeasurementCacheEnabled(boolean)}.
+ *
+ * @return True if measurement cache is enabled, false otherwise.
+ *
+ * @see #setMeasurementCacheEnabled(boolean)
+ */
+ public boolean isMeasurementCacheEnabled() {
+ return mMeasurementCacheEnabled;
+ }
+
+ /**
+ * Sets whether RecyclerView should use its own measurement cache for the children. This is
+ * a more aggressive cache than the framework uses.
+ *
+ * @param measurementCacheEnabled True to enable the measurement cache, false otherwise.
+ *
+ * @see #isMeasurementCacheEnabled()
+ */
+ public void setMeasurementCacheEnabled(boolean measurementCacheEnabled) {
+ mMeasurementCacheEnabled = measurementCacheEnabled;
+ }
+
+ private static boolean isMeasurementUpToDate(int childSize, int spec, int dimension) {
+ final int specMode = MeasureSpec.getMode(spec);
+ final int specSize = MeasureSpec.getSize(spec);
+ if (dimension > 0 && childSize != dimension) {
+ return false;
+ }
+ switch (specMode) {
+ case MeasureSpec.UNSPECIFIED:
+ return true;
+ case MeasureSpec.AT_MOST:
+ return specSize >= childSize;
+ case MeasureSpec.EXACTLY:
+ return specSize == childSize;
+ }
+ return false;
+ }
+
+ /**
+ * Measure a child view using standard measurement policy, taking the padding
+ * of the parent RecyclerView, any added item decorations and the child margins
+ * into account.
+ *
+ * <p>If the RecyclerView can be scrolled in either dimension the caller may
+ * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p>
+ *
+ * @param child Child view to measure
+ * @param widthUsed Width in pixels currently consumed by other views, if relevant
+ * @param heightUsed Height in pixels currently consumed by other views, if relevant
+ */
+ public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
+ widthUsed += insets.left + insets.right;
+ heightUsed += insets.top + insets.bottom;
+
+ final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
+ getPaddingLeft() + getPaddingRight()
+ + lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
+ canScrollHorizontally());
+ final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
+ getPaddingTop() + getPaddingBottom()
+ + lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
+ canScrollVertically());
+ if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
+ child.measure(widthSpec, heightSpec);
+ }
+ }
+
+ /**
+ * Calculate a MeasureSpec value for measuring a child view in one dimension.
+ *
+ * @param parentSize Size of the parent view where the child will be placed
+ * @param padding Total space currently consumed by other elements of the parent
+ * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT.
+ * Generally obtained from the child view's LayoutParams
+ * @param canScroll true if the parent RecyclerView can scroll in this dimension
+ *
+ * @return a MeasureSpec value for the child view
+ * @deprecated use {@link #getChildMeasureSpec(int, int, int, int, boolean)}
+ */
+ @Deprecated
+ public static int getChildMeasureSpec(int parentSize, int padding, int childDimension,
+ boolean canScroll) {
+ int size = Math.max(0, parentSize - padding);
+ int resultSize = 0;
+ int resultMode = 0;
+ if (canScroll) {
+ if (childDimension >= 0) {
+ resultSize = childDimension;
+ resultMode = MeasureSpec.EXACTLY;
+ } else {
+ // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap
+ // instead using UNSPECIFIED.
+ resultSize = 0;
+ resultMode = MeasureSpec.UNSPECIFIED;
+ }
+ } else {
+ if (childDimension >= 0) {
+ resultSize = childDimension;
+ resultMode = MeasureSpec.EXACTLY;
+ } else if (childDimension == LayoutParams.MATCH_PARENT) {
+ resultSize = size;
+ // TODO this should be my spec.
+ resultMode = MeasureSpec.EXACTLY;
+ } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+ resultSize = size;
+ resultMode = MeasureSpec.AT_MOST;
+ }
+ }
+ return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
+ }
+
+ /**
+ * Calculate a MeasureSpec value for measuring a child view in one dimension.
+ *
+ * @param parentSize Size of the parent view where the child will be placed
+ * @param parentMode The measurement spec mode of the parent
+ * @param padding Total space currently consumed by other elements of parent
+ * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT.
+ * Generally obtained from the child view's LayoutParams
+ * @param canScroll true if the parent RecyclerView can scroll in this dimension
+ *
+ * @return a MeasureSpec value for the child view
+ */
+ public static int getChildMeasureSpec(int parentSize, int parentMode, int padding,
+ int childDimension, boolean canScroll) {
+ int size = Math.max(0, parentSize - padding);
+ int resultSize = 0;
+ int resultMode = 0;
+ if (canScroll) {
+ if (childDimension >= 0) {
+ resultSize = childDimension;
+ resultMode = MeasureSpec.EXACTLY;
+ } else if (childDimension == LayoutParams.MATCH_PARENT) {
+ switch (parentMode) {
+ case MeasureSpec.AT_MOST:
+ case MeasureSpec.EXACTLY:
+ resultSize = size;
+ resultMode = parentMode;
+ break;
+ case MeasureSpec.UNSPECIFIED:
+ resultSize = 0;
+ resultMode = MeasureSpec.UNSPECIFIED;
+ break;
+ }
+ } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+ resultSize = 0;
+ resultMode = MeasureSpec.UNSPECIFIED;
+ }
+ } else {
+ if (childDimension >= 0) {
+ resultSize = childDimension;
+ resultMode = MeasureSpec.EXACTLY;
+ } else if (childDimension == LayoutParams.MATCH_PARENT) {
+ resultSize = size;
+ resultMode = parentMode;
+ } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+ resultSize = size;
+ if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) {
+ resultMode = MeasureSpec.AT_MOST;
+ } else {
+ resultMode = MeasureSpec.UNSPECIFIED;
+ }
+
+ }
+ }
+ //noinspection WrongConstant
+ return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
+ }
+
+ /**
+ * Returns the measured width of the given child, plus the additional size of
+ * any insets applied by {@link ItemDecoration ItemDecorations}.
+ *
+ * @param child Child view to query
+ * @return child's measured width plus <code>ItemDecoration</code> insets
+ *
+ * @see View#getMeasuredWidth()
+ */
+ public int getDecoratedMeasuredWidth(View child) {
+ final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+ return child.getMeasuredWidth() + insets.left + insets.right;
+ }
+
+ /**
+ * Returns the measured height of the given child, plus the additional size of
+ * any insets applied by {@link ItemDecoration ItemDecorations}.
+ *
+ * @param child Child view to query
+ * @return child's measured height plus <code>ItemDecoration</code> insets
+ *
+ * @see View#getMeasuredHeight()
+ */
+ public int getDecoratedMeasuredHeight(View child) {
+ final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+ return child.getMeasuredHeight() + insets.top + insets.bottom;
+ }
+
+ /**
+ * Lay out the given child view within the RecyclerView using coordinates that
+ * include any current {@link ItemDecoration ItemDecorations}.
+ *
+ * <p>LayoutManagers should prefer working in sizes and coordinates that include
+ * item decoration insets whenever possible. This allows the LayoutManager to effectively
+ * ignore decoration insets within measurement and layout code. See the following
+ * methods:</p>
+ * <ul>
+ * <li>{@link #layoutDecoratedWithMargins(View, int, int, int, int)}</li>
+ * <li>{@link #getDecoratedBoundsWithMargins(View, Rect)}</li>
+ * <li>{@link #measureChild(View, int, int)}</li>
+ * <li>{@link #measureChildWithMargins(View, int, int)}</li>
+ * <li>{@link #getDecoratedLeft(View)}</li>
+ * <li>{@link #getDecoratedTop(View)}</li>
+ * <li>{@link #getDecoratedRight(View)}</li>
+ * <li>{@link #getDecoratedBottom(View)}</li>
+ * <li>{@link #getDecoratedMeasuredWidth(View)}</li>
+ * <li>{@link #getDecoratedMeasuredHeight(View)}</li>
+ * </ul>
+ *
+ * @param child Child to lay out
+ * @param left Left edge, with item decoration insets included
+ * @param top Top edge, with item decoration insets included
+ * @param right Right edge, with item decoration insets included
+ * @param bottom Bottom edge, with item decoration insets included
+ *
+ * @see View#layout(int, int, int, int)
+ * @see #layoutDecoratedWithMargins(View, int, int, int, int)
+ */
+ public void layoutDecorated(View child, int left, int top, int right, int bottom) {
+ final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+ child.layout(left + insets.left, top + insets.top, right - insets.right,
+ bottom - insets.bottom);
+ }
+
+ /**
+ * Lay out the given child view within the RecyclerView using coordinates that
+ * include any current {@link ItemDecoration ItemDecorations} and margins.
+ *
+ * <p>LayoutManagers should prefer working in sizes and coordinates that include
+ * item decoration insets whenever possible. This allows the LayoutManager to effectively
+ * ignore decoration insets within measurement and layout code. See the following
+ * methods:</p>
+ * <ul>
+ * <li>{@link #layoutDecorated(View, int, int, int, int)}</li>
+ * <li>{@link #measureChild(View, int, int)}</li>
+ * <li>{@link #measureChildWithMargins(View, int, int)}</li>
+ * <li>{@link #getDecoratedLeft(View)}</li>
+ * <li>{@link #getDecoratedTop(View)}</li>
+ * <li>{@link #getDecoratedRight(View)}</li>
+ * <li>{@link #getDecoratedBottom(View)}</li>
+ * <li>{@link #getDecoratedMeasuredWidth(View)}</li>
+ * <li>{@link #getDecoratedMeasuredHeight(View)}</li>
+ * </ul>
+ *
+ * @param child Child to lay out
+ * @param left Left edge, with item decoration insets and left margin included
+ * @param top Top edge, with item decoration insets and top margin included
+ * @param right Right edge, with item decoration insets and right margin included
+ * @param bottom Bottom edge, with item decoration insets and bottom margin included
+ *
+ * @see View#layout(int, int, int, int)
+ * @see #layoutDecorated(View, int, int, int, int)
+ */
+ public void layoutDecoratedWithMargins(View child, int left, int top, int right,
+ int bottom) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ final Rect insets = lp.mDecorInsets;
+ child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
+ right - insets.right - lp.rightMargin,
+ bottom - insets.bottom - lp.bottomMargin);
+ }
+
+ /**
+ * Calculates the bounding box of the View while taking into account its matrix changes
+ * (translation, scale etc) with respect to the RecyclerView.
+ * <p>
+ * If {@code includeDecorInsets} is {@code true}, they are applied first before applying
+ * the View's matrix so that the decor offsets also go through the same transformation.
+ *
+ * @param child The ItemView whose bounding box should be calculated.
+ * @param includeDecorInsets True if the decor insets should be included in the bounding box
+ * @param out The rectangle into which the output will be written.
+ */
+ public void getTransformedBoundingBox(View child, boolean includeDecorInsets, Rect out) {
+ if (includeDecorInsets) {
+ Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+ out.set(-insets.left, -insets.top,
+ child.getWidth() + insets.right, child.getHeight() + insets.bottom);
+ } else {
+ out.set(0, 0, child.getWidth(), child.getHeight());
+ }
+
+ if (mRecyclerView != null) {
+ final Matrix childMatrix = child.getMatrix();
+ if (childMatrix != null && !childMatrix.isIdentity()) {
+ final RectF tempRectF = mRecyclerView.mTempRectF;
+ tempRectF.set(out);
+ childMatrix.mapRect(tempRectF);
+ out.set(
+ (int) Math.floor(tempRectF.left),
+ (int) Math.floor(tempRectF.top),
+ (int) Math.ceil(tempRectF.right),
+ (int) Math.ceil(tempRectF.bottom)
+ );
+ }
+ }
+ out.offset(child.getLeft(), child.getTop());
+ }
+
+ /**
+ * Returns the bounds of the view including its decoration and margins.
+ *
+ * @param view The view element to check
+ * @param outBounds A rect that will receive the bounds of the element including its
+ * decoration and margins.
+ */
+ public void getDecoratedBoundsWithMargins(View view, Rect outBounds) {
+ RecyclerView.getDecoratedBoundsWithMarginsInt(view, outBounds);
+ }
+
+ /**
+ * Returns the left edge of the given child view within its parent, offset by any applied
+ * {@link ItemDecoration ItemDecorations}.
+ *
+ * @param child Child to query
+ * @return Child left edge with offsets applied
+ * @see #getLeftDecorationWidth(View)
+ */
+ public int getDecoratedLeft(View child) {
+ return child.getLeft() - getLeftDecorationWidth(child);
+ }
+
+ /**
+ * Returns the top edge of the given child view within its parent, offset by any applied
+ * {@link ItemDecoration ItemDecorations}.
+ *
+ * @param child Child to query
+ * @return Child top edge with offsets applied
+ * @see #getTopDecorationHeight(View)
+ */
+ public int getDecoratedTop(View child) {
+ return child.getTop() - getTopDecorationHeight(child);
+ }
+
+ /**
+ * Returns the right edge of the given child view within its parent, offset by any applied
+ * {@link ItemDecoration ItemDecorations}.
+ *
+ * @param child Child to query
+ * @return Child right edge with offsets applied
+ * @see #getRightDecorationWidth(View)
+ */
+ public int getDecoratedRight(View child) {
+ return child.getRight() + getRightDecorationWidth(child);
+ }
+
+ /**
+ * Returns the bottom edge of the given child view within its parent, offset by any applied
+ * {@link ItemDecoration ItemDecorations}.
+ *
+ * @param child Child to query
+ * @return Child bottom edge with offsets applied
+ * @see #getBottomDecorationHeight(View)
+ */
+ public int getDecoratedBottom(View child) {
+ return child.getBottom() + getBottomDecorationHeight(child);
+ }
+
+ /**
+ * Calculates the item decor insets applied to the given child and updates the provided
+ * Rect instance with the inset values.
+ * <ul>
+ * <li>The Rect's left is set to the total width of left decorations.</li>
+ * <li>The Rect's top is set to the total height of top decorations.</li>
+ * <li>The Rect's right is set to the total width of right decorations.</li>
+ * <li>The Rect's bottom is set to total height of bottom decorations.</li>
+ * </ul>
+ * <p>
+ * Note that item decorations are automatically calculated when one of the LayoutManager's
+ * measure child methods is called. If you need to measure the child with custom specs via
+ * {@link View#measure(int, int)}, you can use this method to get decorations.
+ *
+ * @param child The child view whose decorations should be calculated
+ * @param outRect The Rect to hold result values
+ */
+ public void calculateItemDecorationsForChild(View child, Rect outRect) {
+ if (mRecyclerView == null) {
+ outRect.set(0, 0, 0, 0);
+ return;
+ }
+ Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
+ outRect.set(insets);
+ }
+
+ /**
+ * Returns the total height of item decorations applied to child's top.
+ * <p>
+ * Note that this value is not updated until the View is measured or
+ * {@link #calculateItemDecorationsForChild(View, Rect)} is called.
+ *
+ * @param child Child to query
+ * @return The total height of item decorations applied to the child's top.
+ * @see #getDecoratedTop(View)
+ * @see #calculateItemDecorationsForChild(View, Rect)
+ */
+ public int getTopDecorationHeight(View child) {
+ return ((LayoutParams) child.getLayoutParams()).mDecorInsets.top;
+ }
+
+ /**
+ * Returns the total height of item decorations applied to child's bottom.
+ * <p>
+ * Note that this value is not updated until the View is measured or
+ * {@link #calculateItemDecorationsForChild(View, Rect)} is called.
+ *
+ * @param child Child to query
+ * @return The total height of item decorations applied to the child's bottom.
+ * @see #getDecoratedBottom(View)
+ * @see #calculateItemDecorationsForChild(View, Rect)
+ */
+ public int getBottomDecorationHeight(View child) {
+ return ((LayoutParams) child.getLayoutParams()).mDecorInsets.bottom;
+ }
+
+ /**
+ * Returns the total width of item decorations applied to child's left.
+ * <p>
+ * Note that this value is not updated until the View is measured or
+ * {@link #calculateItemDecorationsForChild(View, Rect)} is called.
+ *
+ * @param child Child to query
+ * @return The total width of item decorations applied to the child's left.
+ * @see #getDecoratedLeft(View)
+ * @see #calculateItemDecorationsForChild(View, Rect)
+ */
+ public int getLeftDecorationWidth(View child) {
+ return ((LayoutParams) child.getLayoutParams()).mDecorInsets.left;
+ }
+
+ /**
+ * Returns the total width of item decorations applied to child's right.
+ * <p>
+ * Note that this value is not updated until the View is measured or
+ * {@link #calculateItemDecorationsForChild(View, Rect)} is called.
+ *
+ * @param child Child to query
+ * @return The total width of item decorations applied to the child's right.
+ * @see #getDecoratedRight(View)
+ * @see #calculateItemDecorationsForChild(View, Rect)
+ */
+ public int getRightDecorationWidth(View child) {
+ return ((LayoutParams) child.getLayoutParams()).mDecorInsets.right;
+ }
+
+ /**
+ * Called when searching for a focusable view in the given direction has failed
+ * for the current content of the RecyclerView.
+ *
+ * <p>This is the LayoutManager's opportunity to populate views in the given direction
+ * to fulfill the request if it can. The LayoutManager should attach and return
+ * the view to be focused. The default implementation returns null.</p>
+ *
+ * @param focused The currently focused view
+ * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+ * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+ * or 0 for not applicable
+ * @param recycler The recycler to use for obtaining views for currently offscreen items
+ * @param state Transient state of RecyclerView
+ * @return The chosen view to be focused
+ */
+ @Nullable
+ public View onFocusSearchFailed(View focused, int direction, Recycler recycler,
+ State state) {
+ return null;
+ }
+
+ /**
+ * This method gives a LayoutManager an opportunity to intercept the initial focus search
+ * before the default behavior of {@link FocusFinder} is used. If this method returns
+ * null FocusFinder will attempt to find a focusable child view. If it fails
+ * then {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)}
+ * will be called to give the LayoutManager an opportunity to add new views for items
+ * that did not have attached views representing them. The LayoutManager should not add
+ * or remove views from this method.
+ *
+ * @param focused The currently focused view
+ * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+ * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+ * @return A descendant view to focus or null to fall back to default behavior.
+ * The default implementation returns null.
+ */
+ public View onInterceptFocusSearch(View focused, int direction) {
+ return null;
+ }
+
+ /**
+ * Called when a child of the RecyclerView wants a particular rectangle to be positioned
+ * onto the screen. See {@link ViewParent#requestChildRectangleOnScreen(android.view.View,
+ * android.graphics.Rect, boolean)} for more details.
+ *
+ * <p>The base implementation will attempt to perform a standard programmatic scroll
+ * to bring the given rect into view, within the padded area of the RecyclerView.</p>
+ *
+ * @param child The direct child making the request.
+ * @param rect The rectangle in the child's coordinates the child
+ * wishes to be on the screen.
+ * @param immediate True to forbid animated or delayed scrolling,
+ * false otherwise
+ * @return Whether the group scrolled to handle the operation
+ */
+ public boolean requestChildRectangleOnScreen(RecyclerView parent, View child, Rect rect,
+ boolean immediate) {
+ final int parentLeft = getPaddingLeft();
+ final int parentTop = getPaddingTop();
+ final int parentRight = getWidth() - getPaddingRight();
+ final int parentBottom = getHeight() - getPaddingBottom();
+ final int childLeft = child.getLeft() + rect.left - child.getScrollX();
+ final int childTop = child.getTop() + rect.top - child.getScrollY();
+ final int childRight = childLeft + rect.width();
+ final int childBottom = childTop + rect.height();
+
+ final int offScreenLeft = Math.min(0, childLeft - parentLeft);
+ final int offScreenTop = Math.min(0, childTop - parentTop);
+ final int offScreenRight = Math.max(0, childRight - parentRight);
+ final int offScreenBottom = Math.max(0, childBottom - parentBottom);
+
+ // Favor the "start" layout direction over the end when bringing one side or the other
+ // of a large rect into view. If we decide to bring in end because start is already
+ // visible, limit the scroll such that start won't go out of bounds.
+ final int dx;
+ if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ dx = offScreenRight != 0 ? offScreenRight
+ : Math.max(offScreenLeft, childRight - parentRight);
+ } else {
+ dx = offScreenLeft != 0 ? offScreenLeft
+ : Math.min(childLeft - parentLeft, offScreenRight);
+ }
+
+ // Favor bringing the top into view over the bottom. If top is already visible and
+ // we should scroll to make bottom visible, make sure top does not go out of bounds.
+ final int dy = offScreenTop != 0 ? offScreenTop
+ : Math.min(childTop - parentTop, offScreenBottom);
+
+ if (dx != 0 || dy != 0) {
+ if (immediate) {
+ parent.scrollBy(dx, dy);
+ } else {
+ parent.smoothScrollBy(dx, dy);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @deprecated Use {@link #onRequestChildFocus(RecyclerView, State, View, View)}
+ */
+ @Deprecated
+ public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
+ // eat the request if we are in the middle of a scroll or layout
+ return isSmoothScrolling() || parent.isComputingLayout();
+ }
+
+ /**
+ * Called when a descendant view of the RecyclerView requests focus.
+ *
+ * <p>A LayoutManager wishing to keep focused views aligned in a specific
+ * portion of the view may implement that behavior in an override of this method.</p>
+ *
+ * <p>If the LayoutManager executes different behavior that should override the default
+ * behavior of scrolling the focused child on screen instead of running alongside it,
+ * this method should return true.</p>
+ *
+ * @param parent The RecyclerView hosting this LayoutManager
+ * @param state Current state of RecyclerView
+ * @param child Direct child of the RecyclerView containing the newly focused view
+ * @param focused The newly focused view. This may be the same view as child or it may be
+ * null
+ * @return true if the default scroll behavior should be suppressed
+ */
+ public boolean onRequestChildFocus(RecyclerView parent, State state, View child,
+ View focused) {
+ return onRequestChildFocus(parent, child, focused);
+ }
+
+ /**
+ * Called if the RecyclerView this LayoutManager is bound to has a different adapter set.
+ * The LayoutManager may use this opportunity to clear caches and configure state such
+ * that it can relayout appropriately with the new data and potentially new view types.
+ *
+ * <p>The default implementation removes all currently attached views.</p>
+ *
+ * @param oldAdapter The previous adapter instance. Will be null if there was previously no
+ * adapter.
+ * @param newAdapter The new adapter instance. Might be null if
+ * {@link #setAdapter(RecyclerView.Adapter)} is called with {@code null}.
+ */
+ public void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) {
+ }
+
+ /**
+ * Called to populate focusable views within the RecyclerView.
+ *
+ * <p>The LayoutManager implementation should return <code>true</code> if the default
+ * behavior of {@link ViewGroup#addFocusables(java.util.ArrayList, int)} should be
+ * suppressed.</p>
+ *
+ * <p>The default implementation returns <code>false</code> to trigger RecyclerView
+ * to fall back to the default ViewGroup behavior.</p>
+ *
+ * @param recyclerView The RecyclerView hosting this LayoutManager
+ * @param views List of output views. This method should add valid focusable views
+ * to this list.
+ * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+ * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+ * @param focusableMode The type of focusables to be added.
+ *
+ * @return true to suppress the default behavior, false to add default focusables after
+ * this method returns.
+ *
+ * @see #FOCUSABLES_ALL
+ * @see #FOCUSABLES_TOUCH_MODE
+ */
+ public boolean onAddFocusables(RecyclerView recyclerView, ArrayList<View> views,
+ int direction, int focusableMode) {
+ return false;
+ }
+
+ /**
+ * Called when {@link Adapter#notifyDataSetChanged()} is triggered instead of giving
+ * detailed information on what has actually changed.
+ *
+ * @param recyclerView
+ */
+ public void onItemsChanged(RecyclerView recyclerView) {
+ }
+
+ /**
+ * Called when items have been added to the adapter. The LayoutManager may choose to
+ * requestLayout if the inserted items would require refreshing the currently visible set
+ * of child views. (e.g. currently empty space would be filled by appended items, etc.)
+ *
+ * @param recyclerView
+ * @param positionStart
+ * @param itemCount
+ */
+ public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
+ }
+
+ /**
+ * Called when items have been removed from the adapter.
+ *
+ * @param recyclerView
+ * @param positionStart
+ * @param itemCount
+ */
+ public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
+ }
+
+ /**
+ * Called when items have been changed in the adapter.
+ * To receive payload, override {@link #onItemsUpdated(RecyclerView, int, int, Object)}
+ * instead, then this callback will not be invoked.
+ *
+ * @param recyclerView
+ * @param positionStart
+ * @param itemCount
+ */
+ public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
+ }
+
+ /**
+ * Called when items have been changed in the adapter and with optional payload.
+ * Default implementation calls {@link #onItemsUpdated(RecyclerView, int, int)}.
+ *
+ * @param recyclerView
+ * @param positionStart
+ * @param itemCount
+ * @param payload
+ */
+ public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount,
+ Object payload) {
+ onItemsUpdated(recyclerView, positionStart, itemCount);
+ }
+
+ /**
+ * Called when an item is moved withing the adapter.
+ * <p>
+ * Note that, an item may also change position in response to another ADD/REMOVE/MOVE
+ * operation. This callback is only called if and only if {@link Adapter#notifyItemMoved}
+ * is called.
+ *
+ * @param recyclerView
+ * @param from
+ * @param to
+ * @param itemCount
+ */
+ public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
+
+ }
+
+
+ /**
+ * <p>Override this method if you want to support scroll bars.</p>
+ *
+ * <p>Read {@link RecyclerView#computeHorizontalScrollExtent()} for details.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * @param state Current state of RecyclerView
+ * @return The horizontal extent of the scrollbar's thumb
+ * @see RecyclerView#computeHorizontalScrollExtent()
+ */
+ public int computeHorizontalScrollExtent(State state) {
+ return 0;
+ }
+
+ /**
+ * <p>Override this method if you want to support scroll bars.</p>
+ *
+ * <p>Read {@link RecyclerView#computeHorizontalScrollOffset()} for details.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * @param state Current State of RecyclerView where you can find total item count
+ * @return The horizontal offset of the scrollbar's thumb
+ * @see RecyclerView#computeHorizontalScrollOffset()
+ */
+ public int computeHorizontalScrollOffset(State state) {
+ return 0;
+ }
+
+ /**
+ * <p>Override this method if you want to support scroll bars.</p>
+ *
+ * <p>Read {@link RecyclerView#computeHorizontalScrollRange()} for details.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * @param state Current State of RecyclerView where you can find total item count
+ * @return The total horizontal range represented by the vertical scrollbar
+ * @see RecyclerView#computeHorizontalScrollRange()
+ */
+ public int computeHorizontalScrollRange(State state) {
+ return 0;
+ }
+
+ /**
+ * <p>Override this method if you want to support scroll bars.</p>
+ *
+ * <p>Read {@link RecyclerView#computeVerticalScrollExtent()} for details.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * @param state Current state of RecyclerView
+ * @return The vertical extent of the scrollbar's thumb
+ * @see RecyclerView#computeVerticalScrollExtent()
+ */
+ public int computeVerticalScrollExtent(State state) {
+ return 0;
+ }
+
+ /**
+ * <p>Override this method if you want to support scroll bars.</p>
+ *
+ * <p>Read {@link RecyclerView#computeVerticalScrollOffset()} for details.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * @param state Current State of RecyclerView where you can find total item count
+ * @return The vertical offset of the scrollbar's thumb
+ * @see RecyclerView#computeVerticalScrollOffset()
+ */
+ public int computeVerticalScrollOffset(State state) {
+ return 0;
+ }
+
+ /**
+ * <p>Override this method if you want to support scroll bars.</p>
+ *
+ * <p>Read {@link RecyclerView#computeVerticalScrollRange()} for details.</p>
+ *
+ * <p>Default implementation returns 0.</p>
+ *
+ * @param state Current State of RecyclerView where you can find total item count
+ * @return The total vertical range represented by the vertical scrollbar
+ * @see RecyclerView#computeVerticalScrollRange()
+ */
+ public int computeVerticalScrollRange(State state) {
+ return 0;
+ }
+
+ /**
+ * Measure the attached RecyclerView. Implementations must call
+ * {@link #setMeasuredDimension(int, int)} before returning.
+ *
+ * <p>The default implementation will handle EXACTLY measurements and respect
+ * the minimum width and height properties of the host RecyclerView if measured
+ * as UNSPECIFIED. AT_MOST measurements will be treated as EXACTLY and the RecyclerView
+ * will consume all available space.</p>
+ *
+ * @param recycler Recycler
+ * @param state Transient state of RecyclerView
+ * @param widthSpec Width {@link android.view.View.MeasureSpec}
+ * @param heightSpec Height {@link android.view.View.MeasureSpec}
+ */
+ public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
+ mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
+ }
+
+ /**
+ * {@link View#setMeasuredDimension(int, int) Set the measured dimensions} of the
+ * host RecyclerView.
+ *
+ * @param widthSize Measured width
+ * @param heightSize Measured height
+ */
+ public void setMeasuredDimension(int widthSize, int heightSize) {
+ mRecyclerView.setMeasuredDimension(widthSize, heightSize);
+ }
+
+ /**
+ * @return The host RecyclerView's {@link View#getMinimumWidth()}
+ */
+ public int getMinimumWidth() {
+ return mRecyclerView.getMinimumWidth();
+ }
+
+ /**
+ * @return The host RecyclerView's {@link View#getMinimumHeight()}
+ */
+ public int getMinimumHeight() {
+ return mRecyclerView.getMinimumHeight();
+ }
+ /**
+ * <p>Called when the LayoutManager should save its state. This is a good time to save your
+ * scroll position, configuration and anything else that may be required to restore the same
+ * layout state if the LayoutManager is recreated.</p>
+ * <p>RecyclerView does NOT verify if the LayoutManager has changed between state save and
+ * restore. This will let you share information between your LayoutManagers but it is also
+ * your responsibility to make sure they use the same parcelable class.</p>
+ *
+ * @return Necessary information for LayoutManager to be able to restore its state
+ */
+ public Parcelable onSaveInstanceState() {
+ return null;
+ }
+
+
+ public void onRestoreInstanceState(Parcelable state) {
+
+ }
+
+ void stopSmoothScroller() {
+ if (mSmoothScroller != null) {
+ mSmoothScroller.stop();
+ }
+ }
+
+ private void onSmoothScrollerStopped(SmoothScroller smoothScroller) {
+ if (mSmoothScroller == smoothScroller) {
+ mSmoothScroller = null;
+ }
+ }
+
+ /**
+ * RecyclerView calls this method to notify LayoutManager that scroll state has changed.
+ *
+ * @param state The new scroll state for RecyclerView
+ */
+ public void onScrollStateChanged(int state) {
+ }
+
+ /**
+ * Removes all views and recycles them using the given recycler.
+ * <p>
+ * If you want to clean cached views as well, you should call {@link Recycler#clear()} too.
+ * <p>
+ * If a View is marked as "ignored", it is not removed nor recycled.
+ *
+ * @param recycler Recycler to use to recycle children
+ * @see #removeAndRecycleView(View, Recycler)
+ * @see #removeAndRecycleViewAt(int, Recycler)
+ * @see #ignoreView(View)
+ */
+ public void removeAndRecycleAllViews(Recycler recycler) {
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ final View view = getChildAt(i);
+ if (!getChildViewHolderInt(view).shouldIgnore()) {
+ removeAndRecycleViewAt(i, recycler);
+ }
+ }
+ }
+
+ // called by accessibility delegate
+ void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ onInitializeAccessibilityNodeInfo(mRecyclerView.mRecycler, mRecyclerView.mState, info);
+ }
+
+ /**
+ * Called by the AccessibilityDelegate when the information about the current layout should
+ * be populated.
+ * <p>
+ * Default implementation adds a {@link
+ * android.view.accessibility.AccessibilityNodeInfo.CollectionInfo}.
+ * <p>
+ * You should override
+ * {@link #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)},
+ * {@link #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)},
+ * {@link #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State)} and
+ * {@link #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State)} for
+ * more accurate accessibility information.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @param info The info that should be filled by the LayoutManager
+ * @see View#onInitializeAccessibilityNodeInfo(
+ *android.view.accessibility.AccessibilityNodeInfo)
+ * @see #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)
+ * @see #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)
+ * @see #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State)
+ * @see #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State)
+ */
+ public void onInitializeAccessibilityNodeInfo(Recycler recycler, State state,
+ AccessibilityNodeInfo info) {
+ if (mRecyclerView.canScrollVertically(-1)
+ || mRecyclerView.canScrollHorizontally(-1)) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ info.setScrollable(true);
+ }
+ if (mRecyclerView.canScrollVertically(1)
+ || mRecyclerView.canScrollHorizontally(1)) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ info.setScrollable(true);
+ }
+ final AccessibilityNodeInfo.CollectionInfo collectionInfo =
+ AccessibilityNodeInfo.CollectionInfo
+ .obtain(getRowCountForAccessibility(recycler, state),
+ getColumnCountForAccessibility(recycler, state),
+ isLayoutHierarchical(recycler, state),
+ getSelectionModeForAccessibility(recycler, state));
+ info.setCollectionInfo(collectionInfo);
+ }
+
+ // called by accessibility delegate
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ onInitializeAccessibilityEvent(mRecyclerView.mRecycler, mRecyclerView.mState, event);
+ }
+
+ /**
+ * Called by the accessibility delegate to initialize an accessibility event.
+ * <p>
+ * Default implementation adds item count and scroll information to the event.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @param event The event instance to initialize
+ * @see View#onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent)
+ */
+ public void onInitializeAccessibilityEvent(Recycler recycler, State state,
+ AccessibilityEvent event) {
+ if (mRecyclerView == null || event == null) {
+ return;
+ }
+ event.setScrollable(mRecyclerView.canScrollVertically(1)
+ || mRecyclerView.canScrollVertically(-1)
+ || mRecyclerView.canScrollHorizontally(-1)
+ || mRecyclerView.canScrollHorizontally(1));
+
+ if (mRecyclerView.mAdapter != null) {
+ event.setItemCount(mRecyclerView.mAdapter.getItemCount());
+ }
+ }
+
+ // called by accessibility delegate
+ void onInitializeAccessibilityNodeInfoForItem(View host, AccessibilityNodeInfo info) {
+ final ViewHolder vh = getChildViewHolderInt(host);
+ // avoid trying to create accessibility node info for removed children
+ if (vh != null && !vh.isRemoved() && !mChildHelper.isHidden(vh.itemView)) {
+ onInitializeAccessibilityNodeInfoForItem(mRecyclerView.mRecycler,
+ mRecyclerView.mState, host, info);
+ }
+ }
+
+ /**
+ * Called by the AccessibilityDelegate when the accessibility information for a specific
+ * item should be populated.
+ * <p>
+ * Default implementation adds basic positioning information about the item.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @param host The child for which accessibility node info should be populated
+ * @param info The info to fill out about the item
+ * @see android.widget.AbsListView#onInitializeAccessibilityNodeInfoForItem(View, int,
+ * android.view.accessibility.AccessibilityNodeInfo)
+ */
+ public void onInitializeAccessibilityNodeInfoForItem(Recycler recycler, State state,
+ View host, AccessibilityNodeInfo info) {
+ int rowIndexGuess = canScrollVertically() ? getPosition(host) : 0;
+ int columnIndexGuess = canScrollHorizontally() ? getPosition(host) : 0;
+ final AccessibilityNodeInfo.CollectionItemInfo itemInfo =
+ AccessibilityNodeInfo.CollectionItemInfo.obtain(rowIndexGuess, 1,
+ columnIndexGuess, 1, false, false);
+ info.setCollectionItemInfo(itemInfo);
+ }
+
+ /**
+ * A LayoutManager can call this method to force RecyclerView to run simple animations in
+ * the next layout pass, even if there is not any trigger to do so. (e.g. adapter data
+ * change).
+ * <p>
+ * Note that, calling this method will not guarantee that RecyclerView will run animations
+ * at all. For example, if there is not any {@link ItemAnimator} set, RecyclerView will
+ * not run any animations but will still clear this flag after the layout is complete.
+ *
+ */
+ public void requestSimpleAnimationsInNextLayout() {
+ mRequestedSimpleAnimations = true;
+ }
+
+ /**
+ * Returns the selection mode for accessibility. Should be
+ * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_NONE},
+ * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_SINGLE} or
+ * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_MULTIPLE}.
+ * <p>
+ * Default implementation returns
+ * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_NONE}.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @return Selection mode for accessibility. Default implementation returns
+ * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_NONE}.
+ */
+ public int getSelectionModeForAccessibility(Recycler recycler, State state) {
+ return AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE;
+ }
+
+ /**
+ * Returns the number of rows for accessibility.
+ * <p>
+ * Default implementation returns the number of items in the adapter if LayoutManager
+ * supports vertical scrolling or 1 if LayoutManager does not support vertical
+ * scrolling.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @return The number of rows in LayoutManager for accessibility.
+ */
+ public int getRowCountForAccessibility(Recycler recycler, State state) {
+ if (mRecyclerView == null || mRecyclerView.mAdapter == null) {
+ return 1;
+ }
+ return canScrollVertically() ? mRecyclerView.mAdapter.getItemCount() : 1;
+ }
+
+ /**
+ * Returns the number of columns for accessibility.
+ * <p>
+ * Default implementation returns the number of items in the adapter if LayoutManager
+ * supports horizontal scrolling or 1 if LayoutManager does not support horizontal
+ * scrolling.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @return The number of rows in LayoutManager for accessibility.
+ */
+ public int getColumnCountForAccessibility(Recycler recycler, State state) {
+ if (mRecyclerView == null || mRecyclerView.mAdapter == null) {
+ return 1;
+ }
+ return canScrollHorizontally() ? mRecyclerView.mAdapter.getItemCount() : 1;
+ }
+
+ /**
+ * Returns whether layout is hierarchical or not to be used for accessibility.
+ * <p>
+ * Default implementation returns false.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @return True if layout is hierarchical.
+ */
+ public boolean isLayoutHierarchical(Recycler recycler, State state) {
+ return false;
+ }
+
+ // called by accessibility delegate
+ boolean performAccessibilityAction(int action, Bundle args) {
+ return performAccessibilityAction(mRecyclerView.mRecycler, mRecyclerView.mState,
+ action, args);
+ }
+
+ /**
+ * Called by AccessibilityDelegate when an action is requested from the RecyclerView.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @param action The action to perform
+ * @param args Optional action arguments
+ * @see View#performAccessibilityAction(int, android.os.Bundle)
+ */
+ public boolean performAccessibilityAction(Recycler recycler, State state, int action,
+ Bundle args) {
+ if (mRecyclerView == null) {
+ return false;
+ }
+ int vScroll = 0, hScroll = 0;
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
+ if (mRecyclerView.canScrollVertically(-1)) {
+ vScroll = -(getHeight() - getPaddingTop() - getPaddingBottom());
+ }
+ if (mRecyclerView.canScrollHorizontally(-1)) {
+ hScroll = -(getWidth() - getPaddingLeft() - getPaddingRight());
+ }
+ break;
+ case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
+ if (mRecyclerView.canScrollVertically(1)) {
+ vScroll = getHeight() - getPaddingTop() - getPaddingBottom();
+ }
+ if (mRecyclerView.canScrollHorizontally(1)) {
+ hScroll = getWidth() - getPaddingLeft() - getPaddingRight();
+ }
+ break;
+ }
+ if (vScroll == 0 && hScroll == 0) {
+ return false;
+ }
+ mRecyclerView.scrollBy(hScroll, vScroll);
+ return true;
+ }
+
+ // called by accessibility delegate
+ boolean performAccessibilityActionForItem(View view, int action, Bundle args) {
+ return performAccessibilityActionForItem(mRecyclerView.mRecycler, mRecyclerView.mState,
+ view, action, args);
+ }
+
+ /**
+ * Called by AccessibilityDelegate when an accessibility action is requested on one of the
+ * children of LayoutManager.
+ * <p>
+ * Default implementation does not do anything.
+ *
+ * @param recycler The Recycler that can be used to convert view positions into adapter
+ * positions
+ * @param state The current state of RecyclerView
+ * @param view The child view on which the action is performed
+ * @param action The action to perform
+ * @param args Optional action arguments
+ * @return true if action is handled
+ * @see View#performAccessibilityAction(int, android.os.Bundle)
+ */
+ public boolean performAccessibilityActionForItem(Recycler recycler, State state, View view,
+ int action, Bundle args) {
+ return false;
+ }
+
+ /**
+ * Parse the xml attributes to get the most common properties used by layout managers.
+ *
+ * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation
+ * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount
+ * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout
+ * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd
+ *
+ * @return an object containing the properties as specified in the attrs.
+ */
+ public static Properties getProperties(Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ Properties properties = new Properties();
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
+ defStyleAttr, defStyleRes);
+ properties.orientation = a.getInt(R.styleable.RecyclerView_orientation, VERTICAL);
+ properties.spanCount = a.getInt(R.styleable.RecyclerView_spanCount, 1);
+ properties.reverseLayout = a.getBoolean(R.styleable.RecyclerView_reverseLayout, false);
+ properties.stackFromEnd = a.getBoolean(R.styleable.RecyclerView_stackFromEnd, false);
+ a.recycle();
+ return properties;
+ }
+
+ void setExactMeasureSpecsFrom(RecyclerView recyclerView) {
+ setMeasureSpecs(
+ MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY)
+ );
+ }
+
+ /**
+ * Internal API to allow LayoutManagers to be measured twice.
+ * <p>
+ * This is not public because LayoutManagers should be able to handle their layouts in one
+ * pass but it is very convenient to make existing LayoutManagers support wrapping content
+ * when both orientations are undefined.
+ * <p>
+ * This API will be removed after default LayoutManagers properly implement wrap content in
+ * non-scroll orientation.
+ */
+ boolean shouldMeasureTwice() {
+ return false;
+ }
+
+ boolean hasFlexibleChildInBothOrientations() {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final ViewGroup.LayoutParams lp = child.getLayoutParams();
+ if (lp.width < 0 && lp.height < 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Some general properties that a LayoutManager may want to use.
+ */
+ public static class Properties {
+ /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation */
+ public int orientation;
+ /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount */
+ public int spanCount;
+ /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout */
+ public boolean reverseLayout;
+ /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd */
+ public boolean stackFromEnd;
+ }
+ }
+
+ /**
+ * An ItemDecoration allows the application to add a special drawing and layout offset
+ * to specific item views from the adapter's data set. This can be useful for drawing dividers
+ * between items, highlights, visual grouping boundaries and more.
+ *
+ * <p>All ItemDecorations are drawn in the order they were added, before the item
+ * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()}
+ * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView,
+ * RecyclerView.State)}.</p>
+ */
+ public abstract static class ItemDecoration {
+ /**
+ * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
+ * Any content drawn by this method will be drawn before the item views are drawn,
+ * and will thus appear underneath the views.
+ *
+ * @param c Canvas to draw into
+ * @param parent RecyclerView this ItemDecoration is drawing into
+ * @param state The current state of RecyclerView
+ */
+ public void onDraw(Canvas c, RecyclerView parent, State state) {
+ onDraw(c, parent);
+ }
+
+ /**
+ * @deprecated
+ * Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)}
+ */
+ @Deprecated
+ public void onDraw(Canvas c, RecyclerView parent) {
+ }
+
+ /**
+ * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
+ * Any content drawn by this method will be drawn after the item views are drawn
+ * and will thus appear over the views.
+ *
+ * @param c Canvas to draw into
+ * @param parent RecyclerView this ItemDecoration is drawing into
+ * @param state The current state of RecyclerView.
+ */
+ public void onDrawOver(Canvas c, RecyclerView parent, State state) {
+ onDrawOver(c, parent);
+ }
+
+ /**
+ * @deprecated
+ * Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)}
+ */
+ @Deprecated
+ public void onDrawOver(Canvas c, RecyclerView parent) {
+ }
+
+
+ /**
+ * @deprecated
+ * Use {@link #getItemOffsets(Rect, View, RecyclerView, State)}
+ */
+ @Deprecated
+ public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
+ outRect.set(0, 0, 0, 0);
+ }
+
+ /**
+ * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
+ * the number of pixels that the item view should be inset by, similar to padding or margin.
+ * The default implementation sets the bounds of outRect to 0 and returns.
+ *
+ * <p>
+ * If this ItemDecoration does not affect the positioning of item views, it should set
+ * all four fields of <code>outRect</code> (left, top, right, bottom) to zero
+ * before returning.
+ *
+ * <p>
+ * If you need to access Adapter for additional data, you can call
+ * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the
+ * View.
+ *
+ * @param outRect Rect to receive the output.
+ * @param view The child view to decorate
+ * @param parent RecyclerView this ItemDecoration is decorating
+ * @param state The current state of RecyclerView.
+ */
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
+ getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
+ parent);
+ }
+ }
+
+ /**
+ * An OnItemTouchListener allows the application to intercept touch events in progress at the
+ * view hierarchy level of the RecyclerView before those touch events are considered for
+ * RecyclerView's own scrolling behavior.
+ *
+ * <p>This can be useful for applications that wish to implement various forms of gestural
+ * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept
+ * a touch interaction already in progress even if the RecyclerView is already handling that
+ * gesture stream itself for the purposes of scrolling.</p>
+ *
+ * @see SimpleOnItemTouchListener
+ */
+ public interface OnItemTouchListener {
+ /**
+ * Silently observe and/or take over touch events sent to the RecyclerView
+ * before they are handled by either the RecyclerView itself or its child views.
+ *
+ * <p>The onInterceptTouchEvent methods of each attached OnItemTouchListener will be run
+ * in the order in which each listener was added, before any other touch processing
+ * by the RecyclerView itself or child views occurs.</p>
+ *
+ * @param e MotionEvent describing the touch event. All coordinates are in
+ * the RecyclerView's coordinate system.
+ * @return true if this OnItemTouchListener wishes to begin intercepting touch events, false
+ * to continue with the current behavior and continue observing future events in
+ * the gesture.
+ */
+ boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e);
+
+ /**
+ * Process a touch event as part of a gesture that was claimed by returning true from
+ * a previous call to {@link #onInterceptTouchEvent}.
+ *
+ * @param e MotionEvent describing the touch event. All coordinates are in
+ * the RecyclerView's coordinate system.
+ */
+ void onTouchEvent(RecyclerView rv, MotionEvent e);
+
+ /**
+ * Called when a child of RecyclerView does not want RecyclerView and its ancestors to
+ * intercept touch events with
+ * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
+ *
+ * @param disallowIntercept True if the child does not want the parent to
+ * intercept touch events.
+ * @see ViewParent#requestDisallowInterceptTouchEvent(boolean)
+ */
+ void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept);
+ }
+
+ /**
+ * An implementation of {@link RecyclerView.OnItemTouchListener} that has empty method bodies
+ * and default return values.
+ * <p>
+ * You may prefer to extend this class if you don't need to override all methods. Another
+ * benefit of using this class is future compatibility. As the interface may change, we'll
+ * always provide a default implementation on this class so that your code won't break when
+ * you update to a new version of the support library.
+ */
+ public static class SimpleOnItemTouchListener implements RecyclerView.OnItemTouchListener {
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+ }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ }
+ }
+
+
+ /**
+ * An OnScrollListener can be added to a RecyclerView to receive messages when a scrolling event
+ * has occurred on that RecyclerView.
+ * <p>
+ * @see RecyclerView#addOnScrollListener(OnScrollListener)
+ * @see RecyclerView#clearOnChildAttachStateChangeListeners()
+ *
+ */
+ public abstract static class OnScrollListener {
+ /**
+ * Callback method to be invoked when RecyclerView's scroll state changes.
+ *
+ * @param recyclerView The RecyclerView whose scroll state has changed.
+ * @param newState The updated scroll state. One of {@link #SCROLL_STATE_IDLE},
+ * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}.
+ */
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState){}
+
+ /**
+ * Callback method to be invoked when the RecyclerView has been scrolled. This will be
+ * called after the scroll has completed.
+ * <p>
+ * This callback will also be called if visible item range changes after a layout
+ * calculation. In that case, dx and dy will be 0.
+ *
+ * @param recyclerView The RecyclerView which scrolled.
+ * @param dx The amount of horizontal scroll.
+ * @param dy The amount of vertical scroll.
+ */
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy){}
+ }
+
+ /**
+ * A RecyclerListener can be set on a RecyclerView to receive messages whenever
+ * a view is recycled.
+ *
+ * @see RecyclerView#setRecyclerListener(RecyclerListener)
+ */
+ public interface RecyclerListener {
+
+ /**
+ * This method is called whenever the view in the ViewHolder is recycled.
+ *
+ * RecyclerView calls this method right before clearing ViewHolder's internal data and
+ * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information
+ * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get
+ * its adapter position.
+ *
+ * @param holder The ViewHolder containing the view that was recycled
+ */
+ void onViewRecycled(ViewHolder holder);
+ }
+
+ /**
+ * A Listener interface that can be attached to a RecylcerView to get notified
+ * whenever a ViewHolder is attached to or detached from RecyclerView.
+ */
+ public interface OnChildAttachStateChangeListener {
+
+ /**
+ * Called when a view is attached to the RecyclerView.
+ *
+ * @param view The View which is attached to the RecyclerView
+ */
+ void onChildViewAttachedToWindow(View view);
+
+ /**
+ * Called when a view is detached from RecyclerView.
+ *
+ * @param view The View which is being detached from the RecyclerView
+ */
+ void onChildViewDetachedFromWindow(View view);
+ }
+
+ /**
+ * A ViewHolder describes an item view and metadata about its place within the RecyclerView.
+ *
+ * <p>{@link Adapter} implementations should subclass ViewHolder and add fields for caching
+ * potentially expensive {@link View#findViewById(int)} results.</p>
+ *
+ * <p>While {@link LayoutParams} belong to the {@link LayoutManager},
+ * {@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use
+ * their own custom ViewHolder implementations to store data that makes binding view contents
+ * easier. Implementations should assume that individual item views will hold strong references
+ * to <code>ViewHolder</code> objects and that <code>RecyclerView</code> instances may hold
+ * strong references to extra off-screen item views for caching purposes</p>
+ */
+ public abstract static class ViewHolder {
+ public final View itemView;
+ WeakReference<RecyclerView> mNestedRecyclerView;
+ int mPosition = NO_POSITION;
+ int mOldPosition = NO_POSITION;
+ long mItemId = NO_ID;
+ int mItemViewType = INVALID_TYPE;
+ int mPreLayoutPosition = NO_POSITION;
+
+ // The item that this holder is shadowing during an item change event/animation
+ ViewHolder mShadowedHolder = null;
+ // The item that is shadowing this holder during an item change event/animation
+ ViewHolder mShadowingHolder = null;
+
+ /**
+ * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType
+ * are all valid.
+ */
+ static final int FLAG_BOUND = 1 << 0;
+
+ /**
+ * The data this ViewHolder's view reflects is stale and needs to be rebound
+ * by the adapter. mPosition and mItemId are consistent.
+ */
+ static final int FLAG_UPDATE = 1 << 1;
+
+ /**
+ * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId
+ * are not to be trusted and may no longer match the item view type.
+ * This ViewHolder must be fully rebound to different data.
+ */
+ static final int FLAG_INVALID = 1 << 2;
+
+ /**
+ * This ViewHolder points at data that represents an item previously removed from the
+ * data set. Its view may still be used for things like outgoing animations.
+ */
+ static final int FLAG_REMOVED = 1 << 3;
+
+ /**
+ * This ViewHolder should not be recycled. This flag is set via setIsRecyclable()
+ * and is intended to keep views around during animations.
+ */
+ static final int FLAG_NOT_RECYCLABLE = 1 << 4;
+
+ /**
+ * This ViewHolder is returned from scrap which means we are expecting an addView call
+ * for this itemView. When returned from scrap, ViewHolder stays in the scrap list until
+ * the end of the layout pass and then recycled by RecyclerView if it is not added back to
+ * the RecyclerView.
+ */
+ static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5;
+
+ /**
+ * This ViewHolder is fully managed by the LayoutManager. We do not scrap, recycle or remove
+ * it unless LayoutManager is replaced.
+ * It is still fully visible to the LayoutManager.
+ */
+ static final int FLAG_IGNORE = 1 << 7;
+
+ /**
+ * When the View is detached form the parent, we set this flag so that we can take correct
+ * action when we need to remove it or add it back.
+ */
+ static final int FLAG_TMP_DETACHED = 1 << 8;
+
+ /**
+ * Set when we can no longer determine the adapter position of this ViewHolder until it is
+ * rebound to a new position. It is different than FLAG_INVALID because FLAG_INVALID is
+ * set even when the type does not match. Also, FLAG_ADAPTER_POSITION_UNKNOWN is set as soon
+ * as adapter notification arrives vs FLAG_INVALID is set lazily before layout is
+ * re-calculated.
+ */
+ static final int FLAG_ADAPTER_POSITION_UNKNOWN = 1 << 9;
+
+ /**
+ * Set when a addChangePayload(null) is called
+ */
+ static final int FLAG_ADAPTER_FULLUPDATE = 1 << 10;
+
+ /**
+ * Used by ItemAnimator when a ViewHolder's position changes
+ */
+ static final int FLAG_MOVED = 1 << 11;
+
+ /**
+ * Used by ItemAnimator when a ViewHolder appears in pre-layout
+ */
+ static final int FLAG_APPEARED_IN_PRE_LAYOUT = 1 << 12;
+
+ static final int PENDING_ACCESSIBILITY_STATE_NOT_SET = -1;
+
+ /**
+ * Used when a ViewHolder starts the layout pass as a hidden ViewHolder but is re-used from
+ * hidden list (as if it was scrap) without being recycled in between.
+ *
+ * When a ViewHolder is hidden, there are 2 paths it can be re-used:
+ * a) Animation ends, view is recycled and used from the recycle pool.
+ * b) LayoutManager asks for the View for that position while the ViewHolder is hidden.
+ *
+ * This flag is used to represent "case b" where the ViewHolder is reused without being
+ * recycled (thus "bounced" from the hidden list). This state requires special handling
+ * because the ViewHolder must be added to pre layout maps for animations as if it was
+ * already there.
+ */
+ static final int FLAG_BOUNCED_FROM_HIDDEN_LIST = 1 << 13;
+
+ private int mFlags;
+
+ private static final List<Object> FULLUPDATE_PAYLOADS = Collections.EMPTY_LIST;
+
+ List<Object> mPayloads = null;
+ List<Object> mUnmodifiedPayloads = null;
+
+ private int mIsRecyclableCount = 0;
+
+ // If non-null, view is currently considered scrap and may be reused for other data by the
+ // scrap container.
+ private Recycler mScrapContainer = null;
+ // Keeps whether this ViewHolder lives in Change scrap or Attached scrap
+ private boolean mInChangeScrap = false;
+
+ // Saves isImportantForAccessibility value for the view item while it's in hidden state and
+ // marked as unimportant for accessibility.
+ private int mWasImportantForAccessibilityBeforeHidden =
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+ // set if we defer the accessibility state change of the view holder
+ @VisibleForTesting
+ int mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET;
+
+ /**
+ * Is set when VH is bound from the adapter and cleaned right before it is sent to
+ * {@link RecycledViewPool}.
+ */
+ RecyclerView mOwnerRecyclerView;
+
+ public ViewHolder(View itemView) {
+ if (itemView == null) {
+ throw new IllegalArgumentException("itemView may not be null");
+ }
+ this.itemView = itemView;
+ }
+
+ void flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout) {
+ addFlags(ViewHolder.FLAG_REMOVED);
+ offsetPosition(offset, applyToPreLayout);
+ mPosition = mNewPosition;
+ }
+
+ void offsetPosition(int offset, boolean applyToPreLayout) {
+ if (mOldPosition == NO_POSITION) {
+ mOldPosition = mPosition;
+ }
+ if (mPreLayoutPosition == NO_POSITION) {
+ mPreLayoutPosition = mPosition;
+ }
+ if (applyToPreLayout) {
+ mPreLayoutPosition += offset;
+ }
+ mPosition += offset;
+ if (itemView.getLayoutParams() != null) {
+ ((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true;
+ }
+ }
+
+ void clearOldPosition() {
+ mOldPosition = NO_POSITION;
+ mPreLayoutPosition = NO_POSITION;
+ }
+
+ void saveOldPosition() {
+ if (mOldPosition == NO_POSITION) {
+ mOldPosition = mPosition;
+ }
+ }
+
+ boolean shouldIgnore() {
+ return (mFlags & FLAG_IGNORE) != 0;
+ }
+
+ /**
+ * @deprecated This method is deprecated because its meaning is ambiguous due to the async
+ * handling of adapter updates. Please use {@link #getLayoutPosition()} or
+ * {@link #getAdapterPosition()} depending on your use case.
+ *
+ * @see #getLayoutPosition()
+ * @see #getAdapterPosition()
+ */
+ @Deprecated
+ public final int getPosition() {
+ return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
+ }
+
+ /**
+ * Returns the position of the ViewHolder in terms of the latest layout pass.
+ * <p>
+ * This position is mostly used by RecyclerView components to be consistent while
+ * RecyclerView lazily processes adapter updates.
+ * <p>
+ * For performance and animation reasons, RecyclerView batches all adapter updates until the
+ * next layout pass. This may cause mismatches between the Adapter position of the item and
+ * the position it had in the latest layout calculations.
+ * <p>
+ * LayoutManagers should always call this method while doing calculations based on item
+ * positions. All methods in {@link RecyclerView.LayoutManager}, {@link RecyclerView.State},
+ * {@link RecyclerView.Recycler} that receive a position expect it to be the layout position
+ * of the item.
+ * <p>
+ * If LayoutManager needs to call an external method that requires the adapter position of
+ * the item, it can use {@link #getAdapterPosition()} or
+ * {@link RecyclerView.Recycler#convertPreLayoutPositionToPostLayout(int)}.
+ *
+ * @return Returns the adapter position of the ViewHolder in the latest layout pass.
+ * @see #getAdapterPosition()
+ */
+ public final int getLayoutPosition() {
+ return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
+ }
+
+ /**
+ * Returns the Adapter position of the item represented by this ViewHolder.
+ * <p>
+ * Note that this might be different than the {@link #getLayoutPosition()} if there are
+ * pending adapter updates but a new layout pass has not happened yet.
+ * <p>
+ * RecyclerView does not handle any adapter updates until the next layout traversal. This
+ * may create temporary inconsistencies between what user sees on the screen and what
+ * adapter contents have. This inconsistency is not important since it will be less than
+ * 16ms but it might be a problem if you want to use ViewHolder position to access the
+ * adapter. Sometimes, you may need to get the exact adapter position to do
+ * some actions in response to user events. In that case, you should use this method which
+ * will calculate the Adapter position of the ViewHolder.
+ * <p>
+ * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the
+ * next layout pass, the return value of this method will be {@link #NO_POSITION}.
+ *
+ * @return The adapter position of the item if it still exists in the adapter.
+ * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter,
+ * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last
+ * layout pass or the ViewHolder has already been recycled.
+ */
+ public final int getAdapterPosition() {
+ if (mOwnerRecyclerView == null) {
+ return NO_POSITION;
+ }
+ return mOwnerRecyclerView.getAdapterPositionFor(this);
+ }
+
+ /**
+ * When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders
+ * to perform animations.
+ * <p>
+ * If a ViewHolder was laid out in the previous onLayout call, old position will keep its
+ * adapter index in the previous layout.
+ *
+ * @return The previous adapter index of the Item represented by this ViewHolder or
+ * {@link #NO_POSITION} if old position does not exists or cleared (pre-layout is
+ * complete).
+ */
+ public final int getOldPosition() {
+ return mOldPosition;
+ }
+
+ /**
+ * Returns The itemId represented by this ViewHolder.
+ *
+ * @return The item's id if adapter has stable ids, {@link RecyclerView#NO_ID}
+ * otherwise
+ */
+ public final long getItemId() {
+ return mItemId;
+ }
+
+ /**
+ * @return The view type of this ViewHolder.
+ */
+ public final int getItemViewType() {
+ return mItemViewType;
+ }
+
+ boolean isScrap() {
+ return mScrapContainer != null;
+ }
+
+ void unScrap() {
+ mScrapContainer.unscrapView(this);
+ }
+
+ boolean wasReturnedFromScrap() {
+ return (mFlags & FLAG_RETURNED_FROM_SCRAP) != 0;
+ }
+
+ void clearReturnedFromScrapFlag() {
+ mFlags = mFlags & ~FLAG_RETURNED_FROM_SCRAP;
+ }
+
+ void clearTmpDetachFlag() {
+ mFlags = mFlags & ~FLAG_TMP_DETACHED;
+ }
+
+ void stopIgnoring() {
+ mFlags = mFlags & ~FLAG_IGNORE;
+ }
+
+ void setScrapContainer(Recycler recycler, boolean isChangeScrap) {
+ mScrapContainer = recycler;
+ mInChangeScrap = isChangeScrap;
+ }
+
+ boolean isInvalid() {
+ return (mFlags & FLAG_INVALID) != 0;
+ }
+
+ boolean needsUpdate() {
+ return (mFlags & FLAG_UPDATE) != 0;
+ }
+
+ boolean isBound() {
+ return (mFlags & FLAG_BOUND) != 0;
+ }
+
+ boolean isRemoved() {
+ return (mFlags & FLAG_REMOVED) != 0;
+ }
+
+ boolean hasAnyOfTheFlags(int flags) {
+ return (mFlags & flags) != 0;
+ }
+
+ boolean isTmpDetached() {
+ return (mFlags & FLAG_TMP_DETACHED) != 0;
+ }
+
+ boolean isAdapterPositionUnknown() {
+ return (mFlags & FLAG_ADAPTER_POSITION_UNKNOWN) != 0 || isInvalid();
+ }
+
+ void setFlags(int flags, int mask) {
+ mFlags = (mFlags & ~mask) | (flags & mask);
+ }
+
+ void addFlags(int flags) {
+ mFlags |= flags;
+ }
+
+ void addChangePayload(Object payload) {
+ if (payload == null) {
+ addFlags(FLAG_ADAPTER_FULLUPDATE);
+ } else if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) {
+ createPayloadsIfNeeded();
+ mPayloads.add(payload);
+ }
+ }
+
+ private void createPayloadsIfNeeded() {
+ if (mPayloads == null) {
+ mPayloads = new ArrayList<Object>();
+ mUnmodifiedPayloads = Collections.unmodifiableList(mPayloads);
+ }
+ }
+
+ void clearPayload() {
+ if (mPayloads != null) {
+ mPayloads.clear();
+ }
+ mFlags = mFlags & ~FLAG_ADAPTER_FULLUPDATE;
+ }
+
+ List<Object> getUnmodifiedPayloads() {
+ if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) {
+ if (mPayloads == null || mPayloads.size() == 0) {
+ // Initial state, no update being called.
+ return FULLUPDATE_PAYLOADS;
+ }
+ // there are none-null payloads
+ return mUnmodifiedPayloads;
+ } else {
+ // a full update has been called.
+ return FULLUPDATE_PAYLOADS;
+ }
+ }
+
+ void resetInternal() {
+ mFlags = 0;
+ mPosition = NO_POSITION;
+ mOldPosition = NO_POSITION;
+ mItemId = NO_ID;
+ mPreLayoutPosition = NO_POSITION;
+ mIsRecyclableCount = 0;
+ mShadowedHolder = null;
+ mShadowingHolder = null;
+ clearPayload();
+ mWasImportantForAccessibilityBeforeHidden = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+ mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET;
+ clearNestedRecyclerViewIfNotNested(this);
+ }
+
+ /**
+ * Called when the child view enters the hidden state
+ */
+ private void onEnteredHiddenState(RecyclerView parent) {
+ // While the view item is in hidden state, make it invisible for the accessibility.
+ mWasImportantForAccessibilityBeforeHidden =
+ itemView.getImportantForAccessibility();
+ parent.setChildImportantForAccessibilityInternal(this,
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+ }
+
+ /**
+ * Called when the child view leaves the hidden state
+ */
+ private void onLeftHiddenState(RecyclerView parent) {
+ parent.setChildImportantForAccessibilityInternal(this,
+ mWasImportantForAccessibilityBeforeHidden);
+ mWasImportantForAccessibilityBeforeHidden = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ViewHolder{"
+ + Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId
+ + ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition);
+ if (isScrap()) {
+ sb.append(" scrap ")
+ .append(mInChangeScrap ? "[changeScrap]" : "[attachedScrap]");
+ }
+ if (isInvalid()) sb.append(" invalid");
+ if (!isBound()) sb.append(" unbound");
+ if (needsUpdate()) sb.append(" update");
+ if (isRemoved()) sb.append(" removed");
+ if (shouldIgnore()) sb.append(" ignored");
+ if (isTmpDetached()) sb.append(" tmpDetached");
+ if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")");
+ if (isAdapterPositionUnknown()) sb.append(" undefined adapter position");
+
+ if (itemView.getParent() == null) sb.append(" no parent");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Informs the recycler whether this item can be recycled. Views which are not
+ * recyclable will not be reused for other items until setIsRecyclable() is
+ * later set to true. Calls to setIsRecyclable() should always be paired (one
+ * call to setIsRecyclabe(false) should always be matched with a later call to
+ * setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally
+ * reference-counted.
+ *
+ * @param recyclable Whether this item is available to be recycled. Default value
+ * is true.
+ *
+ * @see #isRecyclable()
+ */
+ public final void setIsRecyclable(boolean recyclable) {
+ mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1;
+ if (mIsRecyclableCount < 0) {
+ mIsRecyclableCount = 0;
+ if (DEBUG) {
+ throw new RuntimeException("isRecyclable decremented below 0: "
+ + "unmatched pair of setIsRecyable() calls for " + this);
+ }
+ Log.e(VIEW_LOG_TAG, "isRecyclable decremented below 0: "
+ + "unmatched pair of setIsRecyable() calls for " + this);
+ } else if (!recyclable && mIsRecyclableCount == 1) {
+ mFlags |= FLAG_NOT_RECYCLABLE;
+ } else if (recyclable && mIsRecyclableCount == 0) {
+ mFlags &= ~FLAG_NOT_RECYCLABLE;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this);
+ }
+ }
+
+ /**
+ * @return true if this item is available to be recycled, false otherwise.
+ *
+ * @see #setIsRecyclable(boolean)
+ */
+ public final boolean isRecyclable() {
+ return (mFlags & FLAG_NOT_RECYCLABLE) == 0
+ && !itemView.hasTransientState();
+ }
+
+ /**
+ * Returns whether we have animations referring to this view holder or not.
+ * This is similar to isRecyclable flag but does not check transient state.
+ */
+ private boolean shouldBeKeptAsChild() {
+ return (mFlags & FLAG_NOT_RECYCLABLE) != 0;
+ }
+
+ /**
+ * @return True if ViewHolder is not referenced by RecyclerView animations but has
+ * transient state which will prevent it from being recycled.
+ */
+ private boolean doesTransientStatePreventRecycling() {
+ return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && itemView.hasTransientState();
+ }
+
+ boolean isUpdated() {
+ return (mFlags & FLAG_UPDATE) != 0;
+ }
+ }
+
+ /**
+ * This method is here so that we can control the important for a11y changes and test it.
+ */
+ @VisibleForTesting
+ boolean setChildImportantForAccessibilityInternal(ViewHolder viewHolder,
+ int importantForAccessibility) {
+ if (isComputingLayout()) {
+ viewHolder.mPendingAccessibilityState = importantForAccessibility;
+ mPendingAccessibilityImportanceChange.add(viewHolder);
+ return false;
+ }
+ viewHolder.itemView.setImportantForAccessibility(importantForAccessibility);
+ return true;
+ }
+
+ void dispatchPendingImportantForAccessibilityChanges() {
+ for (int i = mPendingAccessibilityImportanceChange.size() - 1; i >= 0; i--) {
+ ViewHolder viewHolder = mPendingAccessibilityImportanceChange.get(i);
+ if (viewHolder.itemView.getParent() != this || viewHolder.shouldIgnore()) {
+ continue;
+ }
+ int state = viewHolder.mPendingAccessibilityState;
+ if (state != ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET) {
+ //noinspection WrongConstant
+ viewHolder.itemView.setImportantForAccessibility(state);
+ viewHolder.mPendingAccessibilityState =
+ ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET;
+ }
+ }
+ mPendingAccessibilityImportanceChange.clear();
+ }
+
+ int getAdapterPositionFor(ViewHolder viewHolder) {
+ if (viewHolder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
+ | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)
+ || !viewHolder.isBound()) {
+ return RecyclerView.NO_POSITION;
+ }
+ return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition);
+ }
+
+ /**
+ * {@link android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of
+ * {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged
+ * to create their own subclass of this <code>LayoutParams</code> class
+ * to store any additional required per-child view metadata about the layout.
+ */
+ public static class LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ViewHolder mViewHolder;
+ final Rect mDecorInsets = new Rect();
+ boolean mInsetsDirty = true;
+ // Flag is set to true if the view is bound while it is detached from RV.
+ // In this case, we need to manually call invalidate after view is added to guarantee that
+ // invalidation is populated through the View hierarchy
+ boolean mPendingInvalidate = false;
+
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ }
+
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ }
+
+ public LayoutParams(MarginLayoutParams source) {
+ super(source);
+ }
+
+ public LayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ }
+
+ public LayoutParams(LayoutParams source) {
+ super((ViewGroup.LayoutParams) source);
+ }
+
+ /**
+ * Returns true if the view this LayoutParams is attached to needs to have its content
+ * updated from the corresponding adapter.
+ *
+ * @return true if the view should have its content updated
+ */
+ public boolean viewNeedsUpdate() {
+ return mViewHolder.needsUpdate();
+ }
+
+ /**
+ * Returns true if the view this LayoutParams is attached to is now representing
+ * potentially invalid data. A LayoutManager should scrap/recycle it.
+ *
+ * @return true if the view is invalid
+ */
+ public boolean isViewInvalid() {
+ return mViewHolder.isInvalid();
+ }
+
+ /**
+ * Returns true if the adapter data item corresponding to the view this LayoutParams
+ * is attached to has been removed from the data set. A LayoutManager may choose to
+ * treat it differently in order to animate its outgoing or disappearing state.
+ *
+ * @return true if the item the view corresponds to was removed from the data set
+ */
+ public boolean isItemRemoved() {
+ return mViewHolder.isRemoved();
+ }
+
+ /**
+ * Returns true if the adapter data item corresponding to the view this LayoutParams
+ * is attached to has been changed in the data set. A LayoutManager may choose to
+ * treat it differently in order to animate its changing state.
+ *
+ * @return true if the item the view corresponds to was changed in the data set
+ */
+ public boolean isItemChanged() {
+ return mViewHolder.isUpdated();
+ }
+
+ /**
+ * @deprecated use {@link #getViewLayoutPosition()} or {@link #getViewAdapterPosition()}
+ */
+ @Deprecated
+ public int getViewPosition() {
+ return mViewHolder.getPosition();
+ }
+
+ /**
+ * Returns the adapter position that the view this LayoutParams is attached to corresponds
+ * to as of latest layout calculation.
+ *
+ * @return the adapter position this view as of latest layout pass
+ */
+ public int getViewLayoutPosition() {
+ return mViewHolder.getLayoutPosition();
+ }
+
+ /**
+ * Returns the up-to-date adapter position that the view this LayoutParams is attached to
+ * corresponds to.
+ *
+ * @return the up-to-date adapter position this view. It may return
+ * {@link RecyclerView#NO_POSITION} if item represented by this View has been removed or
+ * its up-to-date position cannot be calculated.
+ */
+ public int getViewAdapterPosition() {
+ return mViewHolder.getAdapterPosition();
+ }
+ }
+
+ /**
+ * Observer base class for watching changes to an {@link Adapter}.
+ * See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}.
+ */
+ public abstract static class AdapterDataObserver {
+ public void onChanged() {
+ // Do nothing
+ }
+
+ public void onItemRangeChanged(int positionStart, int itemCount) {
+ // do nothing
+ }
+
+ public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ // fallback to onItemRangeChanged(positionStart, itemCount) if app
+ // does not override this method.
+ onItemRangeChanged(positionStart, itemCount);
+ }
+
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ // do nothing
+ }
+
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ // do nothing
+ }
+
+ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+ // do nothing
+ }
+ }
+
+ /**
+ * <p>Base class for smooth scrolling. Handles basic tracking of the target view position and
+ * provides methods to trigger a programmatic scroll.</p>
+ *
+ * @see LinearSmoothScroller
+ */
+ public abstract static class SmoothScroller {
+
+ private int mTargetPosition = RecyclerView.NO_POSITION;
+
+ private RecyclerView mRecyclerView;
+
+ private LayoutManager mLayoutManager;
+
+ private boolean mPendingInitialRun;
+
+ private boolean mRunning;
+
+ private View mTargetView;
+
+ private final Action mRecyclingAction;
+
+ public SmoothScroller() {
+ mRecyclingAction = new Action(0, 0);
+ }
+
+ /**
+ * Starts a smooth scroll for the given target position.
+ * <p>In each animation step, {@link RecyclerView} will check
+ * for the target view and call either
+ * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or
+ * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)} until
+ * SmoothScroller is stopped.</p>
+ *
+ * <p>Note that if RecyclerView finds the target view, it will automatically stop the
+ * SmoothScroller. This <b>does not</b> mean that scroll will stop, it only means it will
+ * stop calling SmoothScroller in each animation step.</p>
+ */
+ void start(RecyclerView recyclerView, LayoutManager layoutManager) {
+ mRecyclerView = recyclerView;
+ mLayoutManager = layoutManager;
+ if (mTargetPosition == RecyclerView.NO_POSITION) {
+ throw new IllegalArgumentException("Invalid target position");
+ }
+ mRecyclerView.mState.mTargetPosition = mTargetPosition;
+ mRunning = true;
+ mPendingInitialRun = true;
+ mTargetView = findViewByPosition(getTargetPosition());
+ onStart();
+ mRecyclerView.mViewFlinger.postOnAnimation();
+ }
+
+ public void setTargetPosition(int targetPosition) {
+ mTargetPosition = targetPosition;
+ }
+
+ /**
+ * @return The LayoutManager to which this SmoothScroller is attached. Will return
+ * <code>null</code> after the SmoothScroller is stopped.
+ */
+ @Nullable
+ public LayoutManager getLayoutManager() {
+ return mLayoutManager;
+ }
+
+ /**
+ * Stops running the SmoothScroller in each animation callback. Note that this does not
+ * cancel any existing {@link Action} updated by
+ * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or
+ * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)}.
+ */
+ protected final void stop() {
+ if (!mRunning) {
+ return;
+ }
+ onStop();
+ mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION;
+ mTargetView = null;
+ mTargetPosition = RecyclerView.NO_POSITION;
+ mPendingInitialRun = false;
+ mRunning = false;
+ // trigger a cleanup
+ mLayoutManager.onSmoothScrollerStopped(this);
+ // clear references to avoid any potential leak by a custom smooth scroller
+ mLayoutManager = null;
+ mRecyclerView = null;
+ }
+
+ /**
+ * Returns true if SmoothScroller has been started but has not received the first
+ * animation
+ * callback yet.
+ *
+ * @return True if this SmoothScroller is waiting to start
+ */
+ public boolean isPendingInitialRun() {
+ return mPendingInitialRun;
+ }
+
+
+ /**
+ * @return True if SmoothScroller is currently active
+ */
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ /**
+ * Returns the adapter position of the target item
+ *
+ * @return Adapter position of the target item or
+ * {@link RecyclerView#NO_POSITION} if no target view is set.
+ */
+ public int getTargetPosition() {
+ return mTargetPosition;
+ }
+
+ private void onAnimation(int dx, int dy) {
+ final RecyclerView recyclerView = mRecyclerView;
+ if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION || recyclerView == null) {
+ stop();
+ }
+ mPendingInitialRun = false;
+ if (mTargetView != null) {
+ // verify target position
+ if (getChildPosition(mTargetView) == mTargetPosition) {
+ onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction);
+ mRecyclingAction.runIfNecessary(recyclerView);
+ stop();
+ } else {
+ Log.e(TAG, "Passed over target position while smooth scrolling.");
+ mTargetView = null;
+ }
+ }
+ if (mRunning) {
+ onSeekTargetStep(dx, dy, recyclerView.mState, mRecyclingAction);
+ boolean hadJumpTarget = mRecyclingAction.hasJumpTarget();
+ mRecyclingAction.runIfNecessary(recyclerView);
+ if (hadJumpTarget) {
+ // It is not stopped so needs to be restarted
+ if (mRunning) {
+ mPendingInitialRun = true;
+ recyclerView.mViewFlinger.postOnAnimation();
+ } else {
+ stop(); // done
+ }
+ }
+ }
+ }
+
+ /**
+ * @see RecyclerView#getChildLayoutPosition(android.view.View)
+ */
+ public int getChildPosition(View view) {
+ return mRecyclerView.getChildLayoutPosition(view);
+ }
+
+ /**
+ * @see RecyclerView.LayoutManager#getChildCount()
+ */
+ public int getChildCount() {
+ return mRecyclerView.mLayout.getChildCount();
+ }
+
+ /**
+ * @see RecyclerView.LayoutManager#findViewByPosition(int)
+ */
+ public View findViewByPosition(int position) {
+ return mRecyclerView.mLayout.findViewByPosition(position);
+ }
+
+ /**
+ * @see RecyclerView#scrollToPosition(int)
+ * @deprecated Use {@link Action#jumpTo(int)}.
+ */
+ @Deprecated
+ public void instantScrollToPosition(int position) {
+ mRecyclerView.scrollToPosition(position);
+ }
+
+ protected void onChildAttachedToWindow(View child) {
+ if (getChildPosition(child) == getTargetPosition()) {
+ mTargetView = child;
+ if (DEBUG) {
+ Log.d(TAG, "smooth scroll target view has been attached");
+ }
+ }
+ }
+
+ /**
+ * Normalizes the vector.
+ * @param scrollVector The vector that points to the target scroll position
+ */
+ protected void normalize(PointF scrollVector) {
+ final double magnitude = Math.sqrt(scrollVector.x * scrollVector.x + scrollVector.y
+ * scrollVector.y);
+ scrollVector.x /= magnitude;
+ scrollVector.y /= magnitude;
+ }
+
+ /**
+ * Called when smooth scroll is started. This might be a good time to do setup.
+ */
+ protected abstract void onStart();
+
+ /**
+ * Called when smooth scroller is stopped. This is a good place to cleanup your state etc.
+ * @see #stop()
+ */
+ protected abstract void onStop();
+
+ /**
+ * <p>RecyclerView will call this method each time it scrolls until it can find the target
+ * position in the layout.</p>
+ * <p>SmoothScroller should check dx, dy and if scroll should be changed, update the
+ * provided {@link Action} to define the next scroll.</p>
+ *
+ * @param dx Last scroll amount horizontally
+ * @param dy Last scroll amount vertically
+ * @param state Transient state of RecyclerView
+ * @param action If you want to trigger a new smooth scroll and cancel the previous one,
+ * update this object.
+ */
+ protected abstract void onSeekTargetStep(int dx, int dy, State state, Action action);
+
+ /**
+ * Called when the target position is laid out. This is the last callback SmoothScroller
+ * will receive and it should update the provided {@link Action} to define the scroll
+ * details towards the target view.
+ * @param targetView The view element which render the target position.
+ * @param state Transient state of RecyclerView
+ * @param action Action instance that you should update to define final scroll action
+ * towards the targetView
+ */
+ protected abstract void onTargetFound(View targetView, State state, Action action);
+
+ /**
+ * Holds information about a smooth scroll request by a {@link SmoothScroller}.
+ */
+ public static class Action {
+
+ public static final int UNDEFINED_DURATION = Integer.MIN_VALUE;
+
+ private int mDx;
+
+ private int mDy;
+
+ private int mDuration;
+
+ private int mJumpToPosition = NO_POSITION;
+
+ private Interpolator mInterpolator;
+
+ private boolean mChanged = false;
+
+ // we track this variable to inform custom implementer if they are updating the action
+ // in every animation callback
+ private int mConsecutiveUpdates = 0;
+
+ /**
+ * @param dx Pixels to scroll horizontally
+ * @param dy Pixels to scroll vertically
+ */
+ public Action(int dx, int dy) {
+ this(dx, dy, UNDEFINED_DURATION, null);
+ }
+
+ /**
+ * @param dx Pixels to scroll horizontally
+ * @param dy Pixels to scroll vertically
+ * @param duration Duration of the animation in milliseconds
+ */
+ public Action(int dx, int dy, int duration) {
+ this(dx, dy, duration, null);
+ }
+
+ /**
+ * @param dx Pixels to scroll horizontally
+ * @param dy Pixels to scroll vertically
+ * @param duration Duration of the animation in milliseconds
+ * @param interpolator Interpolator to be used when calculating scroll position in each
+ * animation step
+ */
+ public Action(int dx, int dy, int duration, Interpolator interpolator) {
+ mDx = dx;
+ mDy = dy;
+ mDuration = duration;
+ mInterpolator = interpolator;
+ }
+
+ /**
+ * Instead of specifying pixels to scroll, use the target position to jump using
+ * {@link RecyclerView#scrollToPosition(int)}.
+ * <p>
+ * You may prefer using this method if scroll target is really far away and you prefer
+ * to jump to a location and smooth scroll afterwards.
+ * <p>
+ * Note that calling this method takes priority over other update methods such as
+ * {@link #update(int, int, int, Interpolator)}, {@link #setX(float)},
+ * {@link #setY(float)} and #{@link #setInterpolator(Interpolator)}. If you call
+ * {@link #jumpTo(int)}, the other changes will not be considered for this animation
+ * frame.
+ *
+ * @param targetPosition The target item position to scroll to using instant scrolling.
+ */
+ public void jumpTo(int targetPosition) {
+ mJumpToPosition = targetPosition;
+ }
+
+ boolean hasJumpTarget() {
+ return mJumpToPosition >= 0;
+ }
+
+ void runIfNecessary(RecyclerView recyclerView) {
+ if (mJumpToPosition >= 0) {
+ final int position = mJumpToPosition;
+ mJumpToPosition = NO_POSITION;
+ recyclerView.jumpToPositionForSmoothScroller(position);
+ mChanged = false;
+ return;
+ }
+ if (mChanged) {
+ validate();
+ if (mInterpolator == null) {
+ if (mDuration == UNDEFINED_DURATION) {
+ recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy);
+ } else {
+ recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration);
+ }
+ } else {
+ recyclerView.mViewFlinger.smoothScrollBy(
+ mDx, mDy, mDuration, mInterpolator);
+ }
+ mConsecutiveUpdates++;
+ if (mConsecutiveUpdates > 10) {
+ // A new action is being set in every animation step. This looks like a bad
+ // implementation. Inform developer.
+ Log.e(TAG, "Smooth Scroll action is being updated too frequently. Make sure"
+ + " you are not changing it unless necessary");
+ }
+ mChanged = false;
+ } else {
+ mConsecutiveUpdates = 0;
+ }
+ }
+
+ private void validate() {
+ if (mInterpolator != null && mDuration < 1) {
+ throw new IllegalStateException("If you provide an interpolator, you must"
+ + " set a positive duration");
+ } else if (mDuration < 1) {
+ throw new IllegalStateException("Scroll duration must be a positive number");
+ }
+ }
+
+ public int getDx() {
+ return mDx;
+ }
+
+ public void setDx(int dx) {
+ mChanged = true;
+ mDx = dx;
+ }
+
+ public int getDy() {
+ return mDy;
+ }
+
+ public void setDy(int dy) {
+ mChanged = true;
+ mDy = dy;
+ }
+
+ public int getDuration() {
+ return mDuration;
+ }
+
+ public void setDuration(int duration) {
+ mChanged = true;
+ mDuration = duration;
+ }
+
+ public Interpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ /**
+ * Sets the interpolator to calculate scroll steps
+ * @param interpolator The interpolator to use. If you specify an interpolator, you must
+ * also set the duration.
+ * @see #setDuration(int)
+ */
+ public void setInterpolator(Interpolator interpolator) {
+ mChanged = true;
+ mInterpolator = interpolator;
+ }
+
+ /**
+ * Updates the action with given parameters.
+ * @param dx Pixels to scroll horizontally
+ * @param dy Pixels to scroll vertically
+ * @param duration Duration of the animation in milliseconds
+ * @param interpolator Interpolator to be used when calculating scroll position in each
+ * animation step
+ */
+ public void update(int dx, int dy, int duration, Interpolator interpolator) {
+ mDx = dx;
+ mDy = dy;
+ mDuration = duration;
+ mInterpolator = interpolator;
+ mChanged = true;
+ }
+ }
+
+ /**
+ * An interface which is optionally implemented by custom {@link RecyclerView.LayoutManager}
+ * to provide a hint to a {@link SmoothScroller} about the location of the target position.
+ */
+ public interface ScrollVectorProvider {
+ /**
+ * Should calculate the vector that points to the direction where the target position
+ * can be found.
+ * <p>
+ * This method is used by the {@link LinearSmoothScroller} to initiate a scroll towards
+ * the target position.
+ * <p>
+ * The magnitude of the vector is not important. It is always normalized before being
+ * used by the {@link LinearSmoothScroller}.
+ * <p>
+ * LayoutManager should not check whether the position exists in the adapter or not.
+ *
+ * @param targetPosition the target position to which the returned vector should point
+ *
+ * @return the scroll vector for a given position.
+ */
+ PointF computeScrollVectorForPosition(int targetPosition);
+ }
+ }
+
+ static class AdapterDataObservable extends Observable<AdapterDataObserver> {
+ public boolean hasObservers() {
+ return !mObservers.isEmpty();
+ }
+
+ public void notifyChanged() {
+ // since onChanged() is implemented by the app, it could do anything, including
+ // removing itself from {@link mObservers} - and that could cause problems if
+ // an iterator is used on the ArrayList {@link mObservers}.
+ // to avoid such problems, just march thru the list in the reverse order.
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ mObservers.get(i).onChanged();
+ }
+ }
+
+ public void notifyItemRangeChanged(int positionStart, int itemCount) {
+ notifyItemRangeChanged(positionStart, itemCount, null);
+ }
+
+ public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ // since onItemRangeChanged() is implemented by the app, it could do anything, including
+ // removing itself from {@link mObservers} - and that could cause problems if
+ // an iterator is used on the ArrayList {@link mObservers}.
+ // to avoid such problems, just march thru the list in the reverse order.
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
+ }
+ }
+
+ public void notifyItemRangeInserted(int positionStart, int itemCount) {
+ // since onItemRangeInserted() is implemented by the app, it could do anything,
+ // including removing itself from {@link mObservers} - and that could cause problems if
+ // an iterator is used on the ArrayList {@link mObservers}.
+ // to avoid such problems, just march thru the list in the reverse order.
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
+ }
+ }
+
+ public void notifyItemRangeRemoved(int positionStart, int itemCount) {
+ // since onItemRangeRemoved() is implemented by the app, it could do anything, including
+ // removing itself from {@link mObservers} - and that could cause problems if
+ // an iterator is used on the ArrayList {@link mObservers}.
+ // to avoid such problems, just march thru the list in the reverse order.
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
+ }
+ }
+
+ public void notifyItemMoved(int fromPosition, int toPosition) {
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1);
+ }
+ }
+ }
+
+ /**
+ * This is public so that the CREATOR can be access on cold launch.
+ * @hide
+ */
+ public static class SavedState extends AbsSavedState {
+
+ Parcelable mLayoutState;
+
+ /**
+ * called by CREATOR
+ */
+ SavedState(Parcel in) {
+ super(in);
+ mLayoutState = in.readParcelable(LayoutManager.class.getClassLoader());
+ }
+
+ /**
+ * Called by onSaveInstanceState
+ */
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeParcelable(mLayoutState, 0);
+ }
+
+ void copyFrom(SavedState other) {
+ mLayoutState = other.mLayoutState;
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+ /**
+ * <p>Contains useful information about the current RecyclerView state like target scroll
+ * position or view focus. State object can also keep arbitrary data, identified by resource
+ * ids.</p>
+ * <p>Often times, RecyclerView components will need to pass information between each other.
+ * To provide a well defined data bus between components, RecyclerView passes the same State
+ * object to component callbacks and these components can use it to exchange data.</p>
+ * <p>If you implement custom components, you can use State's put/get/remove methods to pass
+ * data between your components without needing to manage their lifecycles.</p>
+ */
+ public static class State {
+ static final int STEP_START = 1;
+ static final int STEP_LAYOUT = 1 << 1;
+ static final int STEP_ANIMATIONS = 1 << 2;
+
+ void assertLayoutStep(int accepted) {
+ if ((accepted & mLayoutStep) == 0) {
+ throw new IllegalStateException("Layout state should be one of "
+ + Integer.toBinaryString(accepted) + " but it is "
+ + Integer.toBinaryString(mLayoutStep));
+ }
+ }
+
+
+ /** Owned by SmoothScroller */
+ private int mTargetPosition = RecyclerView.NO_POSITION;
+
+ private SparseArray<Object> mData;
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ // Fields below are carried from one layout pass to the next
+ ////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Number of items adapter had in the previous layout.
+ */
+ int mPreviousLayoutItemCount = 0;
+
+ /**
+ * Number of items that were NOT laid out but has been deleted from the adapter after the
+ * previous layout.
+ */
+ int mDeletedInvisibleItemCountSincePreviousLayout = 0;
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ // Fields below must be updated or cleared before they are used (generally before a pass)
+ ////////////////////////////////////////////////////////////////////////////////////////////
+
+ @IntDef(flag = true, value = {
+ STEP_START, STEP_LAYOUT, STEP_ANIMATIONS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface LayoutState {}
+
+ @LayoutState
+ int mLayoutStep = STEP_START;
+
+ /**
+ * Number of items adapter has.
+ */
+ int mItemCount = 0;
+
+ boolean mStructureChanged = false;
+
+ boolean mInPreLayout = false;
+
+ boolean mTrackOldChangeHolders = false;
+
+ boolean mIsMeasuring = false;
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ // Fields below are always reset outside of the pass (or passes) that use them
+ ////////////////////////////////////////////////////////////////////////////////////////////
+
+ boolean mRunSimpleAnimations = false;
+
+ boolean mRunPredictiveAnimations = false;
+
+ /**
+ * This data is saved before a layout calculation happens. After the layout is finished,
+ * if the previously focused view has been replaced with another view for the same item, we
+ * move the focus to the new item automatically.
+ */
+ int mFocusedItemPosition;
+ long mFocusedItemId;
+ // when a sub child has focus, record its id and see if we can directly request focus on
+ // that one instead
+ int mFocusedSubChildId;
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+
+ State reset() {
+ mTargetPosition = RecyclerView.NO_POSITION;
+ if (mData != null) {
+ mData.clear();
+ }
+ mItemCount = 0;
+ mStructureChanged = false;
+ mIsMeasuring = false;
+ return this;
+ }
+
+ /**
+ * Prepare for a prefetch occurring on the RecyclerView in between traversals, potentially
+ * prior to any layout passes.
+ *
+ * <p>Don't touch any state stored between layout passes, only reset per-layout state, so
+ * that Recycler#getViewForPosition() can function safely.</p>
+ */
+ void prepareForNestedPrefetch(Adapter adapter) {
+ mLayoutStep = STEP_START;
+ mItemCount = adapter.getItemCount();
+ mStructureChanged = false;
+ mInPreLayout = false;
+ mTrackOldChangeHolders = false;
+ mIsMeasuring = false;
+ }
+
+ /**
+ * Returns true if the RecyclerView is currently measuring the layout. This value is
+ * {@code true} only if the LayoutManager opted into the auto measure API and RecyclerView
+ * has non-exact measurement specs.
+ * <p>
+ * Note that if the LayoutManager supports predictive animations and it is calculating the
+ * pre-layout step, this value will be {@code false} even if the RecyclerView is in
+ * {@code onMeasure} call. This is because pre-layout means the previous state of the
+ * RecyclerView and measurements made for that state cannot change the RecyclerView's size.
+ * LayoutManager is always guaranteed to receive another call to
+ * {@link LayoutManager#onLayoutChildren(Recycler, State)} when this happens.
+ *
+ * @return True if the RecyclerView is currently calculating its bounds, false otherwise.
+ */
+ public boolean isMeasuring() {
+ return mIsMeasuring;
+ }
+
+ /**
+ * Returns true if
+ * @return
+ */
+ public boolean isPreLayout() {
+ return mInPreLayout;
+ }
+
+ /**
+ * Returns whether RecyclerView will run predictive animations in this layout pass
+ * or not.
+ *
+ * @return true if RecyclerView is calculating predictive animations to be run at the end
+ * of the layout pass.
+ */
+ public boolean willRunPredictiveAnimations() {
+ return mRunPredictiveAnimations;
+ }
+
+ /**
+ * Returns whether RecyclerView will run simple animations in this layout pass
+ * or not.
+ *
+ * @return true if RecyclerView is calculating simple animations to be run at the end of
+ * the layout pass.
+ */
+ public boolean willRunSimpleAnimations() {
+ return mRunSimpleAnimations;
+ }
+
+ /**
+ * Removes the mapping from the specified id, if there was any.
+ * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* to
+ * preserve cross functionality and avoid conflicts.
+ */
+ public void remove(int resourceId) {
+ if (mData == null) {
+ return;
+ }
+ mData.remove(resourceId);
+ }
+
+ /**
+ * Gets the Object mapped from the specified id, or <code>null</code>
+ * if no such data exists.
+ *
+ * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.*
+ * to
+ * preserve cross functionality and avoid conflicts.
+ */
+ public <T> T get(int resourceId) {
+ if (mData == null) {
+ return null;
+ }
+ return (T) mData.get(resourceId);
+ }
+
+ /**
+ * Adds a mapping from the specified id to the specified value, replacing the previous
+ * mapping from the specified key if there was one.
+ *
+ * @param resourceId Id of the resource you want to add. It is suggested to use R.id.* to
+ * preserve cross functionality and avoid conflicts.
+ * @param data The data you want to associate with the resourceId.
+ */
+ public void put(int resourceId, Object data) {
+ if (mData == null) {
+ mData = new SparseArray<Object>();
+ }
+ mData.put(resourceId, data);
+ }
+
+ /**
+ * If scroll is triggered to make a certain item visible, this value will return the
+ * adapter index of that item.
+ * @return Adapter index of the target item or
+ * {@link RecyclerView#NO_POSITION} if there is no target
+ * position.
+ */
+ public int getTargetScrollPosition() {
+ return mTargetPosition;
+ }
+
+ /**
+ * Returns if current scroll has a target position.
+ * @return true if scroll is being triggered to make a certain position visible
+ * @see #getTargetScrollPosition()
+ */
+ public boolean hasTargetScrollPosition() {
+ return mTargetPosition != RecyclerView.NO_POSITION;
+ }
+
+ /**
+ * @return true if the structure of the data set has changed since the last call to
+ * onLayoutChildren, false otherwise
+ */
+ public boolean didStructureChange() {
+ return mStructureChanged;
+ }
+
+ /**
+ * Returns the total number of items that can be laid out. Note that this number is not
+ * necessarily equal to the number of items in the adapter, so you should always use this
+ * number for your position calculations and never access the adapter directly.
+ * <p>
+ * RecyclerView listens for Adapter's notify events and calculates the effects of adapter
+ * data changes on existing Views. These calculations are used to decide which animations
+ * should be run.
+ * <p>
+ * To support predictive animations, RecyclerView may rewrite or reorder Adapter changes to
+ * present the correct state to LayoutManager in pre-layout pass.
+ * <p>
+ * For example, a newly added item is not included in pre-layout item count because
+ * pre-layout reflects the contents of the adapter before the item is added. Behind the
+ * scenes, RecyclerView offsets {@link Recycler#getViewForPosition(int)} calls such that
+ * LayoutManager does not know about the new item's existence in pre-layout. The item will
+ * be available in second layout pass and will be included in the item count. Similar
+ * adjustments are made for moved and removed items as well.
+ * <p>
+ * You can get the adapter's item count via {@link LayoutManager#getItemCount()} method.
+ *
+ * @return The number of items currently available
+ * @see LayoutManager#getItemCount()
+ */
+ public int getItemCount() {
+ return mInPreLayout
+ ? (mPreviousLayoutItemCount - mDeletedInvisibleItemCountSincePreviousLayout)
+ : mItemCount;
+ }
+
+ @Override
+ public String toString() {
+ return "State{"
+ + "mTargetPosition=" + mTargetPosition
+ + ", mData=" + mData
+ + ", mItemCount=" + mItemCount
+ + ", mPreviousLayoutItemCount=" + mPreviousLayoutItemCount
+ + ", mDeletedInvisibleItemCountSincePreviousLayout="
+ + mDeletedInvisibleItemCountSincePreviousLayout
+ + ", mStructureChanged=" + mStructureChanged
+ + ", mInPreLayout=" + mInPreLayout
+ + ", mRunSimpleAnimations=" + mRunSimpleAnimations
+ + ", mRunPredictiveAnimations=" + mRunPredictiveAnimations
+ + '}';
+ }
+ }
+
+ /**
+ * This class defines the behavior of fling if the developer wishes to handle it.
+ * <p>
+ * Subclasses of {@link OnFlingListener} can be used to implement custom fling behavior.
+ *
+ * @see #setOnFlingListener(OnFlingListener)
+ */
+ public abstract static class OnFlingListener {
+
+ /**
+ * Override this to handle a fling given the velocities in both x and y directions.
+ * Note that this method will only be called if the associated {@link LayoutManager}
+ * supports scrolling and the fling is not handled by nested scrolls first.
+ *
+ * @param velocityX the fling velocity on the X axis
+ * @param velocityY the fling velocity on the Y axis
+ *
+ * @return true if the fling washandled, false otherwise.
+ */
+ public abstract boolean onFling(int velocityX, int velocityY);
+ }
+
+ /**
+ * Internal listener that manages items after animations finish. This is how items are
+ * retained (not recycled) during animations, but allowed to be recycled afterwards.
+ * It depends on the contract with the ItemAnimator to call the appropriate dispatch*Finished()
+ * method on the animator's listener when it is done animating any item.
+ */
+ private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener {
+
+ ItemAnimatorRestoreListener() {
+ }
+
+ @Override
+ public void onAnimationFinished(ViewHolder item) {
+ item.setIsRecyclable(true);
+ if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vh
+ item.mShadowedHolder = null;
+ }
+ // always null this because an OldViewHolder can never become NewViewHolder w/o being
+ // recycled.
+ item.mShadowingHolder = null;
+ if (!item.shouldBeKeptAsChild()) {
+ if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) {
+ removeDetachedView(item.itemView, false);
+ }
+ }
+ }
+ }
+
+ /**
+ * This class defines the animations that take place on items as changes are made
+ * to the adapter.
+ *
+ * Subclasses of ItemAnimator can be used to implement custom animations for actions on
+ * ViewHolder items. The RecyclerView will manage retaining these items while they
+ * are being animated, but implementors must call {@link #dispatchAnimationFinished(ViewHolder)}
+ * when a ViewHolder's animation is finished. In other words, there must be a matching
+ * {@link #dispatchAnimationFinished(ViewHolder)} call for each
+ * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) animateAppearance()},
+ * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateChange()}
+ * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) animatePersistence()},
+ * and
+ * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateDisappearance()} call.
+ *
+ * <p>By default, RecyclerView uses {@link DefaultItemAnimator}.</p>
+ *
+ * @see #setItemAnimator(ItemAnimator)
+ */
+ @SuppressWarnings("UnusedParameters")
+ public abstract static class ItemAnimator {
+
+ /**
+ * The Item represented by this ViewHolder is updated.
+ * <p>
+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+ */
+ public static final int FLAG_CHANGED = ViewHolder.FLAG_UPDATE;
+
+ /**
+ * The Item represented by this ViewHolder is removed from the adapter.
+ * <p>
+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+ */
+ public static final int FLAG_REMOVED = ViewHolder.FLAG_REMOVED;
+
+ /**
+ * Adapter {@link Adapter#notifyDataSetChanged()} has been called and the content
+ * represented by this ViewHolder is invalid.
+ * <p>
+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+ */
+ public static final int FLAG_INVALIDATED = ViewHolder.FLAG_INVALID;
+
+ /**
+ * The position of the Item represented by this ViewHolder has been changed. This flag is
+ * not bound to {@link Adapter#notifyItemMoved(int, int)}. It might be set in response to
+ * any adapter change that may have a side effect on this item. (e.g. The item before this
+ * one has been removed from the Adapter).
+ * <p>
+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+ */
+ public static final int FLAG_MOVED = ViewHolder.FLAG_MOVED;
+
+ /**
+ * This ViewHolder was not laid out but has been added to the layout in pre-layout state
+ * by the {@link LayoutManager}. This means that the item was already in the Adapter but
+ * invisible and it may become visible in the post layout phase. LayoutManagers may prefer
+ * to add new items in pre-layout to specify their virtual location when they are invisible
+ * (e.g. to specify the item should <i>animate in</i> from below the visible area).
+ * <p>
+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+ */
+ public static final int FLAG_APPEARED_IN_PRE_LAYOUT =
+ ViewHolder.FLAG_APPEARED_IN_PRE_LAYOUT;
+
+ /**
+ * The set of flags that might be passed to
+ * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ */
+ @IntDef(flag = true, value = {
+ FLAG_CHANGED, FLAG_REMOVED, FLAG_MOVED, FLAG_INVALIDATED,
+ FLAG_APPEARED_IN_PRE_LAYOUT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AdapterChanges {}
+ private ItemAnimatorListener mListener = null;
+ private ArrayList<ItemAnimatorFinishedListener> mFinishedListeners =
+ new ArrayList<ItemAnimatorFinishedListener>();
+
+ private long mAddDuration = 120;
+ private long mRemoveDuration = 120;
+ private long mMoveDuration = 250;
+ private long mChangeDuration = 250;
+
+ /**
+ * Gets the current duration for which all move animations will run.
+ *
+ * @return The current move duration
+ */
+ public long getMoveDuration() {
+ return mMoveDuration;
+ }
+
+ /**
+ * Sets the duration for which all move animations will run.
+ *
+ * @param moveDuration The move duration
+ */
+ public void setMoveDuration(long moveDuration) {
+ mMoveDuration = moveDuration;
+ }
+
+ /**
+ * Gets the current duration for which all add animations will run.
+ *
+ * @return The current add duration
+ */
+ public long getAddDuration() {
+ return mAddDuration;
+ }
+
+ /**
+ * Sets the duration for which all add animations will run.
+ *
+ * @param addDuration The add duration
+ */
+ public void setAddDuration(long addDuration) {
+ mAddDuration = addDuration;
+ }
+
+ /**
+ * Gets the current duration for which all remove animations will run.
+ *
+ * @return The current remove duration
+ */
+ public long getRemoveDuration() {
+ return mRemoveDuration;
+ }
+
+ /**
+ * Sets the duration for which all remove animations will run.
+ *
+ * @param removeDuration The remove duration
+ */
+ public void setRemoveDuration(long removeDuration) {
+ mRemoveDuration = removeDuration;
+ }
+
+ /**
+ * Gets the current duration for which all change animations will run.
+ *
+ * @return The current change duration
+ */
+ public long getChangeDuration() {
+ return mChangeDuration;
+ }
+
+ /**
+ * Sets the duration for which all change animations will run.
+ *
+ * @param changeDuration The change duration
+ */
+ public void setChangeDuration(long changeDuration) {
+ mChangeDuration = changeDuration;
+ }
+
+ /**
+ * Internal only:
+ * Sets the listener that must be called when the animator is finished
+ * animating the item (or immediately if no animation happens). This is set
+ * internally and is not intended to be set by external code.
+ *
+ * @param listener The listener that must be called.
+ */
+ void setListener(ItemAnimatorListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Called by the RecyclerView before the layout begins. Item animator should record
+ * necessary information about the View before it is potentially rebound, moved or removed.
+ * <p>
+ * The data returned from this method will be passed to the related <code>animate**</code>
+ * methods.
+ * <p>
+ * Note that this method may be called after pre-layout phase if LayoutManager adds new
+ * Views to the layout in pre-layout pass.
+ * <p>
+ * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of
+ * the View and the adapter change flags.
+ *
+ * @param state The current State of RecyclerView which includes some useful data
+ * about the layout that will be calculated.
+ * @param viewHolder The ViewHolder whose information should be recorded.
+ * @param changeFlags Additional information about what changes happened in the Adapter
+ * about the Item represented by this ViewHolder. For instance, if
+ * item is deleted from the adapter, {@link #FLAG_REMOVED} will be set.
+ * @param payloads The payload list that was previously passed to
+ * {@link Adapter#notifyItemChanged(int, Object)} or
+ * {@link Adapter#notifyItemRangeChanged(int, int, Object)}.
+ *
+ * @return An ItemHolderInfo instance that preserves necessary information about the
+ * ViewHolder. This object will be passed back to related <code>animate**</code> methods
+ * after layout is complete.
+ *
+ * @see #recordPostLayoutInformation(State, ViewHolder)
+ * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ */
+ public @NonNull ItemHolderInfo recordPreLayoutInformation(@NonNull State state,
+ @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags,
+ @NonNull List<Object> payloads) {
+ return obtainHolderInfo().setFrom(viewHolder);
+ }
+
+ /**
+ * Called by the RecyclerView after the layout is complete. Item animator should record
+ * necessary information about the View's final state.
+ * <p>
+ * The data returned from this method will be passed to the related <code>animate**</code>
+ * methods.
+ * <p>
+ * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of
+ * the View.
+ *
+ * @param state The current State of RecyclerView which includes some useful data about
+ * the layout that will be calculated.
+ * @param viewHolder The ViewHolder whose information should be recorded.
+ *
+ * @return An ItemHolderInfo that preserves necessary information about the ViewHolder.
+ * This object will be passed back to related <code>animate**</code> methods when
+ * RecyclerView decides how items should be animated.
+ *
+ * @see #recordPreLayoutInformation(State, ViewHolder, int, List)
+ * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ */
+ public @NonNull ItemHolderInfo recordPostLayoutInformation(@NonNull State state,
+ @NonNull ViewHolder viewHolder) {
+ return obtainHolderInfo().setFrom(viewHolder);
+ }
+
+ /**
+ * Called by the RecyclerView when a ViewHolder has disappeared from the layout.
+ * <p>
+ * This means that the View was a child of the LayoutManager when layout started but has
+ * been removed by the LayoutManager. It might have been removed from the adapter or simply
+ * become invisible due to other factors. You can distinguish these two cases by checking
+ * the change flags that were passed to
+ * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ * <p>
+ * Note that when a ViewHolder both changes and disappears in the same layout pass, the
+ * animation callback method which will be called by the RecyclerView depends on the
+ * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the
+ * LayoutManager's decision whether to layout the changed version of a disappearing
+ * ViewHolder or not. RecyclerView will call
+ * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateChange} instead of {@code animateDisappearance} if and only if the ItemAnimator
+ * returns {@code false} from
+ * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the
+ * LayoutManager lays out a new disappearing view that holds the updated information.
+ * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views.
+ * <p>
+ * If LayoutManager supports predictive animations, it might provide a target disappear
+ * location for the View by laying it out in that location. When that happens,
+ * RecyclerView will call {@link #recordPostLayoutInformation(State, ViewHolder)} and the
+ * response of that call will be passed to this method as the <code>postLayoutInfo</code>.
+ * <p>
+ * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation
+ * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it
+ * decides not to animate the view).
+ *
+ * @param viewHolder The ViewHolder which should be animated
+ * @param preLayoutInfo The information that was returned from
+ * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ * @param postLayoutInfo The information that was returned from
+ * {@link #recordPostLayoutInformation(State, ViewHolder)}. Might be
+ * null if the LayoutManager did not layout the item.
+ *
+ * @return true if a later call to {@link #runPendingAnimations()} is requested,
+ * false otherwise.
+ */
+ public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder,
+ @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo);
+
+ /**
+ * Called by the RecyclerView when a ViewHolder is added to the layout.
+ * <p>
+ * In detail, this means that the ViewHolder was <b>not</b> a child when the layout started
+ * but has been added by the LayoutManager. It might be newly added to the adapter or
+ * simply become visible due to other factors.
+ * <p>
+ * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation
+ * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it
+ * decides not to animate the view).
+ *
+ * @param viewHolder The ViewHolder which should be animated
+ * @param preLayoutInfo The information that was returned from
+ * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ * Might be null if Item was just added to the adapter or
+ * LayoutManager does not support predictive animations or it could
+ * not predict that this ViewHolder will become visible.
+ * @param postLayoutInfo The information that was returned from {@link
+ * #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ *
+ * @return true if a later call to {@link #runPendingAnimations()} is requested,
+ * false otherwise.
+ */
+ public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder,
+ @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
+
+ /**
+ * Called by the RecyclerView when a ViewHolder is present in both before and after the
+ * layout and RecyclerView has not received a {@link Adapter#notifyItemChanged(int)} call
+ * for it or a {@link Adapter#notifyDataSetChanged()} call.
+ * <p>
+ * This ViewHolder still represents the same data that it was representing when the layout
+ * started but its position / size may be changed by the LayoutManager.
+ * <p>
+ * If the Item's layout position didn't change, RecyclerView still calls this method because
+ * it does not track this information (or does not necessarily know that an animation is
+ * not required). Your ItemAnimator should handle this case and if there is nothing to
+ * animate, it should call {@link #dispatchAnimationFinished(ViewHolder)} and return
+ * <code>false</code>.
+ * <p>
+ * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation
+ * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it
+ * decides not to animate the view).
+ *
+ * @param viewHolder The ViewHolder which should be animated
+ * @param preLayoutInfo The information that was returned from
+ * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ * @param postLayoutInfo The information that was returned from {@link
+ * #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ *
+ * @return true if a later call to {@link #runPendingAnimations()} is requested,
+ * false otherwise.
+ */
+ public abstract boolean animatePersistence(@NonNull ViewHolder viewHolder,
+ @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
+
+ /**
+ * Called by the RecyclerView when an adapter item is present both before and after the
+ * layout and RecyclerView has received a {@link Adapter#notifyItemChanged(int)} call
+ * for it. This method may also be called when
+ * {@link Adapter#notifyDataSetChanged()} is called and adapter has stable ids so that
+ * RecyclerView could still rebind views to the same ViewHolders. If viewType changes when
+ * {@link Adapter#notifyDataSetChanged()} is called, this method <b>will not</b> be called,
+ * instead, {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} will be
+ * called for the new ViewHolder and the old one will be recycled.
+ * <p>
+ * If this method is called due to a {@link Adapter#notifyDataSetChanged()} call, there is
+ * a good possibility that item contents didn't really change but it is rebound from the
+ * adapter. {@link DefaultItemAnimator} will skip animating the View if its location on the
+ * screen didn't change and your animator should handle this case as well and avoid creating
+ * unnecessary animations.
+ * <p>
+ * When an item is updated, ItemAnimator has a chance to ask RecyclerView to keep the
+ * previous presentation of the item as-is and supply a new ViewHolder for the updated
+ * presentation (see: {@link #canReuseUpdatedViewHolder(ViewHolder, List)}.
+ * This is useful if you don't know the contents of the Item and would like
+ * to cross-fade the old and the new one ({@link DefaultItemAnimator} uses this technique).
+ * <p>
+ * When you are writing a custom item animator for your layout, it might be more performant
+ * and elegant to re-use the same ViewHolder and animate the content changes manually.
+ * <p>
+ * When {@link Adapter#notifyItemChanged(int)} is called, the Item's view type may change.
+ * If the Item's view type has changed or ItemAnimator returned <code>false</code> for
+ * this ViewHolder when {@link #canReuseUpdatedViewHolder(ViewHolder, List)} was called, the
+ * <code>oldHolder</code> and <code>newHolder</code> will be different ViewHolder instances
+ * which represent the same Item. In that case, only the new ViewHolder is visible
+ * to the LayoutManager but RecyclerView keeps old ViewHolder attached for animations.
+ * <p>
+ * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} for each distinct
+ * ViewHolder when their animation is complete
+ * (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it decides not to
+ * animate the view).
+ * <p>
+ * If oldHolder and newHolder are the same instance, you should call
+ * {@link #dispatchAnimationFinished(ViewHolder)} <b>only once</b>.
+ * <p>
+ * Note that when a ViewHolder both changes and disappears in the same layout pass, the
+ * animation callback method which will be called by the RecyclerView depends on the
+ * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the
+ * LayoutManager's decision whether to layout the changed version of a disappearing
+ * ViewHolder or not. RecyclerView will call
+ * {@code animateChange} instead of
+ * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateDisappearance} if and only if the ItemAnimator returns {@code false} from
+ * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the
+ * LayoutManager lays out a new disappearing view that holds the updated information.
+ * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views.
+ *
+ * @param oldHolder The ViewHolder before the layout is started, might be the same
+ * instance with newHolder.
+ * @param newHolder The ViewHolder after the layout is finished, might be the same
+ * instance with oldHolder.
+ * @param preLayoutInfo The information that was returned from
+ * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ * @param postLayoutInfo The information that was returned from {@link
+ * #recordPreLayoutInformation(State, ViewHolder, int, List)}.
+ *
+ * @return true if a later call to {@link #runPendingAnimations()} is requested,
+ * false otherwise.
+ */
+ public abstract boolean animateChange(@NonNull ViewHolder oldHolder,
+ @NonNull ViewHolder newHolder,
+ @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
+
+ @AdapterChanges static int buildAdapterChangeFlagsForAnimations(ViewHolder viewHolder) {
+ int flags = viewHolder.mFlags & (FLAG_INVALIDATED | FLAG_REMOVED | FLAG_CHANGED);
+ if (viewHolder.isInvalid()) {
+ return FLAG_INVALIDATED;
+ }
+ if ((flags & FLAG_INVALIDATED) == 0) {
+ final int oldPos = viewHolder.getOldPosition();
+ final int pos = viewHolder.getAdapterPosition();
+ if (oldPos != NO_POSITION && pos != NO_POSITION && oldPos != pos) {
+ flags |= FLAG_MOVED;
+ }
+ }
+ return flags;
+ }
+
+ /**
+ * Called when there are pending animations waiting to be started. This state
+ * is governed by the return values from
+ * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateAppearance()},
+ * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateChange()}
+ * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animatePersistence()}, and
+ * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateDisappearance()}, which inform the RecyclerView that the ItemAnimator wants to be
+ * called later to start the associated animations. runPendingAnimations() will be scheduled
+ * to be run on the next frame.
+ */
+ public abstract void runPendingAnimations();
+
+ /**
+ * Method called when an animation on a view should be ended immediately.
+ * This could happen when other events, like scrolling, occur, so that
+ * animating views can be quickly put into their proper end locations.
+ * Implementations should ensure that any animations running on the item
+ * are canceled and affected properties are set to their end values.
+ * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished
+ * animation since the animations are effectively done when this method is called.
+ *
+ * @param item The item for which an animation should be stopped.
+ */
+ public abstract void endAnimation(ViewHolder item);
+
+ /**
+ * Method called when all item animations should be ended immediately.
+ * This could happen when other events, like scrolling, occur, so that
+ * animating views can be quickly put into their proper end locations.
+ * Implementations should ensure that any animations running on any items
+ * are canceled and affected properties are set to their end values.
+ * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished
+ * animation since the animations are effectively done when this method is called.
+ */
+ public abstract void endAnimations();
+
+ /**
+ * Method which returns whether there are any item animations currently running.
+ * This method can be used to determine whether to delay other actions until
+ * animations end.
+ *
+ * @return true if there are any item animations currently running, false otherwise.
+ */
+ public abstract boolean isRunning();
+
+ /**
+ * Method to be called by subclasses when an animation is finished.
+ * <p>
+ * For each call RecyclerView makes to
+ * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateAppearance()},
+ * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animatePersistence()}, or
+ * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateDisappearance()}, there
+ * should
+ * be a matching {@link #dispatchAnimationFinished(ViewHolder)} call by the subclass.
+ * <p>
+ * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateChange()}, subclass should call this method for both the <code>oldHolder</code>
+ * and <code>newHolder</code> (if they are not the same instance).
+ *
+ * @param viewHolder The ViewHolder whose animation is finished.
+ * @see #onAnimationFinished(ViewHolder)
+ */
+ public final void dispatchAnimationFinished(ViewHolder viewHolder) {
+ onAnimationFinished(viewHolder);
+ if (mListener != null) {
+ mListener.onAnimationFinished(viewHolder);
+ }
+ }
+
+ /**
+ * Called after {@link #dispatchAnimationFinished(ViewHolder)} is called by the
+ * ItemAnimator.
+ *
+ * @param viewHolder The ViewHolder whose animation is finished. There might still be other
+ * animations running on this ViewHolder.
+ * @see #dispatchAnimationFinished(ViewHolder)
+ */
+ public void onAnimationFinished(ViewHolder viewHolder) {
+ }
+
+ /**
+ * Method to be called by subclasses when an animation is started.
+ * <p>
+ * For each call RecyclerView makes to
+ * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateAppearance()},
+ * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animatePersistence()}, or
+ * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateDisappearance()}, there should be a matching
+ * {@link #dispatchAnimationStarted(ViewHolder)} call by the subclass.
+ * <p>
+ * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateChange()}, subclass should call this method for both the <code>oldHolder</code>
+ * and <code>newHolder</code> (if they are not the same instance).
+ * <p>
+ * If your ItemAnimator decides not to animate a ViewHolder, it should call
+ * {@link #dispatchAnimationFinished(ViewHolder)} <b>without</b> calling
+ * {@link #dispatchAnimationStarted(ViewHolder)}.
+ *
+ * @param viewHolder The ViewHolder whose animation is starting.
+ * @see #onAnimationStarted(ViewHolder)
+ */
+ public final void dispatchAnimationStarted(ViewHolder viewHolder) {
+ onAnimationStarted(viewHolder);
+ }
+
+ /**
+ * Called when a new animation is started on the given ViewHolder.
+ *
+ * @param viewHolder The ViewHolder which started animating. Note that the ViewHolder
+ * might already be animating and this might be another animation.
+ * @see #dispatchAnimationStarted(ViewHolder)
+ */
+ public void onAnimationStarted(ViewHolder viewHolder) {
+
+ }
+
+ /**
+ * Like {@link #isRunning()}, this method returns whether there are any item
+ * animations currently running. Additionally, the listener passed in will be called
+ * when there are no item animations running, either immediately (before the method
+ * returns) if no animations are currently running, or when the currently running
+ * animations are {@link #dispatchAnimationsFinished() finished}.
+ *
+ * <p>Note that the listener is transient - it is either called immediately and not
+ * stored at all, or stored only until it is called when running animations
+ * are finished sometime later.</p>
+ *
+ * @param listener A listener to be called immediately if no animations are running
+ * or later when currently-running animations have finished. A null listener is
+ * equivalent to calling {@link #isRunning()}.
+ * @return true if there are any item animations currently running, false otherwise.
+ */
+ public final boolean isRunning(ItemAnimatorFinishedListener listener) {
+ boolean running = isRunning();
+ if (listener != null) {
+ if (!running) {
+ listener.onAnimationsFinished();
+ } else {
+ mFinishedListeners.add(listener);
+ }
+ }
+ return running;
+ }
+
+ /**
+ * When an item is changed, ItemAnimator can decide whether it wants to re-use
+ * the same ViewHolder for animations or RecyclerView should create a copy of the
+ * item and ItemAnimator will use both to run the animation (e.g. cross-fade).
+ * <p>
+ * Note that this method will only be called if the {@link ViewHolder} still has the same
+ * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive
+ * both {@link ViewHolder}s in the
+ * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method.
+ * <p>
+ * If your application is using change payloads, you can override
+ * {@link #canReuseUpdatedViewHolder(ViewHolder, List)} to decide based on payloads.
+ *
+ * @param viewHolder The ViewHolder which represents the changed item's old content.
+ *
+ * @return True if RecyclerView should just rebind to the same ViewHolder or false if
+ * RecyclerView should create a new ViewHolder and pass this ViewHolder to the
+ * ItemAnimator to animate. Default implementation returns <code>true</code>.
+ *
+ * @see #canReuseUpdatedViewHolder(ViewHolder, List)
+ */
+ public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder) {
+ return true;
+ }
+
+ /**
+ * When an item is changed, ItemAnimator can decide whether it wants to re-use
+ * the same ViewHolder for animations or RecyclerView should create a copy of the
+ * item and ItemAnimator will use both to run the animation (e.g. cross-fade).
+ * <p>
+ * Note that this method will only be called if the {@link ViewHolder} still has the same
+ * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive
+ * both {@link ViewHolder}s in the
+ * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method.
+ *
+ * @param viewHolder The ViewHolder which represents the changed item's old content.
+ * @param payloads A non-null list of merged payloads that were sent with change
+ * notifications. Can be empty if the adapter is invalidated via
+ * {@link RecyclerView.Adapter#notifyDataSetChanged()}. The same list of
+ * payloads will be passed into
+ * {@link RecyclerView.Adapter#onBindViewHolder(ViewHolder, int, List)}
+ * method <b>if</b> this method returns <code>true</code>.
+ *
+ * @return True if RecyclerView should just rebind to the same ViewHolder or false if
+ * RecyclerView should create a new ViewHolder and pass this ViewHolder to the
+ * ItemAnimator to animate. Default implementation calls
+ * {@link #canReuseUpdatedViewHolder(ViewHolder)}.
+ *
+ * @see #canReuseUpdatedViewHolder(ViewHolder)
+ */
+ public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
+ @NonNull List<Object> payloads) {
+ return canReuseUpdatedViewHolder(viewHolder);
+ }
+
+ /**
+ * This method should be called by ItemAnimator implementations to notify
+ * any listeners that all pending and active item animations are finished.
+ */
+ public final void dispatchAnimationsFinished() {
+ final int count = mFinishedListeners.size();
+ for (int i = 0; i < count; ++i) {
+ mFinishedListeners.get(i).onAnimationsFinished();
+ }
+ mFinishedListeners.clear();
+ }
+
+ /**
+ * Returns a new {@link ItemHolderInfo} which will be used to store information about the
+ * ViewHolder. This information will later be passed into <code>animate**</code> methods.
+ * <p>
+ * You can override this method if you want to extend {@link ItemHolderInfo} and provide
+ * your own instances.
+ *
+ * @return A new {@link ItemHolderInfo}.
+ */
+ public ItemHolderInfo obtainHolderInfo() {
+ return new ItemHolderInfo();
+ }
+
+ /**
+ * The interface to be implemented by listeners to animation events from this
+ * ItemAnimator. This is used internally and is not intended for developers to
+ * create directly.
+ */
+ interface ItemAnimatorListener {
+ void onAnimationFinished(ViewHolder item);
+ }
+
+ /**
+ * This interface is used to inform listeners when all pending or running animations
+ * in an ItemAnimator are finished. This can be used, for example, to delay an action
+ * in a data set until currently-running animations are complete.
+ *
+ * @see #isRunning(ItemAnimatorFinishedListener)
+ */
+ public interface ItemAnimatorFinishedListener {
+ /**
+ * Notifies when all pending or running animations in an ItemAnimator are finished.
+ */
+ void onAnimationsFinished();
+ }
+
+ /**
+ * A simple data structure that holds information about an item's bounds.
+ * This information is used in calculating item animations. Default implementation of
+ * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)} and
+ * {@link #recordPostLayoutInformation(RecyclerView.State, ViewHolder)} returns this data
+ * structure. You can extend this class if you would like to keep more information about
+ * the Views.
+ * <p>
+ * If you want to provide your own implementation but still use `super` methods to record
+ * basic information, you can override {@link #obtainHolderInfo()} to provide your own
+ * instances.
+ */
+ public static class ItemHolderInfo {
+
+ /**
+ * The left edge of the View (excluding decorations)
+ */
+ public int left;
+
+ /**
+ * The top edge of the View (excluding decorations)
+ */
+ public int top;
+
+ /**
+ * The right edge of the View (excluding decorations)
+ */
+ public int right;
+
+ /**
+ * The bottom edge of the View (excluding decorations)
+ */
+ public int bottom;
+
+ /**
+ * The change flags that were passed to
+ * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)}.
+ */
+ @AdapterChanges
+ public int changeFlags;
+
+ public ItemHolderInfo() {
+ }
+
+ /**
+ * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from
+ * the given ViewHolder. Clears all {@link #changeFlags}.
+ *
+ * @param holder The ViewHolder whose bounds should be copied.
+ * @return This {@link ItemHolderInfo}
+ */
+ public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder) {
+ return setFrom(holder, 0);
+ }
+
+ /**
+ * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from
+ * the given ViewHolder and sets the {@link #changeFlags} to the given flags parameter.
+ *
+ * @param holder The ViewHolder whose bounds should be copied.
+ * @param flags The adapter change flags that were passed into
+ * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int,
+ * List)}.
+ * @return This {@link ItemHolderInfo}
+ */
+ public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder,
+ @AdapterChanges int flags) {
+ final View view = holder.itemView;
+ this.left = view.getLeft();
+ this.top = view.getTop();
+ this.right = view.getRight();
+ this.bottom = view.getBottom();
+ return this;
+ }
+ }
+ }
+
+ @Override
+ protected int getChildDrawingOrder(int childCount, int i) {
+ if (mChildDrawingOrderCallback == null) {
+ return super.getChildDrawingOrder(childCount, i);
+ } else {
+ return mChildDrawingOrderCallback.onGetChildDrawingOrder(childCount, i);
+ }
+ }
+
+ /**
+ * A callback interface that can be used to alter the drawing order of RecyclerView children.
+ * <p>
+ * It works using the {@link ViewGroup#getChildDrawingOrder(int, int)} method, so any case
+ * that applies to that method also applies to this callback. For example, changing the drawing
+ * order of two views will not have any effect if their elevation values are different since
+ * elevation overrides the result of this callback.
+ */
+ public interface ChildDrawingOrderCallback {
+ /**
+ * Returns the index of the child to draw for this iteration. Override this
+ * if you want to change the drawing order of children. By default, it
+ * returns i.
+ *
+ * @param i The current iteration.
+ * @return The index of the child to draw this iteration.
+ *
+ * @see RecyclerView#setChildDrawingOrderCallback(RecyclerView.ChildDrawingOrderCallback)
+ */
+ int onGetChildDrawingOrder(int childCount, int i);
+ }
+}
diff --git a/core/java/com/android/internal/widget/RecyclerViewAccessibilityDelegate.java b/core/java/com/android/internal/widget/RecyclerViewAccessibilityDelegate.java
new file mode 100644
index 0000000..282da64
--- /dev/null
+++ b/core/java/com/android/internal/widget/RecyclerViewAccessibilityDelegate.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+/**
+ * The AccessibilityDelegate used by RecyclerView.
+ * <p>
+ * This class handles basic accessibility actions and delegates them to LayoutManager.
+ */
+public class RecyclerViewAccessibilityDelegate extends AccessibilityDelegate {
+ final RecyclerView mRecyclerView;
+
+
+ public RecyclerViewAccessibilityDelegate(RecyclerView recyclerView) {
+ mRecyclerView = recyclerView;
+ }
+
+ boolean shouldIgnore() {
+ return mRecyclerView.hasPendingAdapterUpdates();
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (super.performAccessibilityAction(host, action, args)) {
+ return true;
+ }
+ if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
+ return mRecyclerView.getLayoutManager().performAccessibilityAction(action, args);
+ }
+
+ return false;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.setClassName(RecyclerView.class.getName());
+ if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
+ mRecyclerView.getLayoutManager().onInitializeAccessibilityNodeInfo(info);
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(host, event);
+ event.setClassName(RecyclerView.class.getName());
+ if (host instanceof RecyclerView && !shouldIgnore()) {
+ RecyclerView rv = (RecyclerView) host;
+ if (rv.getLayoutManager() != null) {
+ rv.getLayoutManager().onInitializeAccessibilityEvent(event);
+ }
+ }
+ }
+
+ /**
+ * Gets the AccessibilityDelegate for an individual item in the RecyclerView.
+ * A basic item delegate is provided by default, but you can override this
+ * method to provide a custom per-item delegate.
+ */
+ public AccessibilityDelegate getItemDelegate() {
+ return mItemDelegate;
+ }
+
+ final AccessibilityDelegate mItemDelegate = new AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
+ mRecyclerView.getLayoutManager()
+ .onInitializeAccessibilityNodeInfoForItem(host, info);
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (super.performAccessibilityAction(host, action, args)) {
+ return true;
+ }
+ if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
+ return mRecyclerView.getLayoutManager()
+ .performAccessibilityActionForItem(host, action, args);
+ }
+ return false;
+ }
+ };
+}
+
diff --git a/core/java/com/android/internal/widget/ScrollbarHelper.java b/core/java/com/android/internal/widget/ScrollbarHelper.java
new file mode 100644
index 0000000..ae34e4c
--- /dev/null
+++ b/core/java/com/android/internal/widget/ScrollbarHelper.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.view.View;
+
+/**
+ * A helper class to do scroll offset calculations.
+ */
+class ScrollbarHelper {
+
+ /**
+ * @param startChild View closest to start of the list. (top or left)
+ * @param endChild View closest to end of the list (bottom or right)
+ */
+ static int computeScrollOffset(RecyclerView.State state, OrientationHelper orientation,
+ View startChild, View endChild, RecyclerView.LayoutManager lm,
+ boolean smoothScrollbarEnabled, boolean reverseLayout) {
+ if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null
+ || endChild == null) {
+ return 0;
+ }
+ final int minPosition = Math.min(lm.getPosition(startChild),
+ lm.getPosition(endChild));
+ final int maxPosition = Math.max(lm.getPosition(startChild),
+ lm.getPosition(endChild));
+ final int itemsBefore = reverseLayout
+ ? Math.max(0, state.getItemCount() - maxPosition - 1)
+ : Math.max(0, minPosition);
+ if (!smoothScrollbarEnabled) {
+ return itemsBefore;
+ }
+ final int laidOutArea = Math.abs(orientation.getDecoratedEnd(endChild)
+ - orientation.getDecoratedStart(startChild));
+ final int itemRange = Math.abs(lm.getPosition(startChild)
+ - lm.getPosition(endChild)) + 1;
+ final float avgSizePerRow = (float) laidOutArea / itemRange;
+
+ return Math.round(itemsBefore * avgSizePerRow + (orientation.getStartAfterPadding()
+ - orientation.getDecoratedStart(startChild)));
+ }
+
+ /**
+ * @param startChild View closest to start of the list. (top or left)
+ * @param endChild View closest to end of the list (bottom or right)
+ */
+ static int computeScrollExtent(RecyclerView.State state, OrientationHelper orientation,
+ View startChild, View endChild, RecyclerView.LayoutManager lm,
+ boolean smoothScrollbarEnabled) {
+ if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null
+ || endChild == null) {
+ return 0;
+ }
+ if (!smoothScrollbarEnabled) {
+ return Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild)) + 1;
+ }
+ final int extend = orientation.getDecoratedEnd(endChild)
+ - orientation.getDecoratedStart(startChild);
+ return Math.min(orientation.getTotalSpace(), extend);
+ }
+
+ /**
+ * @param startChild View closest to start of the list. (top or left)
+ * @param endChild View closest to end of the list (bottom or right)
+ */
+ static int computeScrollRange(RecyclerView.State state, OrientationHelper orientation,
+ View startChild, View endChild, RecyclerView.LayoutManager lm,
+ boolean smoothScrollbarEnabled) {
+ if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null
+ || endChild == null) {
+ return 0;
+ }
+ if (!smoothScrollbarEnabled) {
+ return state.getItemCount();
+ }
+ // smooth scrollbar enabled. try to estimate better.
+ final int laidOutArea = orientation.getDecoratedEnd(endChild)
+ - orientation.getDecoratedStart(startChild);
+ final int laidOutRange = Math.abs(lm.getPosition(startChild)
+ - lm.getPosition(endChild))
+ + 1;
+ // estimate a size for full list.
+ return (int) ((float) laidOutArea / laidOutRange * state.getItemCount());
+ }
+}
diff --git a/core/java/com/android/internal/widget/ScrollingView.java b/core/java/com/android/internal/widget/ScrollingView.java
new file mode 100644
index 0000000..a0205e7
--- /dev/null
+++ b/core/java/com/android/internal/widget/ScrollingView.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+/**
+ * An interface that can be implemented by Views to provide scroll related APIs.
+ */
+public interface ScrollingView {
+ /**
+ * <p>Compute the horizontal range that the horizontal scrollbar
+ * represents.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeHorizontalScrollExtent()} and
+ * {@link #computeHorizontalScrollOffset()}.</p>
+ *
+ * <p>The default range is the drawing width of this view.</p>
+ *
+ * @return the total horizontal range represented by the horizontal
+ * scrollbar
+ *
+ * @see #computeHorizontalScrollExtent()
+ * @see #computeHorizontalScrollOffset()
+ * @see android.widget.ScrollBarDrawable
+ */
+ int computeHorizontalScrollRange();
+
+ /**
+ * <p>Compute the horizontal offset of the horizontal scrollbar's thumb
+ * within the horizontal range. This value is used to compute the position
+ * of the thumb within the scrollbar's track.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeHorizontalScrollRange()} and
+ * {@link #computeHorizontalScrollExtent()}.</p>
+ *
+ * <p>The default offset is the scroll offset of this view.</p>
+ *
+ * @return the horizontal offset of the scrollbar's thumb
+ *
+ * @see #computeHorizontalScrollRange()
+ * @see #computeHorizontalScrollExtent()
+ * @see android.widget.ScrollBarDrawable
+ */
+ int computeHorizontalScrollOffset();
+
+ /**
+ * <p>Compute the horizontal extent of the horizontal scrollbar's thumb
+ * within the horizontal range. This value is used to compute the length
+ * of the thumb within the scrollbar's track.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeHorizontalScrollRange()} and
+ * {@link #computeHorizontalScrollOffset()}.</p>
+ *
+ * <p>The default extent is the drawing width of this view.</p>
+ *
+ * @return the horizontal extent of the scrollbar's thumb
+ *
+ * @see #computeHorizontalScrollRange()
+ * @see #computeHorizontalScrollOffset()
+ * @see android.widget.ScrollBarDrawable
+ */
+ int computeHorizontalScrollExtent();
+
+ /**
+ * <p>Compute the vertical range that the vertical scrollbar represents.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeVerticalScrollExtent()} and
+ * {@link #computeVerticalScrollOffset()}.</p>
+ *
+ * @return the total vertical range represented by the vertical scrollbar
+ *
+ * <p>The default range is the drawing height of this view.</p>
+ *
+ * @see #computeVerticalScrollExtent()
+ * @see #computeVerticalScrollOffset()
+ * @see android.widget.ScrollBarDrawable
+ */
+ int computeVerticalScrollRange();
+
+ /**
+ * <p>Compute the vertical offset of the vertical scrollbar's thumb
+ * within the horizontal range. This value is used to compute the position
+ * of the thumb within the scrollbar's track.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeVerticalScrollRange()} and
+ * {@link #computeVerticalScrollExtent()}.</p>
+ *
+ * <p>The default offset is the scroll offset of this view.</p>
+ *
+ * @return the vertical offset of the scrollbar's thumb
+ *
+ * @see #computeVerticalScrollRange()
+ * @see #computeVerticalScrollExtent()
+ * @see android.widget.ScrollBarDrawable
+ */
+ int computeVerticalScrollOffset();
+
+ /**
+ * <p>Compute the vertical extent of the vertical scrollbar's thumb
+ * within the vertical range. This value is used to compute the length
+ * of the thumb within the scrollbar's track.</p>
+ *
+ * <p>The range is expressed in arbitrary units that must be the same as the
+ * units used by {@link #computeVerticalScrollRange()} and
+ * {@link #computeVerticalScrollOffset()}.</p>
+ *
+ * <p>The default extent is the drawing height of this view.</p>
+ *
+ * @return the vertical extent of the scrollbar's thumb
+ *
+ * @see #computeVerticalScrollRange()
+ * @see #computeVerticalScrollOffset()
+ * @see android.widget.ScrollBarDrawable
+ */
+ int computeVerticalScrollExtent();
+}
diff --git a/core/java/com/android/internal/widget/SimpleItemAnimator.java b/core/java/com/android/internal/widget/SimpleItemAnimator.java
new file mode 100644
index 0000000..f4cc753
--- /dev/null
+++ b/core/java/com/android/internal/widget/SimpleItemAnimator.java
@@ -0,0 +1,457 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+import android.view.View;
+
+import com.android.internal.widget.RecyclerView.Adapter;
+import com.android.internal.widget.RecyclerView.ViewHolder;
+
+/**
+ * A wrapper class for ItemAnimator that records View bounds and decides whether it should run
+ * move, change, add or remove animations. This class also replicates the original ItemAnimator
+ * API.
+ * <p>
+ * It uses {@link ItemHolderInfo} to track the bounds information of the Views. If you would like
+ * to
+ * extend this class, you can override {@link #obtainHolderInfo()} method to provide your own info
+ * class that extends {@link ItemHolderInfo}.
+ */
+public abstract class SimpleItemAnimator extends RecyclerView.ItemAnimator {
+
+ private static final boolean DEBUG = false;
+
+ private static final String TAG = "SimpleItemAnimator";
+
+ boolean mSupportsChangeAnimations = true;
+
+ /**
+ * Returns whether this ItemAnimator supports animations of change events.
+ *
+ * @return true if change animations are supported, false otherwise
+ */
+ @SuppressWarnings("unused")
+ public boolean getSupportsChangeAnimations() {
+ return mSupportsChangeAnimations;
+ }
+
+ /**
+ * Sets whether this ItemAnimator supports animations of item change events.
+ * If you set this property to false, actions on the data set which change the
+ * contents of items will not be animated. What those animations do is left
+ * up to the discretion of the ItemAnimator subclass, in its
+ * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} implementation.
+ * The value of this property is true by default.
+ *
+ * @param supportsChangeAnimations true if change animations are supported by
+ * this ItemAnimator, false otherwise. If the property is false,
+ * the ItemAnimator
+ * will not receive a call to
+ * {@link #animateChange(ViewHolder, ViewHolder, int, int, int,
+ * int)} when changes occur.
+ * @see Adapter#notifyItemChanged(int)
+ * @see Adapter#notifyItemRangeChanged(int, int)
+ */
+ public void setSupportsChangeAnimations(boolean supportsChangeAnimations) {
+ mSupportsChangeAnimations = supportsChangeAnimations;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return True if change animations are not supported or the ViewHolder is invalid,
+ * false otherwise.
+ *
+ * @see #setSupportsChangeAnimations(boolean)
+ */
+ @Override
+ public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
+ return !mSupportsChangeAnimations || viewHolder.isInvalid();
+ }
+
+ @Override
+ public boolean animateDisappearance(@NonNull ViewHolder viewHolder,
+ @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
+ int oldLeft = preLayoutInfo.left;
+ int oldTop = preLayoutInfo.top;
+ View disappearingItemView = viewHolder.itemView;
+ int newLeft = postLayoutInfo == null ? disappearingItemView.getLeft() : postLayoutInfo.left;
+ int newTop = postLayoutInfo == null ? disappearingItemView.getTop() : postLayoutInfo.top;
+ if (!viewHolder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) {
+ disappearingItemView.layout(newLeft, newTop,
+ newLeft + disappearingItemView.getWidth(),
+ newTop + disappearingItemView.getHeight());
+ if (DEBUG) {
+ Log.d(TAG, "DISAPPEARING: " + viewHolder + " with view " + disappearingItemView);
+ }
+ return animateMove(viewHolder, oldLeft, oldTop, newLeft, newTop);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "REMOVED: " + viewHolder + " with view " + disappearingItemView);
+ }
+ return animateRemove(viewHolder);
+ }
+ }
+
+ @Override
+ public boolean animateAppearance(@NonNull ViewHolder viewHolder,
+ @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
+ if (preLayoutInfo != null && (preLayoutInfo.left != postLayoutInfo.left
+ || preLayoutInfo.top != postLayoutInfo.top)) {
+ // slide items in if before/after locations differ
+ if (DEBUG) {
+ Log.d(TAG, "APPEARING: " + viewHolder + " with view " + viewHolder);
+ }
+ return animateMove(viewHolder, preLayoutInfo.left, preLayoutInfo.top,
+ postLayoutInfo.left, postLayoutInfo.top);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "ADDED: " + viewHolder + " with view " + viewHolder);
+ }
+ return animateAdd(viewHolder);
+ }
+ }
+
+ @Override
+ public boolean animatePersistence(@NonNull ViewHolder viewHolder,
+ @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
+ if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) {
+ if (DEBUG) {
+ Log.d(TAG, "PERSISTENT: " + viewHolder
+ + " with view " + viewHolder.itemView);
+ }
+ return animateMove(viewHolder,
+ preInfo.left, preInfo.top, postInfo.left, postInfo.top);
+ }
+ dispatchMoveFinished(viewHolder);
+ return false;
+ }
+
+ @Override
+ public boolean animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder,
+ @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
+ if (DEBUG) {
+ Log.d(TAG, "CHANGED: " + oldHolder + " with view " + oldHolder.itemView);
+ }
+ final int fromLeft = preInfo.left;
+ final int fromTop = preInfo.top;
+ final int toLeft, toTop;
+ if (newHolder.shouldIgnore()) {
+ toLeft = preInfo.left;
+ toTop = preInfo.top;
+ } else {
+ toLeft = postInfo.left;
+ toTop = postInfo.top;
+ }
+ return animateChange(oldHolder, newHolder, fromLeft, fromTop, toLeft, toTop);
+ }
+
+ /**
+ * Called when an item is removed from the RecyclerView. Implementors can choose
+ * whether and how to animate that change, but must always call
+ * {@link #dispatchRemoveFinished(ViewHolder)} when done, either
+ * immediately (if no animation will occur) or after the animation actually finishes.
+ * The return value indicates whether an animation has been set up and whether the
+ * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
+ * next opportunity. This mechanism allows ItemAnimator to set up individual animations
+ * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
+ * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
+ * {@link #animateRemove(ViewHolder) animateRemove()}, and
+ * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
+ * then start the animations together in the later call to {@link #runPendingAnimations()}.
+ *
+ * <p>This method may also be called for disappearing items which continue to exist in the
+ * RecyclerView, but for which the system does not have enough information to animate
+ * them out of view. In that case, the default animation for removing items is run
+ * on those items as well.</p>
+ *
+ * @param holder The item that is being removed.
+ * @return true if a later call to {@link #runPendingAnimations()} is requested,
+ * false otherwise.
+ */
+ public abstract boolean animateRemove(ViewHolder holder);
+
+ /**
+ * Called when an item is added to the RecyclerView. Implementors can choose
+ * whether and how to animate that change, but must always call
+ * {@link #dispatchAddFinished(ViewHolder)} when done, either
+ * immediately (if no animation will occur) or after the animation actually finishes.
+ * The return value indicates whether an animation has been set up and whether the
+ * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
+ * next opportunity. This mechanism allows ItemAnimator to set up individual animations
+ * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
+ * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
+ * {@link #animateRemove(ViewHolder) animateRemove()}, and
+ * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
+ * then start the animations together in the later call to {@link #runPendingAnimations()}.
+ *
+ * <p>This method may also be called for appearing items which were already in the
+ * RecyclerView, but for which the system does not have enough information to animate
+ * them into view. In that case, the default animation for adding items is run
+ * on those items as well.</p>
+ *
+ * @param holder The item that is being added.
+ * @return true if a later call to {@link #runPendingAnimations()} is requested,
+ * false otherwise.
+ */
+ public abstract boolean animateAdd(ViewHolder holder);
+
+ /**
+ * Called when an item is moved in the RecyclerView. Implementors can choose
+ * whether and how to animate that change, but must always call
+ * {@link #dispatchMoveFinished(ViewHolder)} when done, either
+ * immediately (if no animation will occur) or after the animation actually finishes.
+ * The return value indicates whether an animation has been set up and whether the
+ * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
+ * next opportunity. This mechanism allows ItemAnimator to set up individual animations
+ * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
+ * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
+ * {@link #animateRemove(ViewHolder) animateRemove()}, and
+ * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
+ * then start the animations together in the later call to {@link #runPendingAnimations()}.
+ *
+ * @param holder The item that is being moved.
+ * @return true if a later call to {@link #runPendingAnimations()} is requested,
+ * false otherwise.
+ */
+ public abstract boolean animateMove(ViewHolder holder, int fromX, int fromY,
+ int toX, int toY);
+
+ /**
+ * Called when an item is changed in the RecyclerView, as indicated by a call to
+ * {@link Adapter#notifyItemChanged(int)} or
+ * {@link Adapter#notifyItemRangeChanged(int, int)}.
+ * <p>
+ * Implementers can choose whether and how to animate changes, but must always call
+ * {@link #dispatchChangeFinished(ViewHolder, boolean)} for each non-null distinct ViewHolder,
+ * either immediately (if no animation will occur) or after the animation actually finishes.
+ * If the {@code oldHolder} is the same ViewHolder as the {@code newHolder}, you must call
+ * {@link #dispatchChangeFinished(ViewHolder, boolean)} once and only once. In that case, the
+ * second parameter of {@code dispatchChangeFinished} is ignored.
+ * <p>
+ * The return value indicates whether an animation has been set up and whether the
+ * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
+ * next opportunity. This mechanism allows ItemAnimator to set up individual animations
+ * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
+ * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
+ * {@link #animateRemove(ViewHolder) animateRemove()}, and
+ * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
+ * then start the animations together in the later call to {@link #runPendingAnimations()}.
+ *
+ * @param oldHolder The original item that changed.
+ * @param newHolder The new item that was created with the changed content. Might be null
+ * @param fromLeft Left of the old view holder
+ * @param fromTop Top of the old view holder
+ * @param toLeft Left of the new view holder
+ * @param toTop Top of the new view holder
+ * @return true if a later call to {@link #runPendingAnimations()} is requested,
+ * false otherwise.
+ */
+ public abstract boolean animateChange(ViewHolder oldHolder,
+ ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop);
+
+ /**
+ * Method to be called by subclasses when a remove animation is done.
+ *
+ * @param item The item which has been removed
+ * @see RecyclerView.ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo,
+ * ItemHolderInfo)
+ */
+ public final void dispatchRemoveFinished(ViewHolder item) {
+ onRemoveFinished(item);
+ dispatchAnimationFinished(item);
+ }
+
+ /**
+ * Method to be called by subclasses when a move animation is done.
+ *
+ * @param item The item which has been moved
+ * @see RecyclerView.ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo,
+ * ItemHolderInfo)
+ * @see RecyclerView.ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * @see RecyclerView.ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ */
+ public final void dispatchMoveFinished(ViewHolder item) {
+ onMoveFinished(item);
+ dispatchAnimationFinished(item);
+ }
+
+ /**
+ * Method to be called by subclasses when an add animation is done.
+ *
+ * @param item The item which has been added
+ */
+ public final void dispatchAddFinished(ViewHolder item) {
+ onAddFinished(item);
+ dispatchAnimationFinished(item);
+ }
+
+ /**
+ * Method to be called by subclasses when a change animation is done.
+ *
+ * @param item The item which has been changed (this method must be called for
+ * each non-null ViewHolder passed into
+ * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}).
+ * @param oldItem true if this is the old item that was changed, false if
+ * it is the new item that replaced the old item.
+ * @see #animateChange(ViewHolder, ViewHolder, int, int, int, int)
+ */
+ public final void dispatchChangeFinished(ViewHolder item, boolean oldItem) {
+ onChangeFinished(item, oldItem);
+ dispatchAnimationFinished(item);
+ }
+
+ /**
+ * Method to be called by subclasses when a remove animation is being started.
+ *
+ * @param item The item being removed
+ */
+ public final void dispatchRemoveStarting(ViewHolder item) {
+ onRemoveStarting(item);
+ }
+
+ /**
+ * Method to be called by subclasses when a move animation is being started.
+ *
+ * @param item The item being moved
+ */
+ public final void dispatchMoveStarting(ViewHolder item) {
+ onMoveStarting(item);
+ }
+
+ /**
+ * Method to be called by subclasses when an add animation is being started.
+ *
+ * @param item The item being added
+ */
+ public final void dispatchAddStarting(ViewHolder item) {
+ onAddStarting(item);
+ }
+
+ /**
+ * Method to be called by subclasses when a change animation is being started.
+ *
+ * @param item The item which has been changed (this method must be called for
+ * each non-null ViewHolder passed into
+ * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}).
+ * @param oldItem true if this is the old item that was changed, false if
+ * it is the new item that replaced the old item.
+ */
+ public final void dispatchChangeStarting(ViewHolder item, boolean oldItem) {
+ onChangeStarting(item, oldItem);
+ }
+
+ /**
+ * Called when a remove animation is being started on the given ViewHolder.
+ * The default implementation does nothing. Subclasses may wish to override
+ * this method to handle any ViewHolder-specific operations linked to animation
+ * lifecycles.
+ *
+ * @param item The ViewHolder being animated.
+ */
+ @SuppressWarnings("UnusedParameters")
+ public void onRemoveStarting(ViewHolder item) {
+ }
+
+ /**
+ * Called when a remove animation has ended on the given ViewHolder.
+ * The default implementation does nothing. Subclasses may wish to override
+ * this method to handle any ViewHolder-specific operations linked to animation
+ * lifecycles.
+ *
+ * @param item The ViewHolder being animated.
+ */
+ public void onRemoveFinished(ViewHolder item) {
+ }
+
+ /**
+ * Called when an add animation is being started on the given ViewHolder.
+ * The default implementation does nothing. Subclasses may wish to override
+ * this method to handle any ViewHolder-specific operations linked to animation
+ * lifecycles.
+ *
+ * @param item The ViewHolder being animated.
+ */
+ @SuppressWarnings("UnusedParameters")
+ public void onAddStarting(ViewHolder item) {
+ }
+
+ /**
+ * Called when an add animation has ended on the given ViewHolder.
+ * The default implementation does nothing. Subclasses may wish to override
+ * this method to handle any ViewHolder-specific operations linked to animation
+ * lifecycles.
+ *
+ * @param item The ViewHolder being animated.
+ */
+ public void onAddFinished(ViewHolder item) {
+ }
+
+ /**
+ * Called when a move animation is being started on the given ViewHolder.
+ * The default implementation does nothing. Subclasses may wish to override
+ * this method to handle any ViewHolder-specific operations linked to animation
+ * lifecycles.
+ *
+ * @param item The ViewHolder being animated.
+ */
+ @SuppressWarnings("UnusedParameters")
+ public void onMoveStarting(ViewHolder item) {
+ }
+
+ /**
+ * Called when a move animation has ended on the given ViewHolder.
+ * The default implementation does nothing. Subclasses may wish to override
+ * this method to handle any ViewHolder-specific operations linked to animation
+ * lifecycles.
+ *
+ * @param item The ViewHolder being animated.
+ */
+ public void onMoveFinished(ViewHolder item) {
+ }
+
+ /**
+ * Called when a change animation is being started on the given ViewHolder.
+ * The default implementation does nothing. Subclasses may wish to override
+ * this method to handle any ViewHolder-specific operations linked to animation
+ * lifecycles.
+ *
+ * @param item The ViewHolder being animated.
+ * @param oldItem true if this is the old item that was changed, false if
+ * it is the new item that replaced the old item.
+ */
+ @SuppressWarnings("UnusedParameters")
+ public void onChangeStarting(ViewHolder item, boolean oldItem) {
+ }
+
+ /**
+ * Called when a change animation has ended on the given ViewHolder.
+ * The default implementation does nothing. Subclasses may wish to override
+ * this method to handle any ViewHolder-specific operations linked to animation
+ * lifecycles.
+ *
+ * @param item The ViewHolder being animated.
+ * @param oldItem true if this is the old item that was changed, false if
+ * it is the new item that replaced the old item.
+ */
+ public void onChangeFinished(ViewHolder item, boolean oldItem) {
+ }
+}
+
diff --git a/core/java/com/android/internal/widget/ViewInfoStore.java b/core/java/com/android/internal/widget/ViewInfoStore.java
new file mode 100644
index 0000000..6784a85
--- /dev/null
+++ b/core/java/com/android/internal/widget/ViewInfoStore.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.LongSparseArray;
+import android.util.Pools;
+
+import static com.android.internal.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
+import static com.android.internal.widget.RecyclerView.ViewHolder;
+import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR;
+import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_AND_DISAPPEAR;
+import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_PRE_AND_POST;
+import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_DISAPPEARED;
+import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_POST;
+import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_PRE;
+import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_PRE_AND_POST;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * This class abstracts all tracking for Views to run animations.
+ */
+class ViewInfoStore {
+
+ private static final boolean DEBUG = false;
+
+ /**
+ * View data records for pre-layout
+ */
+ @VisibleForTesting
+ final ArrayMap<ViewHolder, InfoRecord> mLayoutHolderMap = new ArrayMap<>();
+
+ @VisibleForTesting
+ final LongSparseArray<ViewHolder> mOldChangedHolders = new LongSparseArray<>();
+
+ /**
+ * Clears the state and all existing tracking data
+ */
+ void clear() {
+ mLayoutHolderMap.clear();
+ mOldChangedHolders.clear();
+ }
+
+ /**
+ * Adds the item information to the prelayout tracking
+ * @param holder The ViewHolder whose information is being saved
+ * @param info The information to save
+ */
+ void addToPreLayout(ViewHolder holder, ItemHolderInfo info) {
+ InfoRecord record = mLayoutHolderMap.get(holder);
+ if (record == null) {
+ record = InfoRecord.obtain();
+ mLayoutHolderMap.put(holder, record);
+ }
+ record.preInfo = info;
+ record.flags |= FLAG_PRE;
+ }
+
+ boolean isDisappearing(ViewHolder holder) {
+ final InfoRecord record = mLayoutHolderMap.get(holder);
+ return record != null && ((record.flags & FLAG_DISAPPEARED) != 0);
+ }
+
+ /**
+ * Finds the ItemHolderInfo for the given ViewHolder in preLayout list and removes it.
+ *
+ * @param vh The ViewHolder whose information is being queried
+ * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist
+ */
+ @Nullable
+ ItemHolderInfo popFromPreLayout(ViewHolder vh) {
+ return popFromLayoutStep(vh, FLAG_PRE);
+ }
+
+ /**
+ * Finds the ItemHolderInfo for the given ViewHolder in postLayout list and removes it.
+ *
+ * @param vh The ViewHolder whose information is being queried
+ * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist
+ */
+ @Nullable
+ ItemHolderInfo popFromPostLayout(ViewHolder vh) {
+ return popFromLayoutStep(vh, FLAG_POST);
+ }
+
+ private ItemHolderInfo popFromLayoutStep(ViewHolder vh, int flag) {
+ int index = mLayoutHolderMap.indexOfKey(vh);
+ if (index < 0) {
+ return null;
+ }
+ final InfoRecord record = mLayoutHolderMap.valueAt(index);
+ if (record != null && (record.flags & flag) != 0) {
+ record.flags &= ~flag;
+ final ItemHolderInfo info;
+ if (flag == FLAG_PRE) {
+ info = record.preInfo;
+ } else if (flag == FLAG_POST) {
+ info = record.postInfo;
+ } else {
+ throw new IllegalArgumentException("Must provide flag PRE or POST");
+ }
+ // if not pre-post flag is left, clear.
+ if ((record.flags & (FLAG_PRE | FLAG_POST)) == 0) {
+ mLayoutHolderMap.removeAt(index);
+ InfoRecord.recycle(record);
+ }
+ return info;
+ }
+ return null;
+ }
+
+ /**
+ * Adds the given ViewHolder to the oldChangeHolders list
+ * @param key The key to identify the ViewHolder.
+ * @param holder The ViewHolder to store
+ */
+ void addToOldChangeHolders(long key, ViewHolder holder) {
+ mOldChangedHolders.put(key, holder);
+ }
+
+ /**
+ * Adds the given ViewHolder to the appeared in pre layout list. These are Views added by the
+ * LayoutManager during a pre-layout pass. We distinguish them from other views that were
+ * already in the pre-layout so that ItemAnimator can choose to run a different animation for
+ * them.
+ *
+ * @param holder The ViewHolder to store
+ * @param info The information to save
+ */
+ void addToAppearedInPreLayoutHolders(ViewHolder holder, ItemHolderInfo info) {
+ InfoRecord record = mLayoutHolderMap.get(holder);
+ if (record == null) {
+ record = InfoRecord.obtain();
+ mLayoutHolderMap.put(holder, record);
+ }
+ record.flags |= FLAG_APPEAR;
+ record.preInfo = info;
+ }
+
+ /**
+ * Checks whether the given ViewHolder is in preLayout list
+ * @param viewHolder The ViewHolder to query
+ *
+ * @return True if the ViewHolder is present in preLayout, false otherwise
+ */
+ boolean isInPreLayout(ViewHolder viewHolder) {
+ final InfoRecord record = mLayoutHolderMap.get(viewHolder);
+ return record != null && (record.flags & FLAG_PRE) != 0;
+ }
+
+ /**
+ * Queries the oldChangeHolder list for the given key. If they are not tracked, simply returns
+ * null.
+ * @param key The key to be used to find the ViewHolder.
+ *
+ * @return A ViewHolder if exists or null if it does not exist.
+ */
+ ViewHolder getFromOldChangeHolders(long key) {
+ return mOldChangedHolders.get(key);
+ }
+
+ /**
+ * Adds the item information to the post layout list
+ * @param holder The ViewHolder whose information is being saved
+ * @param info The information to save
+ */
+ void addToPostLayout(ViewHolder holder, ItemHolderInfo info) {
+ InfoRecord record = mLayoutHolderMap.get(holder);
+ if (record == null) {
+ record = InfoRecord.obtain();
+ mLayoutHolderMap.put(holder, record);
+ }
+ record.postInfo = info;
+ record.flags |= FLAG_POST;
+ }
+
+ /**
+ * A ViewHolder might be added by the LayoutManager just to animate its disappearance.
+ * This list holds such items so that we can animate / recycle these ViewHolders properly.
+ *
+ * @param holder The ViewHolder which disappeared during a layout.
+ */
+ void addToDisappearedInLayout(ViewHolder holder) {
+ InfoRecord record = mLayoutHolderMap.get(holder);
+ if (record == null) {
+ record = InfoRecord.obtain();
+ mLayoutHolderMap.put(holder, record);
+ }
+ record.flags |= FLAG_DISAPPEARED;
+ }
+
+ /**
+ * Removes a ViewHolder from disappearing list.
+ * @param holder The ViewHolder to be removed from the disappearing list.
+ */
+ void removeFromDisappearedInLayout(ViewHolder holder) {
+ InfoRecord record = mLayoutHolderMap.get(holder);
+ if (record == null) {
+ return;
+ }
+ record.flags &= ~FLAG_DISAPPEARED;
+ }
+
+ void process(ProcessCallback callback) {
+ for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
+ final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
+ final InfoRecord record = mLayoutHolderMap.removeAt(index);
+ if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
+ // Appeared then disappeared. Not useful for animations.
+ callback.unused(viewHolder);
+ } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
+ // Set as "disappeared" by the LayoutManager (addDisappearingView)
+ if (record.preInfo == null) {
+ // similar to appear disappear but happened between different layout passes.
+ // this can happen when the layout manager is using auto-measure
+ callback.unused(viewHolder);
+ } else {
+ callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
+ }
+ } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
+ // Appeared in the layout but not in the adapter (e.g. entered the viewport)
+ callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
+ } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
+ // Persistent in both passes. Animate persistence
+ callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
+ } else if ((record.flags & FLAG_PRE) != 0) {
+ // Was in pre-layout, never been added to post layout
+ callback.processDisappeared(viewHolder, record.preInfo, null);
+ } else if ((record.flags & FLAG_POST) != 0) {
+ // Was not in pre-layout, been added to post layout
+ callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
+ } else if ((record.flags & FLAG_APPEAR) != 0) {
+ // Scrap view. RecyclerView will handle removing/recycling this.
+ } else if (DEBUG) {
+ throw new IllegalStateException("record without any reasonable flag combination:/");
+ }
+ InfoRecord.recycle(record);
+ }
+ }
+
+ /**
+ * Removes the ViewHolder from all list
+ * @param holder The ViewHolder which we should stop tracking
+ */
+ void removeViewHolder(ViewHolder holder) {
+ for (int i = mOldChangedHolders.size() - 1; i >= 0; i--) {
+ if (holder == mOldChangedHolders.valueAt(i)) {
+ mOldChangedHolders.removeAt(i);
+ break;
+ }
+ }
+ final InfoRecord info = mLayoutHolderMap.remove(holder);
+ if (info != null) {
+ InfoRecord.recycle(info);
+ }
+ }
+
+ void onDetach() {
+ InfoRecord.drainCache();
+ }
+
+ public void onViewDetached(ViewHolder viewHolder) {
+ removeFromDisappearedInLayout(viewHolder);
+ }
+
+ interface ProcessCallback {
+ void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
+ @Nullable ItemHolderInfo postInfo);
+ void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo,
+ ItemHolderInfo postInfo);
+ void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
+ @NonNull ItemHolderInfo postInfo);
+ void unused(ViewHolder holder);
+ }
+
+ static class InfoRecord {
+ // disappearing list
+ static final int FLAG_DISAPPEARED = 1;
+ // appear in pre layout list
+ static final int FLAG_APPEAR = 1 << 1;
+ // pre layout, this is necessary to distinguish null item info
+ static final int FLAG_PRE = 1 << 2;
+ // post layout, this is necessary to distinguish null item info
+ static final int FLAG_POST = 1 << 3;
+ static final int FLAG_APPEAR_AND_DISAPPEAR = FLAG_APPEAR | FLAG_DISAPPEARED;
+ static final int FLAG_PRE_AND_POST = FLAG_PRE | FLAG_POST;
+ static final int FLAG_APPEAR_PRE_AND_POST = FLAG_APPEAR | FLAG_PRE | FLAG_POST;
+ int flags;
+ @Nullable ItemHolderInfo preInfo;
+ @Nullable ItemHolderInfo postInfo;
+ static Pools.Pool<InfoRecord> sPool = new Pools.SimplePool<>(20);
+
+ private InfoRecord() {
+ }
+
+ static InfoRecord obtain() {
+ InfoRecord record = sPool.acquire();
+ return record == null ? new InfoRecord() : record;
+ }
+
+ static void recycle(InfoRecord record) {
+ record.flags = 0;
+ record.preInfo = null;
+ record.postInfo = null;
+ sPool.release(record);
+ }
+
+ static void drainCache() {
+ //noinspection StatementWithEmptyBody
+ while (sPool.acquire() != null);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/helper/ItemTouchHelper.java b/core/java/com/android/internal/widget/helper/ItemTouchHelper.java
new file mode 100644
index 0000000..9636ed8
--- /dev/null
+++ b/core/java/com/android/internal/widget/helper/ItemTouchHelper.java
@@ -0,0 +1,2391 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget.helper;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+import android.view.animation.Interpolator;
+
+import com.android.internal.R;
+import com.android.internal.widget.LinearLayoutManager;
+import com.android.internal.widget.RecyclerView;
+import com.android.internal.widget.RecyclerView.OnItemTouchListener;
+import com.android.internal.widget.RecyclerView.ViewHolder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.
+ * <p>
+ * It works with a RecyclerView and a Callback class, which configures what type of interactions
+ * are enabled and also receives events when user performs these actions.
+ * <p>
+ * Depending on which functionality you support, you should override
+ * {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)} and / or
+ * {@link Callback#onSwiped(ViewHolder, int)}.
+ * <p>
+ * This class is designed to work with any LayoutManager but for certain situations, it can be
+ * optimized for your custom LayoutManager by extending methods in the
+ * {@link ItemTouchHelper.Callback} class or implementing {@link ItemTouchHelper.ViewDropHandler}
+ * interface in your LayoutManager.
+ * <p>
+ * By default, ItemTouchHelper moves the items' translateX/Y properties to reposition them. On
+ * platforms older than Honeycomb, ItemTouchHelper uses canvas translations and View's visibility
+ * property to move items in response to touch events. You can customize these behaviors by
+ * overriding {@link Callback#onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int,
+ * boolean)}
+ * or {@link Callback#onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
+ * boolean)}.
+ * <p/>
+ * Most of the time, you only need to override <code>onChildDraw</code> but due to limitations of
+ * platform prior to Honeycomb, you may need to implement <code>onChildDrawOver</code> as well.
+ */
+public class ItemTouchHelper extends RecyclerView.ItemDecoration
+ implements RecyclerView.OnChildAttachStateChangeListener {
+
+ /**
+ * Up direction, used for swipe & drag control.
+ */
+ public static final int UP = 1;
+
+ /**
+ * Down direction, used for swipe & drag control.
+ */
+ public static final int DOWN = 1 << 1;
+
+ /**
+ * Left direction, used for swipe & drag control.
+ */
+ public static final int LEFT = 1 << 2;
+
+ /**
+ * Right direction, used for swipe & drag control.
+ */
+ public static final int RIGHT = 1 << 3;
+
+ // If you change these relative direction values, update Callback#convertToAbsoluteDirection,
+ // Callback#convertToRelativeDirection.
+ /**
+ * Horizontal start direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout
+ * direction. Used for swipe & drag control.
+ */
+ public static final int START = LEFT << 2;
+
+ /**
+ * Horizontal end direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout
+ * direction. Used for swipe & drag control.
+ */
+ public static final int END = RIGHT << 2;
+
+ /**
+ * ItemTouchHelper is in idle state. At this state, either there is no related motion event by
+ * the user or latest motion events have not yet triggered a swipe or drag.
+ */
+ public static final int ACTION_STATE_IDLE = 0;
+
+ /**
+ * A View is currently being swiped.
+ */
+ public static final int ACTION_STATE_SWIPE = 1;
+
+ /**
+ * A View is currently being dragged.
+ */
+ public static final int ACTION_STATE_DRAG = 2;
+
+ /**
+ * Animation type for views which are swiped successfully.
+ */
+ public static final int ANIMATION_TYPE_SWIPE_SUCCESS = 1 << 1;
+
+ /**
+ * Animation type for views which are not completely swiped thus will animate back to their
+ * original position.
+ */
+ public static final int ANIMATION_TYPE_SWIPE_CANCEL = 1 << 2;
+
+ /**
+ * Animation type for views that were dragged and now will animate to their final position.
+ */
+ public static final int ANIMATION_TYPE_DRAG = 1 << 3;
+
+ static final String TAG = "ItemTouchHelper";
+
+ static final boolean DEBUG = false;
+
+ static final int ACTIVE_POINTER_ID_NONE = -1;
+
+ static final int DIRECTION_FLAG_COUNT = 8;
+
+ private static final int ACTION_MODE_IDLE_MASK = (1 << DIRECTION_FLAG_COUNT) - 1;
+
+ static final int ACTION_MODE_SWIPE_MASK = ACTION_MODE_IDLE_MASK << DIRECTION_FLAG_COUNT;
+
+ static final int ACTION_MODE_DRAG_MASK = ACTION_MODE_SWIPE_MASK << DIRECTION_FLAG_COUNT;
+
+ /**
+ * The unit we are using to track velocity
+ */
+ private static final int PIXELS_PER_SECOND = 1000;
+
+ /**
+ * Views, whose state should be cleared after they are detached from RecyclerView.
+ * This is necessary after swipe dismissing an item. We wait until animator finishes its job
+ * to clean these views.
+ */
+ final List<View> mPendingCleanup = new ArrayList<View>();
+
+ /**
+ * Re-use array to calculate dx dy for a ViewHolder
+ */
+ private final float[] mTmpPosition = new float[2];
+
+ /**
+ * Currently selected view holder
+ */
+ ViewHolder mSelected = null;
+
+ /**
+ * The reference coordinates for the action start. For drag & drop, this is the time long
+ * press is completed vs for swipe, this is the initial touch point.
+ */
+ float mInitialTouchX;
+
+ float mInitialTouchY;
+
+ /**
+ * Set when ItemTouchHelper is assigned to a RecyclerView.
+ */
+ float mSwipeEscapeVelocity;
+
+ /**
+ * Set when ItemTouchHelper is assigned to a RecyclerView.
+ */
+ float mMaxSwipeVelocity;
+
+ /**
+ * The diff between the last event and initial touch.
+ */
+ float mDx;
+
+ float mDy;
+
+ /**
+ * The coordinates of the selected view at the time it is selected. We record these values
+ * when action starts so that we can consistently position it even if LayoutManager moves the
+ * View.
+ */
+ float mSelectedStartX;
+
+ float mSelectedStartY;
+
+ /**
+ * The pointer we are tracking.
+ */
+ int mActivePointerId = ACTIVE_POINTER_ID_NONE;
+
+ /**
+ * Developer callback which controls the behavior of ItemTouchHelper.
+ */
+ Callback mCallback;
+
+ /**
+ * Current mode.
+ */
+ int mActionState = ACTION_STATE_IDLE;
+
+ /**
+ * The direction flags obtained from unmasking
+ * {@link Callback#getAbsoluteMovementFlags(RecyclerView, ViewHolder)} for the current
+ * action state.
+ */
+ int mSelectedFlags;
+
+ /**
+ * When a View is dragged or swiped and needs to go back to where it was, we create a Recover
+ * Animation and animate it to its location using this custom Animator, instead of using
+ * framework Animators.
+ * Using framework animators has the side effect of clashing with ItemAnimator, creating
+ * jumpy UIs.
+ */
+ List<RecoverAnimation> mRecoverAnimations = new ArrayList<RecoverAnimation>();
+
+ private int mSlop;
+
+ RecyclerView mRecyclerView;
+
+ /**
+ * When user drags a view to the edge, we start scrolling the LayoutManager as long as View
+ * is partially out of bounds.
+ */
+ final Runnable mScrollRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mSelected != null && scrollIfNecessary()) {
+ if (mSelected != null) { //it might be lost during scrolling
+ moveIfNecessary(mSelected);
+ }
+ mRecyclerView.removeCallbacks(mScrollRunnable);
+ mRecyclerView.postOnAnimation(this);
+ }
+ }
+ };
+
+ /**
+ * Used for detecting fling swipe
+ */
+ VelocityTracker mVelocityTracker;
+
+ //re-used list for selecting a swap target
+ private List<ViewHolder> mSwapTargets;
+
+ //re used for for sorting swap targets
+ private List<Integer> mDistances;
+
+ /**
+ * If drag & drop is supported, we use child drawing order to bring them to front.
+ */
+ private RecyclerView.ChildDrawingOrderCallback mChildDrawingOrderCallback = null;
+
+ /**
+ * This keeps a reference to the child dragged by the user. Even after user stops dragging,
+ * until view reaches its final position (end of recover animation), we keep a reference so
+ * that it can be drawn above other children.
+ */
+ View mOverdrawChild = null;
+
+ /**
+ * We cache the position of the overdraw child to avoid recalculating it each time child
+ * position callback is called. This value is invalidated whenever a child is attached or
+ * detached.
+ */
+ int mOverdrawChildPosition = -1;
+
+ /**
+ * Used to detect long press.
+ */
+ GestureDetector mGestureDetector;
+
+ private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() {
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) {
+ mGestureDetector.onTouchEvent(event);
+ if (DEBUG) {
+ Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event);
+ }
+ final int action = event.getActionMasked();
+ if (action == MotionEvent.ACTION_DOWN) {
+ mActivePointerId = event.getPointerId(0);
+ mInitialTouchX = event.getX();
+ mInitialTouchY = event.getY();
+ obtainVelocityTracker();
+ if (mSelected == null) {
+ final RecoverAnimation animation = findAnimation(event);
+ if (animation != null) {
+ mInitialTouchX -= animation.mX;
+ mInitialTouchY -= animation.mY;
+ endRecoverAnimation(animation.mViewHolder, true);
+ if (mPendingCleanup.remove(animation.mViewHolder.itemView)) {
+ mCallback.clearView(mRecyclerView, animation.mViewHolder);
+ }
+ select(animation.mViewHolder, animation.mActionState);
+ updateDxDy(event, mSelectedFlags, 0);
+ }
+ }
+ } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+ mActivePointerId = ACTIVE_POINTER_ID_NONE;
+ select(null, ACTION_STATE_IDLE);
+ } else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) {
+ // in a non scroll orientation, if distance change is above threshold, we
+ // can select the item
+ final int index = event.findPointerIndex(mActivePointerId);
+ if (DEBUG) {
+ Log.d(TAG, "pointer index " + index);
+ }
+ if (index >= 0) {
+ checkSelectForSwipe(action, event, index);
+ }
+ }
+ if (mVelocityTracker != null) {
+ mVelocityTracker.addMovement(event);
+ }
+ return mSelected != null;
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView recyclerView, MotionEvent event) {
+ mGestureDetector.onTouchEvent(event);
+ if (DEBUG) {
+ Log.d(TAG,
+ "on touch: x:" + mInitialTouchX + ",y:" + mInitialTouchY + ", :" + event);
+ }
+ if (mVelocityTracker != null) {
+ mVelocityTracker.addMovement(event);
+ }
+ if (mActivePointerId == ACTIVE_POINTER_ID_NONE) {
+ return;
+ }
+ final int action = event.getActionMasked();
+ final int activePointerIndex = event.findPointerIndex(mActivePointerId);
+ if (activePointerIndex >= 0) {
+ checkSelectForSwipe(action, event, activePointerIndex);
+ }
+ ViewHolder viewHolder = mSelected;
+ if (viewHolder == null) {
+ return;
+ }
+ switch (action) {
+ case MotionEvent.ACTION_MOVE: {
+ // Find the index of the active pointer and fetch its position
+ if (activePointerIndex >= 0) {
+ updateDxDy(event, mSelectedFlags, activePointerIndex);
+ moveIfNecessary(viewHolder);
+ mRecyclerView.removeCallbacks(mScrollRunnable);
+ mScrollRunnable.run();
+ mRecyclerView.invalidate();
+ }
+ break;
+ }
+ case MotionEvent.ACTION_CANCEL:
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ // fall through
+ case MotionEvent.ACTION_UP:
+ select(null, ACTION_STATE_IDLE);
+ mActivePointerId = ACTIVE_POINTER_ID_NONE;
+ break;
+ case MotionEvent.ACTION_POINTER_UP: {
+ final int pointerIndex = event.getActionIndex();
+ final int pointerId = event.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mActivePointerId = event.getPointerId(newPointerIndex);
+ updateDxDy(event, mSelectedFlags, pointerIndex);
+ }
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ if (!disallowIntercept) {
+ return;
+ }
+ select(null, ACTION_STATE_IDLE);
+ }
+ };
+
+ /**
+ * Temporary rect instance that is used when we need to lookup Item decorations.
+ */
+ private Rect mTmpRect;
+
+ /**
+ * When user started to drag scroll. Reset when we don't scroll
+ */
+ private long mDragScrollStartTimeInMs;
+
+ /**
+ * Creates an ItemTouchHelper that will work with the given Callback.
+ * <p>
+ * You can attach ItemTouchHelper to a RecyclerView via
+ * {@link #attachToRecyclerView(RecyclerView)}. Upon attaching, it will add an item decoration,
+ * an onItemTouchListener and a Child attach / detach listener to the RecyclerView.
+ *
+ * @param callback The Callback which controls the behavior of this touch helper.
+ */
+ public ItemTouchHelper(Callback callback) {
+ mCallback = callback;
+ }
+
+ private static boolean hitTest(View child, float x, float y, float left, float top) {
+ return x >= left
+ && x <= left + child.getWidth()
+ && y >= top
+ && y <= top + child.getHeight();
+ }
+
+ /**
+ * Attaches the ItemTouchHelper to the provided RecyclerView. If TouchHelper is already
+ * attached to a RecyclerView, it will first detach from the previous one. You can call this
+ * method with {@code null} to detach it from the current RecyclerView.
+ *
+ * @param recyclerView The RecyclerView instance to which you want to add this helper or
+ * {@code null} if you want to remove ItemTouchHelper from the current
+ * RecyclerView.
+ */
+ public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
+ if (mRecyclerView == recyclerView) {
+ return; // nothing to do
+ }
+ if (mRecyclerView != null) {
+ destroyCallbacks();
+ }
+ mRecyclerView = recyclerView;
+ if (mRecyclerView != null) {
+ final Resources resources = recyclerView.getResources();
+ mSwipeEscapeVelocity = resources
+ .getDimension(R.dimen.item_touch_helper_swipe_escape_velocity);
+ mMaxSwipeVelocity = resources
+ .getDimension(R.dimen.item_touch_helper_swipe_escape_max_velocity);
+ setupCallbacks();
+ }
+ }
+
+ private void setupCallbacks() {
+ ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());
+ mSlop = vc.getScaledTouchSlop();
+ mRecyclerView.addItemDecoration(this);
+ mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
+ mRecyclerView.addOnChildAttachStateChangeListener(this);
+ initGestureDetector();
+ }
+
+ private void destroyCallbacks() {
+ mRecyclerView.removeItemDecoration(this);
+ mRecyclerView.removeOnItemTouchListener(mOnItemTouchListener);
+ mRecyclerView.removeOnChildAttachStateChangeListener(this);
+ // clean all attached
+ final int recoverAnimSize = mRecoverAnimations.size();
+ for (int i = recoverAnimSize - 1; i >= 0; i--) {
+ final RecoverAnimation recoverAnimation = mRecoverAnimations.get(0);
+ mCallback.clearView(mRecyclerView, recoverAnimation.mViewHolder);
+ }
+ mRecoverAnimations.clear();
+ mOverdrawChild = null;
+ mOverdrawChildPosition = -1;
+ releaseVelocityTracker();
+ }
+
+ private void initGestureDetector() {
+ if (mGestureDetector != null) {
+ return;
+ }
+ mGestureDetector = new GestureDetector(mRecyclerView.getContext(),
+ new ItemTouchHelperGestureListener());
+ }
+
+ private void getSelectedDxDy(float[] outPosition) {
+ if ((mSelectedFlags & (LEFT | RIGHT)) != 0) {
+ outPosition[0] = mSelectedStartX + mDx - mSelected.itemView.getLeft();
+ } else {
+ outPosition[0] = mSelected.itemView.getTranslationX();
+ }
+ if ((mSelectedFlags & (UP | DOWN)) != 0) {
+ outPosition[1] = mSelectedStartY + mDy - mSelected.itemView.getTop();
+ } else {
+ outPosition[1] = mSelected.itemView.getTranslationY();
+ }
+ }
+
+ @Override
+ public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ float dx = 0, dy = 0;
+ if (mSelected != null) {
+ getSelectedDxDy(mTmpPosition);
+ dx = mTmpPosition[0];
+ dy = mTmpPosition[1];
+ }
+ mCallback.onDrawOver(c, parent, mSelected,
+ mRecoverAnimations, mActionState, dx, dy);
+ }
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ // we don't know if RV changed something so we should invalidate this index.
+ mOverdrawChildPosition = -1;
+ float dx = 0, dy = 0;
+ if (mSelected != null) {
+ getSelectedDxDy(mTmpPosition);
+ dx = mTmpPosition[0];
+ dy = mTmpPosition[1];
+ }
+ mCallback.onDraw(c, parent, mSelected,
+ mRecoverAnimations, mActionState, dx, dy);
+ }
+
+ /**
+ * Starts dragging or swiping the given View. Call with null if you want to clear it.
+ *
+ * @param selected The ViewHolder to drag or swipe. Can be null if you want to cancel the
+ * current action
+ * @param actionState The type of action
+ */
+ void select(ViewHolder selected, int actionState) {
+ if (selected == mSelected && actionState == mActionState) {
+ return;
+ }
+ mDragScrollStartTimeInMs = Long.MIN_VALUE;
+ final int prevActionState = mActionState;
+ // prevent duplicate animations
+ endRecoverAnimation(selected, true);
+ mActionState = actionState;
+ if (actionState == ACTION_STATE_DRAG) {
+ // we remove after animation is complete. this means we only elevate the last drag
+ // child but that should perform good enough as it is very hard to start dragging a
+ // new child before the previous one settles.
+ mOverdrawChild = selected.itemView;
+ addChildDrawingOrderCallback();
+ }
+ int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState))
+ - 1;
+ boolean preventLayout = false;
+
+ if (mSelected != null) {
+ final ViewHolder prevSelected = mSelected;
+ if (prevSelected.itemView.getParent() != null) {
+ final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0
+ : swipeIfNecessary(prevSelected);
+ releaseVelocityTracker();
+ // find where we should animate to
+ final float targetTranslateX, targetTranslateY;
+ int animationType;
+ switch (swipeDir) {
+ case LEFT:
+ case RIGHT:
+ case START:
+ case END:
+ targetTranslateY = 0;
+ targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth();
+ break;
+ case UP:
+ case DOWN:
+ targetTranslateX = 0;
+ targetTranslateY = Math.signum(mDy) * mRecyclerView.getHeight();
+ break;
+ default:
+ targetTranslateX = 0;
+ targetTranslateY = 0;
+ }
+ if (prevActionState == ACTION_STATE_DRAG) {
+ animationType = ANIMATION_TYPE_DRAG;
+ } else if (swipeDir > 0) {
+ animationType = ANIMATION_TYPE_SWIPE_SUCCESS;
+ } else {
+ animationType = ANIMATION_TYPE_SWIPE_CANCEL;
+ }
+ getSelectedDxDy(mTmpPosition);
+ final float currentTranslateX = mTmpPosition[0];
+ final float currentTranslateY = mTmpPosition[1];
+ final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType,
+ prevActionState, currentTranslateX, currentTranslateY,
+ targetTranslateX, targetTranslateY) {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (this.mOverridden) {
+ return;
+ }
+ if (swipeDir <= 0) {
+ // this is a drag or failed swipe. recover immediately
+ mCallback.clearView(mRecyclerView, prevSelected);
+ // full cleanup will happen on onDrawOver
+ } else {
+ // wait until remove animation is complete.
+ mPendingCleanup.add(prevSelected.itemView);
+ mIsPendingCleanup = true;
+ if (swipeDir > 0) {
+ // Animation might be ended by other animators during a layout.
+ // We defer callback to avoid editing adapter during a layout.
+ postDispatchSwipe(this, swipeDir);
+ }
+ }
+ // removed from the list after it is drawn for the last time
+ if (mOverdrawChild == prevSelected.itemView) {
+ removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
+ }
+ }
+ };
+ final long duration = mCallback.getAnimationDuration(mRecyclerView, animationType,
+ targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY);
+ rv.setDuration(duration);
+ mRecoverAnimations.add(rv);
+ rv.start();
+ preventLayout = true;
+ } else {
+ removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
+ mCallback.clearView(mRecyclerView, prevSelected);
+ }
+ mSelected = null;
+ }
+ if (selected != null) {
+ mSelectedFlags =
+ (mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask)
+ >> (mActionState * DIRECTION_FLAG_COUNT);
+ mSelectedStartX = selected.itemView.getLeft();
+ mSelectedStartY = selected.itemView.getTop();
+ mSelected = selected;
+
+ if (actionState == ACTION_STATE_DRAG) {
+ mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ }
+ }
+ final ViewParent rvParent = mRecyclerView.getParent();
+ if (rvParent != null) {
+ rvParent.requestDisallowInterceptTouchEvent(mSelected != null);
+ }
+ if (!preventLayout) {
+ mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout();
+ }
+ mCallback.onSelectedChanged(mSelected, mActionState);
+ mRecyclerView.invalidate();
+ }
+
+ void postDispatchSwipe(final RecoverAnimation anim, final int swipeDir) {
+ // wait until animations are complete.
+ mRecyclerView.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mRecyclerView != null && mRecyclerView.isAttachedToWindow()
+ && !anim.mOverridden
+ && anim.mViewHolder.getAdapterPosition() != RecyclerView.NO_POSITION) {
+ final RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator();
+ // if animator is running or we have other active recover animations, we try
+ // not to call onSwiped because DefaultItemAnimator is not good at merging
+ // animations. Instead, we wait and batch.
+ if ((animator == null || !animator.isRunning(null))
+ && !hasRunningRecoverAnim()) {
+ mCallback.onSwiped(anim.mViewHolder, swipeDir);
+ } else {
+ mRecyclerView.post(this);
+ }
+ }
+ }
+ });
+ }
+
+ boolean hasRunningRecoverAnim() {
+ final int size = mRecoverAnimations.size();
+ for (int i = 0; i < size; i++) {
+ if (!mRecoverAnimations.get(i).mEnded) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * If user drags the view to the edge, trigger a scroll if necessary.
+ */
+ boolean scrollIfNecessary() {
+ if (mSelected == null) {
+ mDragScrollStartTimeInMs = Long.MIN_VALUE;
+ return false;
+ }
+ final long now = System.currentTimeMillis();
+ final long scrollDuration = mDragScrollStartTimeInMs
+ == Long.MIN_VALUE ? 0 : now - mDragScrollStartTimeInMs;
+ RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
+ if (mTmpRect == null) {
+ mTmpRect = new Rect();
+ }
+ int scrollX = 0;
+ int scrollY = 0;
+ lm.calculateItemDecorationsForChild(mSelected.itemView, mTmpRect);
+ if (lm.canScrollHorizontally()) {
+ int curX = (int) (mSelectedStartX + mDx);
+ final int leftDiff = curX - mTmpRect.left - mRecyclerView.getPaddingLeft();
+ if (mDx < 0 && leftDiff < 0) {
+ scrollX = leftDiff;
+ } else if (mDx > 0) {
+ final int rightDiff =
+ curX + mSelected.itemView.getWidth() + mTmpRect.right
+ - (mRecyclerView.getWidth() - mRecyclerView.getPaddingRight());
+ if (rightDiff > 0) {
+ scrollX = rightDiff;
+ }
+ }
+ }
+ if (lm.canScrollVertically()) {
+ int curY = (int) (mSelectedStartY + mDy);
+ final int topDiff = curY - mTmpRect.top - mRecyclerView.getPaddingTop();
+ if (mDy < 0 && topDiff < 0) {
+ scrollY = topDiff;
+ } else if (mDy > 0) {
+ final int bottomDiff = curY + mSelected.itemView.getHeight() + mTmpRect.bottom
+ - (mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom());
+ if (bottomDiff > 0) {
+ scrollY = bottomDiff;
+ }
+ }
+ }
+ if (scrollX != 0) {
+ scrollX = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
+ mSelected.itemView.getWidth(), scrollX,
+ mRecyclerView.getWidth(), scrollDuration);
+ }
+ if (scrollY != 0) {
+ scrollY = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
+ mSelected.itemView.getHeight(), scrollY,
+ mRecyclerView.getHeight(), scrollDuration);
+ }
+ if (scrollX != 0 || scrollY != 0) {
+ if (mDragScrollStartTimeInMs == Long.MIN_VALUE) {
+ mDragScrollStartTimeInMs = now;
+ }
+ mRecyclerView.scrollBy(scrollX, scrollY);
+ return true;
+ }
+ mDragScrollStartTimeInMs = Long.MIN_VALUE;
+ return false;
+ }
+
+ private List<ViewHolder> findSwapTargets(ViewHolder viewHolder) {
+ if (mSwapTargets == null) {
+ mSwapTargets = new ArrayList<ViewHolder>();
+ mDistances = new ArrayList<Integer>();
+ } else {
+ mSwapTargets.clear();
+ mDistances.clear();
+ }
+ final int margin = mCallback.getBoundingBoxMargin();
+ final int left = Math.round(mSelectedStartX + mDx) - margin;
+ final int top = Math.round(mSelectedStartY + mDy) - margin;
+ final int right = left + viewHolder.itemView.getWidth() + 2 * margin;
+ final int bottom = top + viewHolder.itemView.getHeight() + 2 * margin;
+ final int centerX = (left + right) / 2;
+ final int centerY = (top + bottom) / 2;
+ final RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
+ final int childCount = lm.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View other = lm.getChildAt(i);
+ if (other == viewHolder.itemView) {
+ continue; //myself!
+ }
+ if (other.getBottom() < top || other.getTop() > bottom
+ || other.getRight() < left || other.getLeft() > right) {
+ continue;
+ }
+ final ViewHolder otherVh = mRecyclerView.getChildViewHolder(other);
+ if (mCallback.canDropOver(mRecyclerView, mSelected, otherVh)) {
+ // find the index to add
+ final int dx = Math.abs(centerX - (other.getLeft() + other.getRight()) / 2);
+ final int dy = Math.abs(centerY - (other.getTop() + other.getBottom()) / 2);
+ final int dist = dx * dx + dy * dy;
+
+ int pos = 0;
+ final int cnt = mSwapTargets.size();
+ for (int j = 0; j < cnt; j++) {
+ if (dist > mDistances.get(j)) {
+ pos++;
+ } else {
+ break;
+ }
+ }
+ mSwapTargets.add(pos, otherVh);
+ mDistances.add(pos, dist);
+ }
+ }
+ return mSwapTargets;
+ }
+
+ /**
+ * Checks if we should swap w/ another view holder.
+ */
+ void moveIfNecessary(ViewHolder viewHolder) {
+ if (mRecyclerView.isLayoutRequested()) {
+ return;
+ }
+ if (mActionState != ACTION_STATE_DRAG) {
+ return;
+ }
+
+ final float threshold = mCallback.getMoveThreshold(viewHolder);
+ final int x = (int) (mSelectedStartX + mDx);
+ final int y = (int) (mSelectedStartY + mDy);
+ if (Math.abs(y - viewHolder.itemView.getTop()) < viewHolder.itemView.getHeight() * threshold
+ && Math.abs(x - viewHolder.itemView.getLeft())
+ < viewHolder.itemView.getWidth() * threshold) {
+ return;
+ }
+ List<ViewHolder> swapTargets = findSwapTargets(viewHolder);
+ if (swapTargets.size() == 0) {
+ return;
+ }
+ // may swap.
+ ViewHolder target = mCallback.chooseDropTarget(viewHolder, swapTargets, x, y);
+ if (target == null) {
+ mSwapTargets.clear();
+ mDistances.clear();
+ return;
+ }
+ final int toPosition = target.getAdapterPosition();
+ final int fromPosition = viewHolder.getAdapterPosition();
+ if (mCallback.onMove(mRecyclerView, viewHolder, target)) {
+ // keep target visible
+ mCallback.onMoved(mRecyclerView, viewHolder, fromPosition,
+ target, toPosition, x, y);
+ }
+ }
+
+ @Override
+ public void onChildViewAttachedToWindow(View view) {
+ }
+
+ @Override
+ public void onChildViewDetachedFromWindow(View view) {
+ removeChildDrawingOrderCallbackIfNecessary(view);
+ final ViewHolder holder = mRecyclerView.getChildViewHolder(view);
+ if (holder == null) {
+ return;
+ }
+ if (mSelected != null && holder == mSelected) {
+ select(null, ACTION_STATE_IDLE);
+ } else {
+ endRecoverAnimation(holder, false); // this may push it into pending cleanup list.
+ if (mPendingCleanup.remove(holder.itemView)) {
+ mCallback.clearView(mRecyclerView, holder);
+ }
+ }
+ }
+
+ /**
+ * Returns the animation type or 0 if cannot be found.
+ */
+ int endRecoverAnimation(ViewHolder viewHolder, boolean override) {
+ final int recoverAnimSize = mRecoverAnimations.size();
+ for (int i = recoverAnimSize - 1; i >= 0; i--) {
+ final RecoverAnimation anim = mRecoverAnimations.get(i);
+ if (anim.mViewHolder == viewHolder) {
+ anim.mOverridden |= override;
+ if (!anim.mEnded) {
+ anim.cancel();
+ }
+ mRecoverAnimations.remove(i);
+ return anim.mAnimationType;
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ outRect.setEmpty();
+ }
+
+ void obtainVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ }
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+
+ private void releaseVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private ViewHolder findSwipedView(MotionEvent motionEvent) {
+ final RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
+ if (mActivePointerId == ACTIVE_POINTER_ID_NONE) {
+ return null;
+ }
+ final int pointerIndex = motionEvent.findPointerIndex(mActivePointerId);
+ final float dx = motionEvent.getX(pointerIndex) - mInitialTouchX;
+ final float dy = motionEvent.getY(pointerIndex) - mInitialTouchY;
+ final float absDx = Math.abs(dx);
+ final float absDy = Math.abs(dy);
+
+ if (absDx < mSlop && absDy < mSlop) {
+ return null;
+ }
+ if (absDx > absDy && lm.canScrollHorizontally()) {
+ return null;
+ } else if (absDy > absDx && lm.canScrollVertically()) {
+ return null;
+ }
+ View child = findChildView(motionEvent);
+ if (child == null) {
+ return null;
+ }
+ return mRecyclerView.getChildViewHolder(child);
+ }
+
+ /**
+ * Checks whether we should select a View for swiping.
+ */
+ boolean checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) {
+ if (mSelected != null || action != MotionEvent.ACTION_MOVE
+ || mActionState == ACTION_STATE_DRAG || !mCallback.isItemViewSwipeEnabled()) {
+ return false;
+ }
+ if (mRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) {
+ return false;
+ }
+ final ViewHolder vh = findSwipedView(motionEvent);
+ if (vh == null) {
+ return false;
+ }
+ final int movementFlags = mCallback.getAbsoluteMovementFlags(mRecyclerView, vh);
+
+ final int swipeFlags = (movementFlags & ACTION_MODE_SWIPE_MASK)
+ >> (DIRECTION_FLAG_COUNT * ACTION_STATE_SWIPE);
+
+ if (swipeFlags == 0) {
+ return false;
+ }
+
+ // mDx and mDy are only set in allowed directions. We use custom x/y here instead of
+ // updateDxDy to avoid swiping if user moves more in the other direction
+ final float x = motionEvent.getX(pointerIndex);
+ final float y = motionEvent.getY(pointerIndex);
+
+ // Calculate the distance moved
+ final float dx = x - mInitialTouchX;
+ final float dy = y - mInitialTouchY;
+ // swipe target is chose w/o applying flags so it does not really check if swiping in that
+ // direction is allowed. This why here, we use mDx mDy to check slope value again.
+ final float absDx = Math.abs(dx);
+ final float absDy = Math.abs(dy);
+
+ if (absDx < mSlop && absDy < mSlop) {
+ return false;
+ }
+ if (absDx > absDy) {
+ if (dx < 0 && (swipeFlags & LEFT) == 0) {
+ return false;
+ }
+ if (dx > 0 && (swipeFlags & RIGHT) == 0) {
+ return false;
+ }
+ } else {
+ if (dy < 0 && (swipeFlags & UP) == 0) {
+ return false;
+ }
+ if (dy > 0 && (swipeFlags & DOWN) == 0) {
+ return false;
+ }
+ }
+ mDx = mDy = 0f;
+ mActivePointerId = motionEvent.getPointerId(0);
+ select(vh, ACTION_STATE_SWIPE);
+ return true;
+ }
+
+ View findChildView(MotionEvent event) {
+ // first check elevated views, if none, then call RV
+ final float x = event.getX();
+ final float y = event.getY();
+ if (mSelected != null) {
+ final View selectedView = mSelected.itemView;
+ if (hitTest(selectedView, x, y, mSelectedStartX + mDx, mSelectedStartY + mDy)) {
+ return selectedView;
+ }
+ }
+ for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
+ final RecoverAnimation anim = mRecoverAnimations.get(i);
+ final View view = anim.mViewHolder.itemView;
+ if (hitTest(view, x, y, anim.mX, anim.mY)) {
+ return view;
+ }
+ }
+ return mRecyclerView.findChildViewUnder(x, y);
+ }
+
+ /**
+ * Starts dragging the provided ViewHolder. By default, ItemTouchHelper starts a drag when a
+ * View is long pressed. You can disable that behavior by overriding
+ * {@link ItemTouchHelper.Callback#isLongPressDragEnabled()}.
+ * <p>
+ * For this method to work:
+ * <ul>
+ * <li>The provided ViewHolder must be a child of the RecyclerView to which this
+ * ItemTouchHelper
+ * is attached.</li>
+ * <li>{@link ItemTouchHelper.Callback} must have dragging enabled.</li>
+ * <li>There must be a previous touch event that was reported to the ItemTouchHelper
+ * through RecyclerView's ItemTouchListener mechanism. As long as no other ItemTouchListener
+ * grabs previous events, this should work as expected.</li>
+ * </ul>
+ *
+ * For example, if you would like to let your user to be able to drag an Item by touching one
+ * of its descendants, you may implement it as follows:
+ * <pre>
+ * viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() {
+ * public boolean onTouch(View v, MotionEvent event) {
+ * if (MotionEvent.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
+ * mItemTouchHelper.startDrag(viewHolder);
+ * }
+ * return false;
+ * }
+ * });
+ * </pre>
+ * <p>
+ *
+ * @param viewHolder The ViewHolder to start dragging. It must be a direct child of
+ * RecyclerView.
+ * @see ItemTouchHelper.Callback#isItemViewSwipeEnabled()
+ */
+ public void startDrag(ViewHolder viewHolder) {
+ if (!mCallback.hasDragFlag(mRecyclerView, viewHolder)) {
+ Log.e(TAG, "Start drag has been called but dragging is not enabled");
+ return;
+ }
+ if (viewHolder.itemView.getParent() != mRecyclerView) {
+ Log.e(TAG, "Start drag has been called with a view holder which is not a child of "
+ + "the RecyclerView which is controlled by this ItemTouchHelper.");
+ return;
+ }
+ obtainVelocityTracker();
+ mDx = mDy = 0f;
+ select(viewHolder, ACTION_STATE_DRAG);
+ }
+
+ /**
+ * Starts swiping the provided ViewHolder. By default, ItemTouchHelper starts swiping a View
+ * when user swipes their finger (or mouse pointer) over the View. You can disable this
+ * behavior
+ * by overriding {@link ItemTouchHelper.Callback}
+ * <p>
+ * For this method to work:
+ * <ul>
+ * <li>The provided ViewHolder must be a child of the RecyclerView to which this
+ * ItemTouchHelper is attached.</li>
+ * <li>{@link ItemTouchHelper.Callback} must have swiping enabled.</li>
+ * <li>There must be a previous touch event that was reported to the ItemTouchHelper
+ * through RecyclerView's ItemTouchListener mechanism. As long as no other ItemTouchListener
+ * grabs previous events, this should work as expected.</li>
+ * </ul>
+ *
+ * For example, if you would like to let your user to be able to swipe an Item by touching one
+ * of its descendants, you may implement it as follows:
+ * <pre>
+ * viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() {
+ * public boolean onTouch(View v, MotionEvent event) {
+ * if (MotionEvent.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
+ * mItemTouchHelper.startSwipe(viewHolder);
+ * }
+ * return false;
+ * }
+ * });
+ * </pre>
+ *
+ * @param viewHolder The ViewHolder to start swiping. It must be a direct child of
+ * RecyclerView.
+ */
+ public void startSwipe(ViewHolder viewHolder) {
+ if (!mCallback.hasSwipeFlag(mRecyclerView, viewHolder)) {
+ Log.e(TAG, "Start swipe has been called but swiping is not enabled");
+ return;
+ }
+ if (viewHolder.itemView.getParent() != mRecyclerView) {
+ Log.e(TAG, "Start swipe has been called with a view holder which is not a child of "
+ + "the RecyclerView controlled by this ItemTouchHelper.");
+ return;
+ }
+ obtainVelocityTracker();
+ mDx = mDy = 0f;
+ select(viewHolder, ACTION_STATE_SWIPE);
+ }
+
+ RecoverAnimation findAnimation(MotionEvent event) {
+ if (mRecoverAnimations.isEmpty()) {
+ return null;
+ }
+ View target = findChildView(event);
+ for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
+ final RecoverAnimation anim = mRecoverAnimations.get(i);
+ if (anim.mViewHolder.itemView == target) {
+ return anim;
+ }
+ }
+ return null;
+ }
+
+ void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) {
+ final float x = ev.getX(pointerIndex);
+ final float y = ev.getY(pointerIndex);
+
+ // Calculate the distance moved
+ mDx = x - mInitialTouchX;
+ mDy = y - mInitialTouchY;
+ if ((directionFlags & LEFT) == 0) {
+ mDx = Math.max(0, mDx);
+ }
+ if ((directionFlags & RIGHT) == 0) {
+ mDx = Math.min(0, mDx);
+ }
+ if ((directionFlags & UP) == 0) {
+ mDy = Math.max(0, mDy);
+ }
+ if ((directionFlags & DOWN) == 0) {
+ mDy = Math.min(0, mDy);
+ }
+ }
+
+ private int swipeIfNecessary(ViewHolder viewHolder) {
+ if (mActionState == ACTION_STATE_DRAG) {
+ return 0;
+ }
+ final int originalMovementFlags = mCallback.getMovementFlags(mRecyclerView, viewHolder);
+ final int absoluteMovementFlags = mCallback.convertToAbsoluteDirection(
+ originalMovementFlags,
+ mRecyclerView.getLayoutDirection());
+ final int flags = (absoluteMovementFlags
+ & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT);
+ if (flags == 0) {
+ return 0;
+ }
+ final int originalFlags = (originalMovementFlags
+ & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT);
+ int swipeDir;
+ if (Math.abs(mDx) > Math.abs(mDy)) {
+ if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) {
+ // if swipe dir is not in original flags, it should be the relative direction
+ if ((originalFlags & swipeDir) == 0) {
+ // convert to relative
+ return Callback.convertToRelativeDirection(swipeDir,
+ mRecyclerView.getLayoutDirection());
+ }
+ return swipeDir;
+ }
+ if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) {
+ return swipeDir;
+ }
+ } else {
+ if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) {
+ return swipeDir;
+ }
+ if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) {
+ // if swipe dir is not in original flags, it should be the relative direction
+ if ((originalFlags & swipeDir) == 0) {
+ // convert to relative
+ return Callback.convertToRelativeDirection(swipeDir,
+ mRecyclerView.getLayoutDirection());
+ }
+ return swipeDir;
+ }
+ }
+ return 0;
+ }
+
+ private int checkHorizontalSwipe(ViewHolder viewHolder, int flags) {
+ if ((flags & (LEFT | RIGHT)) != 0) {
+ final int dirFlag = mDx > 0 ? RIGHT : LEFT;
+ if (mVelocityTracker != null && mActivePointerId > -1) {
+ mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND,
+ mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity));
+ final float xVelocity = mVelocityTracker.getXVelocity(mActivePointerId);
+ final float yVelocity = mVelocityTracker.getYVelocity(mActivePointerId);
+ final int velDirFlag = xVelocity > 0f ? RIGHT : LEFT;
+ final float absXVelocity = Math.abs(xVelocity);
+ if ((velDirFlag & flags) != 0 && dirFlag == velDirFlag
+ && absXVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity)
+ && absXVelocity > Math.abs(yVelocity)) {
+ return velDirFlag;
+ }
+ }
+
+ final float threshold = mRecyclerView.getWidth() * mCallback
+ .getSwipeThreshold(viewHolder);
+
+ if ((flags & dirFlag) != 0 && Math.abs(mDx) > threshold) {
+ return dirFlag;
+ }
+ }
+ return 0;
+ }
+
+ private int checkVerticalSwipe(ViewHolder viewHolder, int flags) {
+ if ((flags & (UP | DOWN)) != 0) {
+ final int dirFlag = mDy > 0 ? DOWN : UP;
+ if (mVelocityTracker != null && mActivePointerId > -1) {
+ mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND,
+ mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity));
+ final float xVelocity = mVelocityTracker.getXVelocity(mActivePointerId);
+ final float yVelocity = mVelocityTracker.getYVelocity(mActivePointerId);
+ final int velDirFlag = yVelocity > 0f ? DOWN : UP;
+ final float absYVelocity = Math.abs(yVelocity);
+ if ((velDirFlag & flags) != 0 && velDirFlag == dirFlag
+ && absYVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity)
+ && absYVelocity > Math.abs(xVelocity)) {
+ return velDirFlag;
+ }
+ }
+
+ final float threshold = mRecyclerView.getHeight() * mCallback
+ .getSwipeThreshold(viewHolder);
+ if ((flags & dirFlag) != 0 && Math.abs(mDy) > threshold) {
+ return dirFlag;
+ }
+ }
+ return 0;
+ }
+
+ private void addChildDrawingOrderCallback() {
+ if (Build.VERSION.SDK_INT >= 21) {
+ return; // we use elevation on Lollipop
+ }
+ if (mChildDrawingOrderCallback == null) {
+ mChildDrawingOrderCallback = new RecyclerView.ChildDrawingOrderCallback() {
+ @Override
+ public int onGetChildDrawingOrder(int childCount, int i) {
+ if (mOverdrawChild == null) {
+ return i;
+ }
+ int childPosition = mOverdrawChildPosition;
+ if (childPosition == -1) {
+ childPosition = mRecyclerView.indexOfChild(mOverdrawChild);
+ mOverdrawChildPosition = childPosition;
+ }
+ if (i == childCount - 1) {
+ return childPosition;
+ }
+ return i < childPosition ? i : i + 1;
+ }
+ };
+ }
+ mRecyclerView.setChildDrawingOrderCallback(mChildDrawingOrderCallback);
+ }
+
+ void removeChildDrawingOrderCallbackIfNecessary(View view) {
+ if (view == mOverdrawChild) {
+ mOverdrawChild = null;
+ // only remove if we've added
+ if (mChildDrawingOrderCallback != null) {
+ mRecyclerView.setChildDrawingOrderCallback(null);
+ }
+ }
+ }
+
+ /**
+ * An interface which can be implemented by LayoutManager for better integration with
+ * {@link ItemTouchHelper}.
+ */
+ public interface ViewDropHandler {
+
+ /**
+ * Called by the {@link ItemTouchHelper} after a View is dropped over another View.
+ * <p>
+ * A LayoutManager should implement this interface to get ready for the upcoming move
+ * operation.
+ * <p>
+ * For example, LinearLayoutManager sets up a "scrollToPositionWithOffset" calls so that
+ * the View under drag will be used as an anchor View while calculating the next layout,
+ * making layout stay consistent.
+ *
+ * @param view The View which is being dragged. It is very likely that user is still
+ * dragging this View so there might be other
+ * {@link #prepareForDrop(View, View, int, int)} after this one.
+ * @param target The target view which is being dropped on.
+ * @param x The <code>left</code> offset of the View that is being dragged. This value
+ * includes the movement caused by the user.
+ * @param y The <code>top</code> offset of the View that is being dragged. This value
+ * includes the movement caused by the user.
+ */
+ void prepareForDrop(View view, View target, int x, int y);
+ }
+
+ /**
+ * This class is the contract between ItemTouchHelper and your application. It lets you control
+ * which touch behaviors are enabled per each ViewHolder and also receive callbacks when user
+ * performs these actions.
+ * <p>
+ * To control which actions user can take on each view, you should override
+ * {@link #getMovementFlags(RecyclerView, ViewHolder)} and return appropriate set
+ * of direction flags. ({@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link #END},
+ * {@link #UP}, {@link #DOWN}). You can use
+ * {@link #makeMovementFlags(int, int)} to easily construct it. Alternatively, you can use
+ * {@link SimpleCallback}.
+ * <p>
+ * If user drags an item, ItemTouchHelper will call
+ * {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)
+ * onMove(recyclerView, dragged, target)}.
+ * Upon receiving this callback, you should move the item from the old position
+ * ({@code dragged.getAdapterPosition()}) to new position ({@code target.getAdapterPosition()})
+ * in your adapter and also call {@link RecyclerView.Adapter#notifyItemMoved(int, int)}.
+ * To control where a View can be dropped, you can override
+ * {@link #canDropOver(RecyclerView, ViewHolder, ViewHolder)}. When a
+ * dragging View overlaps multiple other views, Callback chooses the closest View with which
+ * dragged View might have changed positions. Although this approach works for many use cases,
+ * if you have a custom LayoutManager, you can override
+ * {@link #chooseDropTarget(ViewHolder, java.util.List, int, int)} to select a
+ * custom drop target.
+ * <p>
+ * When a View is swiped, ItemTouchHelper animates it until it goes out of bounds, then calls
+ * {@link #onSwiped(ViewHolder, int)}. At this point, you should update your
+ * adapter (e.g. remove the item) and call related Adapter#notify event.
+ */
+ @SuppressWarnings("UnusedParameters")
+ public abstract static class Callback {
+
+ public static final int DEFAULT_DRAG_ANIMATION_DURATION = 200;
+
+ public static final int DEFAULT_SWIPE_ANIMATION_DURATION = 250;
+
+ static final int RELATIVE_DIR_FLAGS = START | END
+ | ((START | END) << DIRECTION_FLAG_COUNT)
+ | ((START | END) << (2 * DIRECTION_FLAG_COUNT));
+
+ private static final ItemTouchUIUtil sUICallback = new ItemTouchUIUtilImpl();
+
+ private static final int ABS_HORIZONTAL_DIR_FLAGS = LEFT | RIGHT
+ | ((LEFT | RIGHT) << DIRECTION_FLAG_COUNT)
+ | ((LEFT | RIGHT) << (2 * DIRECTION_FLAG_COUNT));
+
+ private static final Interpolator sDragScrollInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float t) {
+ return t * t * t * t * t;
+ }
+ };
+
+ private static final Interpolator sDragViewScrollCapInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float t) {
+ t -= 1.0f;
+ return t * t * t * t * t + 1.0f;
+ }
+ };
+
+ /**
+ * Drag scroll speed keeps accelerating until this many milliseconds before being capped.
+ */
+ private static final long DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS = 2000;
+
+ private int mCachedMaxScrollSpeed = -1;
+
+ /**
+ * Returns the {@link ItemTouchUIUtil} that is used by the {@link Callback} class for
+ * visual
+ * changes on Views in response to user interactions. {@link ItemTouchUIUtil} has different
+ * implementations for different platform versions.
+ * <p>
+ * By default, {@link Callback} applies these changes on
+ * {@link RecyclerView.ViewHolder#itemView}.
+ * <p>
+ * For example, if you have a use case where you only want the text to move when user
+ * swipes over the view, you can do the following:
+ * <pre>
+ * public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder){
+ * getDefaultUIUtil().clearView(((ItemTouchViewHolder) viewHolder).textView);
+ * }
+ * public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
+ * if (viewHolder != null){
+ * getDefaultUIUtil().onSelected(((ItemTouchViewHolder) viewHolder).textView);
+ * }
+ * }
+ * public void onChildDraw(Canvas c, RecyclerView recyclerView,
+ * RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState,
+ * boolean isCurrentlyActive) {
+ * getDefaultUIUtil().onDraw(c, recyclerView,
+ * ((ItemTouchViewHolder) viewHolder).textView, dX, dY,
+ * actionState, isCurrentlyActive);
+ * return true;
+ * }
+ * public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
+ * RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState,
+ * boolean isCurrentlyActive) {
+ * getDefaultUIUtil().onDrawOver(c, recyclerView,
+ * ((ItemTouchViewHolder) viewHolder).textView, dX, dY,
+ * actionState, isCurrentlyActive);
+ * return true;
+ * }
+ * </pre>
+ *
+ * @return The {@link ItemTouchUIUtil} instance that is used by the {@link Callback}
+ */
+ public static ItemTouchUIUtil getDefaultUIUtil() {
+ return sUICallback;
+ }
+
+ /**
+ * Replaces a movement direction with its relative version by taking layout direction into
+ * account.
+ *
+ * @param flags The flag value that include any number of movement flags.
+ * @param layoutDirection The layout direction of the View. Can be obtained from
+ * {@link View#getLayoutDirection()}.
+ * @return Updated flags which uses relative flags ({@link #START}, {@link #END}) instead
+ * of {@link #LEFT}, {@link #RIGHT}.
+ * @see #convertToAbsoluteDirection(int, int)
+ */
+ public static int convertToRelativeDirection(int flags, int layoutDirection) {
+ int masked = flags & ABS_HORIZONTAL_DIR_FLAGS;
+ if (masked == 0) {
+ return flags; // does not have any abs flags, good.
+ }
+ flags &= ~masked; //remove left / right.
+ if (layoutDirection == View.LAYOUT_DIRECTION_LTR) {
+ // no change. just OR with 2 bits shifted mask and return
+ flags |= masked << 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT.
+ return flags;
+ } else {
+ // add RIGHT flag as START
+ flags |= ((masked << 1) & ~ABS_HORIZONTAL_DIR_FLAGS);
+ // first clean RIGHT bit then add LEFT flag as END
+ flags |= ((masked << 1) & ABS_HORIZONTAL_DIR_FLAGS) << 2;
+ }
+ return flags;
+ }
+
+ /**
+ * Convenience method to create movement flags.
+ * <p>
+ * For instance, if you want to let your items be drag & dropped vertically and swiped
+ * left to be dismissed, you can call this method with:
+ * <code>makeMovementFlags(UP | DOWN, LEFT);</code>
+ *
+ * @param dragFlags The directions in which the item can be dragged.
+ * @param swipeFlags The directions in which the item can be swiped.
+ * @return Returns an integer composed of the given drag and swipe flags.
+ */
+ public static int makeMovementFlags(int dragFlags, int swipeFlags) {
+ return makeFlag(ACTION_STATE_IDLE, swipeFlags | dragFlags)
+ | makeFlag(ACTION_STATE_SWIPE, swipeFlags)
+ | makeFlag(ACTION_STATE_DRAG, dragFlags);
+ }
+
+ /**
+ * Shifts the given direction flags to the offset of the given action state.
+ *
+ * @param actionState The action state you want to get flags in. Should be one of
+ * {@link #ACTION_STATE_IDLE}, {@link #ACTION_STATE_SWIPE} or
+ * {@link #ACTION_STATE_DRAG}.
+ * @param directions The direction flags. Can be composed from {@link #UP}, {@link #DOWN},
+ * {@link #RIGHT}, {@link #LEFT} {@link #START} and {@link #END}.
+ * @return And integer that represents the given directions in the provided actionState.
+ */
+ public static int makeFlag(int actionState, int directions) {
+ return directions << (actionState * DIRECTION_FLAG_COUNT);
+ }
+
+ /**
+ * Should return a composite flag which defines the enabled move directions in each state
+ * (idle, swiping, dragging).
+ * <p>
+ * Instead of composing this flag manually, you can use {@link #makeMovementFlags(int,
+ * int)}
+ * or {@link #makeFlag(int, int)}.
+ * <p>
+ * This flag is composed of 3 sets of 8 bits, where first 8 bits are for IDLE state, next
+ * 8 bits are for SWIPE state and third 8 bits are for DRAG state.
+ * Each 8 bit sections can be constructed by simply OR'ing direction flags defined in
+ * {@link ItemTouchHelper}.
+ * <p>
+ * For example, if you want it to allow swiping LEFT and RIGHT but only allow starting to
+ * swipe by swiping RIGHT, you can return:
+ * <pre>
+ * makeFlag(ACTION_STATE_IDLE, RIGHT) | makeFlag(ACTION_STATE_SWIPE, LEFT | RIGHT);
+ * </pre>
+ * This means, allow right movement while IDLE and allow right and left movement while
+ * swiping.
+ *
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached.
+ * @param viewHolder The ViewHolder for which the movement information is necessary.
+ * @return flags specifying which movements are allowed on this ViewHolder.
+ * @see #makeMovementFlags(int, int)
+ * @see #makeFlag(int, int)
+ */
+ public abstract int getMovementFlags(RecyclerView recyclerView,
+ ViewHolder viewHolder);
+
+ /**
+ * Converts a given set of flags to absolution direction which means {@link #START} and
+ * {@link #END} are replaced with {@link #LEFT} and {@link #RIGHT} depending on the layout
+ * direction.
+ *
+ * @param flags The flag value that include any number of movement flags.
+ * @param layoutDirection The layout direction of the RecyclerView.
+ * @return Updated flags which includes only absolute direction values.
+ */
+ public int convertToAbsoluteDirection(int flags, int layoutDirection) {
+ int masked = flags & RELATIVE_DIR_FLAGS;
+ if (masked == 0) {
+ return flags; // does not have any relative flags, good.
+ }
+ flags &= ~masked; //remove start / end
+ if (layoutDirection == View.LAYOUT_DIRECTION_LTR) {
+ // no change. just OR with 2 bits shifted mask and return
+ flags |= masked >> 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT.
+ return flags;
+ } else {
+ // add START flag as RIGHT
+ flags |= ((masked >> 1) & ~RELATIVE_DIR_FLAGS);
+ // first clean start bit then add END flag as LEFT
+ flags |= ((masked >> 1) & RELATIVE_DIR_FLAGS) >> 2;
+ }
+ return flags;
+ }
+
+ final int getAbsoluteMovementFlags(RecyclerView recyclerView,
+ ViewHolder viewHolder) {
+ final int flags = getMovementFlags(recyclerView, viewHolder);
+ return convertToAbsoluteDirection(flags, recyclerView.getLayoutDirection());
+ }
+
+ boolean hasDragFlag(RecyclerView recyclerView, ViewHolder viewHolder) {
+ final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
+ return (flags & ACTION_MODE_DRAG_MASK) != 0;
+ }
+
+ boolean hasSwipeFlag(RecyclerView recyclerView,
+ ViewHolder viewHolder) {
+ final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
+ return (flags & ACTION_MODE_SWIPE_MASK) != 0;
+ }
+
+ /**
+ * Return true if the current ViewHolder can be dropped over the the target ViewHolder.
+ * <p>
+ * This method is used when selecting drop target for the dragged View. After Views are
+ * eliminated either via bounds check or via this method, resulting set of views will be
+ * passed to {@link #chooseDropTarget(ViewHolder, java.util.List, int, int)}.
+ * <p>
+ * Default implementation returns true.
+ *
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
+ * @param current The ViewHolder that user is dragging.
+ * @param target The ViewHolder which is below the dragged ViewHolder.
+ * @return True if the dragged ViewHolder can be replaced with the target ViewHolder, false
+ * otherwise.
+ */
+ public boolean canDropOver(RecyclerView recyclerView, ViewHolder current,
+ ViewHolder target) {
+ return true;
+ }
+
+ /**
+ * Called when ItemTouchHelper wants to move the dragged item from its old position to
+ * the new position.
+ * <p>
+ * If this method returns true, ItemTouchHelper assumes {@code viewHolder} has been moved
+ * to the adapter position of {@code target} ViewHolder
+ * ({@link ViewHolder#getAdapterPosition()
+ * ViewHolder#getAdapterPosition()}).
+ * <p>
+ * If you don't support drag & drop, this method will never be called.
+ *
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
+ * @param viewHolder The ViewHolder which is being dragged by the user.
+ * @param target The ViewHolder over which the currently active item is being
+ * dragged.
+ * @return True if the {@code viewHolder} has been moved to the adapter position of
+ * {@code target}.
+ * @see #onMoved(RecyclerView, ViewHolder, int, ViewHolder, int, int, int)
+ */
+ public abstract boolean onMove(RecyclerView recyclerView,
+ ViewHolder viewHolder, ViewHolder target);
+
+ /**
+ * Returns whether ItemTouchHelper should start a drag and drop operation if an item is
+ * long pressed.
+ * <p>
+ * Default value returns true but you may want to disable this if you want to start
+ * dragging on a custom view touch using {@link #startDrag(ViewHolder)}.
+ *
+ * @return True if ItemTouchHelper should start dragging an item when it is long pressed,
+ * false otherwise. Default value is <code>true</code>.
+ * @see #startDrag(ViewHolder)
+ */
+ public boolean isLongPressDragEnabled() {
+ return true;
+ }
+
+ /**
+ * Returns whether ItemTouchHelper should start a swipe operation if a pointer is swiped
+ * over the View.
+ * <p>
+ * Default value returns true but you may want to disable this if you want to start
+ * swiping on a custom view touch using {@link #startSwipe(ViewHolder)}.
+ *
+ * @return True if ItemTouchHelper should start swiping an item when user swipes a pointer
+ * over the View, false otherwise. Default value is <code>true</code>.
+ * @see #startSwipe(ViewHolder)
+ */
+ public boolean isItemViewSwipeEnabled() {
+ return true;
+ }
+
+ /**
+ * When finding views under a dragged view, by default, ItemTouchHelper searches for views
+ * that overlap with the dragged View. By overriding this method, you can extend or shrink
+ * the search box.
+ *
+ * @return The extra margin to be added to the hit box of the dragged View.
+ */
+ public int getBoundingBoxMargin() {
+ return 0;
+ }
+
+ /**
+ * Returns the fraction that the user should move the View to be considered as swiped.
+ * The fraction is calculated with respect to RecyclerView's bounds.
+ * <p>
+ * Default value is .5f, which means, to swipe a View, user must move the View at least
+ * half of RecyclerView's width or height, depending on the swipe direction.
+ *
+ * @param viewHolder The ViewHolder that is being dragged.
+ * @return A float value that denotes the fraction of the View size. Default value
+ * is .5f .
+ */
+ public float getSwipeThreshold(ViewHolder viewHolder) {
+ return .5f;
+ }
+
+ /**
+ * Returns the fraction that the user should move the View to be considered as it is
+ * dragged. After a view is moved this amount, ItemTouchHelper starts checking for Views
+ * below it for a possible drop.
+ *
+ * @param viewHolder The ViewHolder that is being dragged.
+ * @return A float value that denotes the fraction of the View size. Default value is
+ * .5f .
+ */
+ public float getMoveThreshold(ViewHolder viewHolder) {
+ return .5f;
+ }
+
+ /**
+ * Defines the minimum velocity which will be considered as a swipe action by the user.
+ * <p>
+ * You can increase this value to make it harder to swipe or decrease it to make it easier.
+ * Keep in mind that ItemTouchHelper also checks the perpendicular velocity and makes sure
+ * current direction velocity is larger then the perpendicular one. Otherwise, user's
+ * movement is ambiguous. You can change the threshold by overriding
+ * {@link #getSwipeVelocityThreshold(float)}.
+ * <p>
+ * The velocity is calculated in pixels per second.
+ * <p>
+ * The default framework value is passed as a parameter so that you can modify it with a
+ * multiplier.
+ *
+ * @param defaultValue The default value (in pixels per second) used by the
+ * ItemTouchHelper.
+ * @return The minimum swipe velocity. The default implementation returns the
+ * <code>defaultValue</code> parameter.
+ * @see #getSwipeVelocityThreshold(float)
+ * @see #getSwipeThreshold(ViewHolder)
+ */
+ public float getSwipeEscapeVelocity(float defaultValue) {
+ return defaultValue;
+ }
+
+ /**
+ * Defines the maximum velocity ItemTouchHelper will ever calculate for pointer movements.
+ * <p>
+ * To consider a movement as swipe, ItemTouchHelper requires it to be larger than the
+ * perpendicular movement. If both directions reach to the max threshold, none of them will
+ * be considered as a swipe because it is usually an indication that user rather tried to
+ * scroll then swipe.
+ * <p>
+ * The velocity is calculated in pixels per second.
+ * <p>
+ * You can customize this behavior by changing this method. If you increase the value, it
+ * will be easier for the user to swipe diagonally and if you decrease the value, user will
+ * need to make a rather straight finger movement to trigger a swipe.
+ *
+ * @param defaultValue The default value(in pixels per second) used by the ItemTouchHelper.
+ * @return The velocity cap for pointer movements. The default implementation returns the
+ * <code>defaultValue</code> parameter.
+ * @see #getSwipeEscapeVelocity(float)
+ */
+ public float getSwipeVelocityThreshold(float defaultValue) {
+ return defaultValue;
+ }
+
+ /**
+ * Called by ItemTouchHelper to select a drop target from the list of ViewHolders that
+ * are under the dragged View.
+ * <p>
+ * Default implementation filters the View with which dragged item have changed position
+ * in the drag direction. For instance, if the view is dragged UP, it compares the
+ * <code>view.getTop()</code> of the two views before and after drag started. If that value
+ * is different, the target view passes the filter.
+ * <p>
+ * Among these Views which pass the test, the one closest to the dragged view is chosen.
+ * <p>
+ * This method is called on the main thread every time user moves the View. If you want to
+ * override it, make sure it does not do any expensive operations.
+ *
+ * @param selected The ViewHolder being dragged by the user.
+ * @param dropTargets The list of ViewHolder that are under the dragged View and
+ * candidate as a drop.
+ * @param curX The updated left value of the dragged View after drag translations
+ * are applied. This value does not include margins added by
+ * {@link RecyclerView.ItemDecoration}s.
+ * @param curY The updated top value of the dragged View after drag translations
+ * are applied. This value does not include margins added by
+ * {@link RecyclerView.ItemDecoration}s.
+ * @return A ViewHolder to whose position the dragged ViewHolder should be
+ * moved to.
+ */
+ public ViewHolder chooseDropTarget(ViewHolder selected,
+ List<ViewHolder> dropTargets, int curX, int curY) {
+ int right = curX + selected.itemView.getWidth();
+ int bottom = curY + selected.itemView.getHeight();
+ ViewHolder winner = null;
+ int winnerScore = -1;
+ final int dx = curX - selected.itemView.getLeft();
+ final int dy = curY - selected.itemView.getTop();
+ final int targetsSize = dropTargets.size();
+ for (int i = 0; i < targetsSize; i++) {
+ final ViewHolder target = dropTargets.get(i);
+ if (dx > 0) {
+ int diff = target.itemView.getRight() - right;
+ if (diff < 0 && target.itemView.getRight() > selected.itemView.getRight()) {
+ final int score = Math.abs(diff);
+ if (score > winnerScore) {
+ winnerScore = score;
+ winner = target;
+ }
+ }
+ }
+ if (dx < 0) {
+ int diff = target.itemView.getLeft() - curX;
+ if (diff > 0 && target.itemView.getLeft() < selected.itemView.getLeft()) {
+ final int score = Math.abs(diff);
+ if (score > winnerScore) {
+ winnerScore = score;
+ winner = target;
+ }
+ }
+ }
+ if (dy < 0) {
+ int diff = target.itemView.getTop() - curY;
+ if (diff > 0 && target.itemView.getTop() < selected.itemView.getTop()) {
+ final int score = Math.abs(diff);
+ if (score > winnerScore) {
+ winnerScore = score;
+ winner = target;
+ }
+ }
+ }
+
+ if (dy > 0) {
+ int diff = target.itemView.getBottom() - bottom;
+ if (diff < 0 && target.itemView.getBottom() > selected.itemView.getBottom()) {
+ final int score = Math.abs(diff);
+ if (score > winnerScore) {
+ winnerScore = score;
+ winner = target;
+ }
+ }
+ }
+ }
+ return winner;
+ }
+
+ /**
+ * Called when a ViewHolder is swiped by the user.
+ * <p>
+ * If you are returning relative directions ({@link #START} , {@link #END}) from the
+ * {@link #getMovementFlags(RecyclerView, ViewHolder)} method, this method
+ * will also use relative directions. Otherwise, it will use absolute directions.
+ * <p>
+ * If you don't support swiping, this method will never be called.
+ * <p>
+ * ItemTouchHelper will keep a reference to the View until it is detached from
+ * RecyclerView.
+ * As soon as it is detached, ItemTouchHelper will call
+ * {@link #clearView(RecyclerView, ViewHolder)}.
+ *
+ * @param viewHolder The ViewHolder which has been swiped by the user.
+ * @param direction The direction to which the ViewHolder is swiped. It is one of
+ * {@link #UP}, {@link #DOWN},
+ * {@link #LEFT} or {@link #RIGHT}. If your
+ * {@link #getMovementFlags(RecyclerView, ViewHolder)}
+ * method
+ * returned relative flags instead of {@link #LEFT} / {@link #RIGHT};
+ * `direction` will be relative as well. ({@link #START} or {@link
+ * #END}).
+ */
+ public abstract void onSwiped(ViewHolder viewHolder, int direction);
+
+ /**
+ * Called when the ViewHolder swiped or dragged by the ItemTouchHelper is changed.
+ * <p/>
+ * If you override this method, you should call super.
+ *
+ * @param viewHolder The new ViewHolder that is being swiped or dragged. Might be null if
+ * it is cleared.
+ * @param actionState One of {@link ItemTouchHelper#ACTION_STATE_IDLE},
+ * {@link ItemTouchHelper#ACTION_STATE_SWIPE} or
+ * {@link ItemTouchHelper#ACTION_STATE_DRAG}.
+ * @see #clearView(RecyclerView, RecyclerView.ViewHolder)
+ */
+ public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
+ if (viewHolder != null) {
+ sUICallback.onSelected(viewHolder.itemView);
+ }
+ }
+
+ private int getMaxDragScroll(RecyclerView recyclerView) {
+ if (mCachedMaxScrollSpeed == -1) {
+ mCachedMaxScrollSpeed = recyclerView.getResources().getDimensionPixelSize(
+ R.dimen.item_touch_helper_max_drag_scroll_per_frame);
+ }
+ return mCachedMaxScrollSpeed;
+ }
+
+ /**
+ * Called when {@link #onMove(RecyclerView, ViewHolder, ViewHolder)} returns true.
+ * <p>
+ * ItemTouchHelper does not create an extra Bitmap or View while dragging, instead, it
+ * modifies the existing View. Because of this reason, it is important that the View is
+ * still part of the layout after it is moved. This may not work as intended when swapped
+ * Views are close to RecyclerView bounds or there are gaps between them (e.g. other Views
+ * which were not eligible for dropping over).
+ * <p>
+ * This method is responsible to give necessary hint to the LayoutManager so that it will
+ * keep the View in visible area. For example, for LinearLayoutManager, this is as simple
+ * as calling {@link LinearLayoutManager#scrollToPositionWithOffset(int, int)}.
+ *
+ * Default implementation calls {@link RecyclerView#scrollToPosition(int)} if the View's
+ * new position is likely to be out of bounds.
+ * <p>
+ * It is important to ensure the ViewHolder will stay visible as otherwise, it might be
+ * removed by the LayoutManager if the move causes the View to go out of bounds. In that
+ * case, drag will end prematurely.
+ *
+ * @param recyclerView The RecyclerView controlled by the ItemTouchHelper.
+ * @param viewHolder The ViewHolder under user's control.
+ * @param fromPos The previous adapter position of the dragged item (before it was
+ * moved).
+ * @param target The ViewHolder on which the currently active item has been dropped.
+ * @param toPos The new adapter position of the dragged item.
+ * @param x The updated left value of the dragged View after drag translations
+ * are applied. This value does not include margins added by
+ * {@link RecyclerView.ItemDecoration}s.
+ * @param y The updated top value of the dragged View after drag translations
+ * are applied. This value does not include margins added by
+ * {@link RecyclerView.ItemDecoration}s.
+ */
+ public void onMoved(final RecyclerView recyclerView,
+ final ViewHolder viewHolder, int fromPos, final ViewHolder target, int toPos, int x,
+ int y) {
+ final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
+ if (layoutManager instanceof ViewDropHandler) {
+ ((ViewDropHandler) layoutManager).prepareForDrop(viewHolder.itemView,
+ target.itemView, x, y);
+ return;
+ }
+
+ // if layout manager cannot handle it, do some guesswork
+ if (layoutManager.canScrollHorizontally()) {
+ final int minLeft = layoutManager.getDecoratedLeft(target.itemView);
+ if (minLeft <= recyclerView.getPaddingLeft()) {
+ recyclerView.scrollToPosition(toPos);
+ }
+ final int maxRight = layoutManager.getDecoratedRight(target.itemView);
+ if (maxRight >= recyclerView.getWidth() - recyclerView.getPaddingRight()) {
+ recyclerView.scrollToPosition(toPos);
+ }
+ }
+
+ if (layoutManager.canScrollVertically()) {
+ final int minTop = layoutManager.getDecoratedTop(target.itemView);
+ if (minTop <= recyclerView.getPaddingTop()) {
+ recyclerView.scrollToPosition(toPos);
+ }
+ final int maxBottom = layoutManager.getDecoratedBottom(target.itemView);
+ if (maxBottom >= recyclerView.getHeight() - recyclerView.getPaddingBottom()) {
+ recyclerView.scrollToPosition(toPos);
+ }
+ }
+ }
+
+ void onDraw(Canvas c, RecyclerView parent, ViewHolder selected,
+ List<ItemTouchHelper.RecoverAnimation> recoverAnimationList,
+ int actionState, float dX, float dY) {
+ final int recoverAnimSize = recoverAnimationList.size();
+ for (int i = 0; i < recoverAnimSize; i++) {
+ final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i);
+ anim.update();
+ final int count = c.save();
+ onChildDraw(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState,
+ false);
+ c.restoreToCount(count);
+ }
+ if (selected != null) {
+ final int count = c.save();
+ onChildDraw(c, parent, selected, dX, dY, actionState, true);
+ c.restoreToCount(count);
+ }
+ }
+
+ void onDrawOver(Canvas c, RecyclerView parent, ViewHolder selected,
+ List<ItemTouchHelper.RecoverAnimation> recoverAnimationList,
+ int actionState, float dX, float dY) {
+ final int recoverAnimSize = recoverAnimationList.size();
+ for (int i = 0; i < recoverAnimSize; i++) {
+ final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i);
+ final int count = c.save();
+ onChildDrawOver(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState,
+ false);
+ c.restoreToCount(count);
+ }
+ if (selected != null) {
+ final int count = c.save();
+ onChildDrawOver(c, parent, selected, dX, dY, actionState, true);
+ c.restoreToCount(count);
+ }
+ boolean hasRunningAnimation = false;
+ for (int i = recoverAnimSize - 1; i >= 0; i--) {
+ final RecoverAnimation anim = recoverAnimationList.get(i);
+ if (anim.mEnded && !anim.mIsPendingCleanup) {
+ recoverAnimationList.remove(i);
+ } else if (!anim.mEnded) {
+ hasRunningAnimation = true;
+ }
+ }
+ if (hasRunningAnimation) {
+ parent.invalidate();
+ }
+ }
+
+ /**
+ * Called by the ItemTouchHelper when the user interaction with an element is over and it
+ * also completed its animation.
+ * <p>
+ * This is a good place to clear all changes on the View that was done in
+ * {@link #onSelectedChanged(RecyclerView.ViewHolder, int)},
+ * {@link #onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int,
+ * boolean)} or
+ * {@link #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, boolean)}.
+ *
+ * @param recyclerView The RecyclerView which is controlled by the ItemTouchHelper.
+ * @param viewHolder The View that was interacted by the user.
+ */
+ public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
+ sUICallback.clearView(viewHolder.itemView);
+ }
+
+ /**
+ * Called by ItemTouchHelper on RecyclerView's onDraw callback.
+ * <p>
+ * If you would like to customize how your View's respond to user interactions, this is
+ * a good place to override.
+ * <p>
+ * Default implementation translates the child by the given <code>dX</code>,
+ * <code>dY</code>.
+ * ItemTouchHelper also takes care of drawing the child after other children if it is being
+ * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
+ * is
+ * achieved via {@link android.view.ViewGroup#getChildDrawingOrder(int, int)} and on L
+ * and after, it changes View's elevation value to be greater than all other children.)
+ *
+ * @param c The canvas which RecyclerView is drawing its children
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to
+ * @param viewHolder The ViewHolder which is being interacted by the User or it was
+ * interacted and simply animating to its original position
+ * @param dX The amount of horizontal displacement caused by user's action
+ * @param dY The amount of vertical displacement caused by user's action
+ * @param actionState The type of interaction on the View. Is either {@link
+ * #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
+ * @param isCurrentlyActive True if this view is currently being controlled by the user or
+ * false it is simply animating back to its original state.
+ * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
+ * boolean)
+ */
+ public void onChildDraw(Canvas c, RecyclerView recyclerView,
+ ViewHolder viewHolder,
+ float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ sUICallback.onDraw(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
+ isCurrentlyActive);
+ }
+
+ /**
+ * Called by ItemTouchHelper on RecyclerView's onDraw callback.
+ * <p>
+ * If you would like to customize how your View's respond to user interactions, this is
+ * a good place to override.
+ * <p>
+ * Default implementation translates the child by the given <code>dX</code>,
+ * <code>dY</code>.
+ * ItemTouchHelper also takes care of drawing the child after other children if it is being
+ * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
+ * is
+ * achieved via {@link android.view.ViewGroup#getChildDrawingOrder(int, int)} and on L
+ * and after, it changes View's elevation value to be greater than all other children.)
+ *
+ * @param c The canvas which RecyclerView is drawing its children
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to
+ * @param viewHolder The ViewHolder which is being interacted by the User or it was
+ * interacted and simply animating to its original position
+ * @param dX The amount of horizontal displacement caused by user's action
+ * @param dY The amount of vertical displacement caused by user's action
+ * @param actionState The type of interaction on the View. Is either {@link
+ * #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
+ * @param isCurrentlyActive True if this view is currently being controlled by the user or
+ * false it is simply animating back to its original state.
+ * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
+ * boolean)
+ */
+ public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
+ ViewHolder viewHolder,
+ float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ sUICallback.onDrawOver(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
+ isCurrentlyActive);
+ }
+
+ /**
+ * Called by the ItemTouchHelper when user action finished on a ViewHolder and now the View
+ * will be animated to its final position.
+ * <p>
+ * Default implementation uses ItemAnimator's duration values. If
+ * <code>animationType</code> is {@link #ANIMATION_TYPE_DRAG}, it returns
+ * {@link RecyclerView.ItemAnimator#getMoveDuration()}, otherwise, it returns
+ * {@link RecyclerView.ItemAnimator#getRemoveDuration()}. If RecyclerView does not have
+ * any {@link RecyclerView.ItemAnimator} attached, this method returns
+ * {@code DEFAULT_DRAG_ANIMATION_DURATION} or {@code DEFAULT_SWIPE_ANIMATION_DURATION}
+ * depending on the animation type.
+ *
+ * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
+ * @param animationType The type of animation. Is one of {@link #ANIMATION_TYPE_DRAG},
+ * {@link #ANIMATION_TYPE_SWIPE_CANCEL} or
+ * {@link #ANIMATION_TYPE_SWIPE_SUCCESS}.
+ * @param animateDx The horizontal distance that the animation will offset
+ * @param animateDy The vertical distance that the animation will offset
+ * @return The duration for the animation
+ */
+ public long getAnimationDuration(RecyclerView recyclerView, int animationType,
+ float animateDx, float animateDy) {
+ final RecyclerView.ItemAnimator itemAnimator = recyclerView.getItemAnimator();
+ if (itemAnimator == null) {
+ return animationType == ANIMATION_TYPE_DRAG ? DEFAULT_DRAG_ANIMATION_DURATION
+ : DEFAULT_SWIPE_ANIMATION_DURATION;
+ } else {
+ return animationType == ANIMATION_TYPE_DRAG ? itemAnimator.getMoveDuration()
+ : itemAnimator.getRemoveDuration();
+ }
+ }
+
+ /**
+ * Called by the ItemTouchHelper when user is dragging a view out of bounds.
+ * <p>
+ * You can override this method to decide how much RecyclerView should scroll in response
+ * to this action. Default implementation calculates a value based on the amount of View
+ * out of bounds and the time it spent there. The longer user keeps the View out of bounds,
+ * the faster the list will scroll. Similarly, the larger portion of the View is out of
+ * bounds, the faster the RecyclerView will scroll.
+ *
+ * @param recyclerView The RecyclerView instance to which ItemTouchHelper is
+ * attached to.
+ * @param viewSize The total size of the View in scroll direction, excluding
+ * item decorations.
+ * @param viewSizeOutOfBounds The total size of the View that is out of bounds. This value
+ * is negative if the View is dragged towards left or top edge.
+ * @param totalSize The total size of RecyclerView in the scroll direction.
+ * @param msSinceStartScroll The time passed since View is kept out of bounds.
+ * @return The amount that RecyclerView should scroll. Keep in mind that this value will
+ * be passed to {@link RecyclerView#scrollBy(int, int)} method.
+ */
+ public int interpolateOutOfBoundsScroll(RecyclerView recyclerView,
+ int viewSize, int viewSizeOutOfBounds,
+ int totalSize, long msSinceStartScroll) {
+ final int maxScroll = getMaxDragScroll(recyclerView);
+ final int absOutOfBounds = Math.abs(viewSizeOutOfBounds);
+ final int direction = (int) Math.signum(viewSizeOutOfBounds);
+ // might be negative if other direction
+ float outOfBoundsRatio = Math.min(1f, 1f * absOutOfBounds / viewSize);
+ final int cappedScroll = (int) (direction * maxScroll
+ * sDragViewScrollCapInterpolator.getInterpolation(outOfBoundsRatio));
+ final float timeRatio;
+ if (msSinceStartScroll > DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS) {
+ timeRatio = 1f;
+ } else {
+ timeRatio = (float) msSinceStartScroll / DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS;
+ }
+ final int value = (int) (cappedScroll * sDragScrollInterpolator
+ .getInterpolation(timeRatio));
+ if (value == 0) {
+ return viewSizeOutOfBounds > 0 ? 1 : -1;
+ }
+ return value;
+ }
+ }
+
+ /**
+ * A simple wrapper to the default Callback which you can construct with drag and swipe
+ * directions and this class will handle the flag callbacks. You should still override onMove
+ * or
+ * onSwiped depending on your use case.
+ *
+ * <pre>
+ * ItemTouchHelper mIth = new ItemTouchHelper(
+ * new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
+ * ItemTouchHelper.LEFT) {
+ * public abstract boolean onMove(RecyclerView recyclerView,
+ * ViewHolder viewHolder, ViewHolder target) {
+ * final int fromPos = viewHolder.getAdapterPosition();
+ * final int toPos = target.getAdapterPosition();
+ * // move item in `fromPos` to `toPos` in adapter.
+ * return true;// true if moved, false otherwise
+ * }
+ * public void onSwiped(ViewHolder viewHolder, int direction) {
+ * // remove from adapter
+ * }
+ * });
+ * </pre>
+ */
+ public abstract static class SimpleCallback extends Callback {
+
+ private int mDefaultSwipeDirs;
+
+ private int mDefaultDragDirs;
+
+ /**
+ * Creates a Callback for the given drag and swipe allowance. These values serve as
+ * defaults
+ * and if you want to customize behavior per ViewHolder, you can override
+ * {@link #getSwipeDirs(RecyclerView, ViewHolder)}
+ * and / or {@link #getDragDirs(RecyclerView, ViewHolder)}.
+ *
+ * @param dragDirs Binary OR of direction flags in which the Views can be dragged. Must be
+ * composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link
+ * #END},
+ * {@link #UP} and {@link #DOWN}.
+ * @param swipeDirs Binary OR of direction flags in which the Views can be swiped. Must be
+ * composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link
+ * #END},
+ * {@link #UP} and {@link #DOWN}.
+ */
+ public SimpleCallback(int dragDirs, int swipeDirs) {
+ mDefaultSwipeDirs = swipeDirs;
+ mDefaultDragDirs = dragDirs;
+ }
+
+ /**
+ * Updates the default swipe directions. For example, you can use this method to toggle
+ * certain directions depending on your use case.
+ *
+ * @param defaultSwipeDirs Binary OR of directions in which the ViewHolders can be swiped.
+ */
+ public void setDefaultSwipeDirs(int defaultSwipeDirs) {
+ mDefaultSwipeDirs = defaultSwipeDirs;
+ }
+
+ /**
+ * Updates the default drag directions. For example, you can use this method to toggle
+ * certain directions depending on your use case.
+ *
+ * @param defaultDragDirs Binary OR of directions in which the ViewHolders can be dragged.
+ */
+ public void setDefaultDragDirs(int defaultDragDirs) {
+ mDefaultDragDirs = defaultDragDirs;
+ }
+
+ /**
+ * Returns the swipe directions for the provided ViewHolder.
+ * Default implementation returns the swipe directions that was set via constructor or
+ * {@link #setDefaultSwipeDirs(int)}.
+ *
+ * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
+ * @param viewHolder The RecyclerView for which the swipe direction is queried.
+ * @return A binary OR of direction flags.
+ */
+ public int getSwipeDirs(RecyclerView recyclerView, ViewHolder viewHolder) {
+ return mDefaultSwipeDirs;
+ }
+
+ /**
+ * Returns the drag directions for the provided ViewHolder.
+ * Default implementation returns the drag directions that was set via constructor or
+ * {@link #setDefaultDragDirs(int)}.
+ *
+ * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
+ * @param viewHolder The RecyclerView for which the swipe direction is queried.
+ * @return A binary OR of direction flags.
+ */
+ public int getDragDirs(RecyclerView recyclerView, ViewHolder viewHolder) {
+ return mDefaultDragDirs;
+ }
+
+ @Override
+ public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
+ return makeMovementFlags(getDragDirs(recyclerView, viewHolder),
+ getSwipeDirs(recyclerView, viewHolder));
+ }
+ }
+
+ private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
+
+ ItemTouchHelperGestureListener() {
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return true;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ View child = findChildView(e);
+ if (child != null) {
+ ViewHolder vh = mRecyclerView.getChildViewHolder(child);
+ if (vh != null) {
+ if (!mCallback.hasDragFlag(mRecyclerView, vh)) {
+ return;
+ }
+ int pointerId = e.getPointerId(0);
+ // Long press is deferred.
+ // Check w/ active pointer id to avoid selecting after motion
+ // event is canceled.
+ if (pointerId == mActivePointerId) {
+ final int index = e.findPointerIndex(mActivePointerId);
+ final float x = e.getX(index);
+ final float y = e.getY(index);
+ mInitialTouchX = x;
+ mInitialTouchY = y;
+ mDx = mDy = 0f;
+ if (DEBUG) {
+ Log.d(TAG,
+ "onlong press: x:" + mInitialTouchX + ",y:" + mInitialTouchY);
+ }
+ if (mCallback.isLongPressDragEnabled()) {
+ select(vh, ACTION_STATE_DRAG);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private class RecoverAnimation implements Animator.AnimatorListener {
+
+ final float mStartDx;
+
+ final float mStartDy;
+
+ final float mTargetX;
+
+ final float mTargetY;
+
+ final ViewHolder mViewHolder;
+
+ final int mActionState;
+
+ private final ValueAnimator mValueAnimator;
+
+ final int mAnimationType;
+
+ public boolean mIsPendingCleanup;
+
+ float mX;
+
+ float mY;
+
+ // if user starts touching a recovering view, we put it into interaction mode again,
+ // instantly.
+ boolean mOverridden = false;
+
+ boolean mEnded = false;
+
+ private float mFraction;
+
+ RecoverAnimation(ViewHolder viewHolder, int animationType,
+ int actionState, float startDx, float startDy, float targetX, float targetY) {
+ mActionState = actionState;
+ mAnimationType = animationType;
+ mViewHolder = viewHolder;
+ mStartDx = startDx;
+ mStartDy = startDy;
+ mTargetX = targetX;
+ mTargetY = targetY;
+ mValueAnimator = ValueAnimator.ofFloat(0f, 1f);
+ mValueAnimator.addUpdateListener(
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ setFraction(animation.getAnimatedFraction());
+ }
+ });
+ mValueAnimator.setTarget(viewHolder.itemView);
+ mValueAnimator.addListener(this);
+ setFraction(0f);
+ }
+
+ public void setDuration(long duration) {
+ mValueAnimator.setDuration(duration);
+ }
+
+ public void start() {
+ mViewHolder.setIsRecyclable(false);
+ mValueAnimator.start();
+ }
+
+ public void cancel() {
+ mValueAnimator.cancel();
+ }
+
+ public void setFraction(float fraction) {
+ mFraction = fraction;
+ }
+
+ /**
+ * We run updates on onDraw method but use the fraction from animator callback.
+ * This way, we can sync translate x/y values w/ the animators to avoid one-off frames.
+ */
+ public void update() {
+ if (mStartDx == mTargetX) {
+ mX = mViewHolder.itemView.getTranslationX();
+ } else {
+ mX = mStartDx + mFraction * (mTargetX - mStartDx);
+ }
+ if (mStartDy == mTargetY) {
+ mY = mViewHolder.itemView.getTranslationY();
+ } else {
+ mY = mStartDy + mFraction * (mTargetY - mStartDy);
+ }
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mEnded) {
+ mViewHolder.setIsRecyclable(true);
+ }
+ mEnded = true;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ setFraction(1f); //make sure we recover the view's state.
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/helper/ItemTouchUIUtil.java b/core/java/com/android/internal/widget/helper/ItemTouchUIUtil.java
new file mode 100644
index 0000000..e368a6d
--- /dev/null
+++ b/core/java/com/android/internal/widget/helper/ItemTouchUIUtil.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget.helper;
+
+import android.graphics.Canvas;
+import android.view.View;
+
+import com.android.internal.widget.RecyclerView;
+
+/**
+ * Utility class for {@link ItemTouchHelper} which handles item transformations for different
+ * API versions.
+ * <p/>
+ * This class has methods that map to {@link ItemTouchHelper.Callback}'s drawing methods. Default
+ * implementations in {@link ItemTouchHelper.Callback} call these methods with
+ * {@link RecyclerView.ViewHolder#itemView} and {@link ItemTouchUIUtil} makes necessary changes
+ * on the View depending on the API level. You can access the instance of {@link ItemTouchUIUtil}
+ * via {@link ItemTouchHelper.Callback#getDefaultUIUtil()} and call its methods with the children
+ * of ViewHolder that you want to apply default effects.
+ *
+ * @see ItemTouchHelper.Callback#getDefaultUIUtil()
+ */
+public interface ItemTouchUIUtil {
+
+ /**
+ * The default implementation for {@link ItemTouchHelper.Callback#onChildDraw(Canvas,
+ * RecyclerView, RecyclerView.ViewHolder, float, float, int, boolean)}
+ */
+ void onDraw(Canvas c, RecyclerView recyclerView, View view,
+ float dX, float dY, int actionState, boolean isCurrentlyActive);
+
+ /**
+ * The default implementation for {@link ItemTouchHelper.Callback#onChildDrawOver(Canvas,
+ * RecyclerView, RecyclerView.ViewHolder, float, float, int, boolean)}
+ */
+ void onDrawOver(Canvas c, RecyclerView recyclerView, View view,
+ float dX, float dY, int actionState, boolean isCurrentlyActive);
+
+ /**
+ * The default implementation for {@link ItemTouchHelper.Callback#clearView(RecyclerView,
+ * RecyclerView.ViewHolder)}
+ */
+ void clearView(View view);
+
+ /**
+ * The default implementation for {@link ItemTouchHelper.Callback#onSelectedChanged(
+ * RecyclerView.ViewHolder, int)}
+ */
+ void onSelected(View view);
+}
+
diff --git a/core/java/com/android/internal/widget/helper/ItemTouchUIUtilImpl.java b/core/java/com/android/internal/widget/helper/ItemTouchUIUtilImpl.java
new file mode 100644
index 0000000..0de240b
--- /dev/null
+++ b/core/java/com/android/internal/widget/helper/ItemTouchUIUtilImpl.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget.helper;
+
+import android.graphics.Canvas;
+import android.view.View;
+
+import com.android.internal.R;
+import com.android.internal.widget.RecyclerView;
+
+/**
+ * Package private class to keep implementations. Putting them inside ItemTouchUIUtil makes them
+ * public API, which is not desired in this case.
+ */
+class ItemTouchUIUtilImpl implements ItemTouchUIUtil {
+ @Override
+ public void onDraw(Canvas c, RecyclerView recyclerView, View view,
+ float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ if (isCurrentlyActive) {
+ Object originalElevation = view.getTag(
+ R.id.item_touch_helper_previous_elevation);
+ if (originalElevation == null) {
+ originalElevation = view.getElevation();
+ float newElevation = 1f + findMaxElevation(recyclerView, view);
+ view.setElevation(newElevation);
+ view.setTag(R.id.item_touch_helper_previous_elevation,
+ originalElevation);
+ }
+ }
+ view.setTranslationX(dX);
+ view.setTranslationY(dY);
+ }
+
+ private float findMaxElevation(RecyclerView recyclerView, View itemView) {
+ final int childCount = recyclerView.getChildCount();
+ float max = 0;
+ for (int i = 0; i < childCount; i++) {
+ final View child = recyclerView.getChildAt(i);
+ if (child == itemView) {
+ continue;
+ }
+ final float elevation = child.getElevation();
+ if (elevation > max) {
+ max = elevation;
+ }
+ }
+ return max;
+ }
+
+ @Override
+ public void clearView(View view) {
+ final Object tag = view.getTag(
+ R.id.item_touch_helper_previous_elevation);
+ if (tag != null && tag instanceof Float) {
+ view.setElevation((Float) tag);
+ }
+ view.setTag(R.id.item_touch_helper_previous_elevation, null);
+ view.setTranslationX(0f);
+ view.setTranslationY(0f);
+ }
+
+ @Override
+ public void onSelected(View view) {
+ }
+
+ @Override
+ public void onDrawOver(Canvas c, RecyclerView recyclerView,
+ View view, float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ }
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 0d3ccdc..327f142 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -158,6 +158,7 @@
android_hardware_camera2_legacy_LegacyCameraDevice.cpp \
android_hardware_camera2_legacy_PerfMeasurement.cpp \
android_hardware_camera2_DngCreator.cpp \
+ android_hardware_HardwareBuffer.cpp \
android_hardware_Radio.cpp \
android_hardware_SensorManager.cpp \
android_hardware_SerialPort.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index fb5d037..340f2ee 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -87,6 +87,7 @@
extern int register_android_hardware_camera2_legacy_LegacyCameraDevice(JNIEnv *env);
extern int register_android_hardware_camera2_legacy_PerfMeasurement(JNIEnv *env);
extern int register_android_hardware_camera2_DngCreator(JNIEnv *env);
+extern int register_android_hardware_HardwareBuffer(JNIEnv *env);
extern int register_android_hardware_Radio(JNIEnv *env);
extern int register_android_hardware_SensorManager(JNIEnv *env);
extern int register_android_hardware_SerialPort(JNIEnv *env);
@@ -1373,6 +1374,7 @@
REG_JNI(register_android_hardware_camera2_legacy_LegacyCameraDevice),
REG_JNI(register_android_hardware_camera2_legacy_PerfMeasurement),
REG_JNI(register_android_hardware_camera2_DngCreator),
+ REG_JNI(register_android_hardware_HardwareBuffer),
REG_JNI(register_android_hardware_Radio),
REG_JNI(register_android_hardware_SensorManager),
REG_JNI(register_android_hardware_SerialPort),
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index 15e7165..ac8f413 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -190,8 +190,8 @@
delete static_cast<Asset*>(context);
}
-static jboolean FontFamily_addFontFromAsset(JNIEnv* env, jobject, jlong familyPtr,
- jobject jassetMgr, jstring jpath) {
+static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong familyPtr,
+ jobject jassetMgr, jstring jpath, jint cookie, jboolean isAsset) {
NPE_CHECK_RETURN_ZERO(env, jassetMgr);
NPE_CHECK_RETURN_ZERO(env, jpath);
@@ -201,7 +201,18 @@
}
ScopedUtfChars str(env, jpath);
- Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
+ if (str.c_str() == nullptr) {
+ return false;
+ }
+
+ Asset* asset;
+ if (isAsset) {
+ asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
+ } else {
+ asset = cookie ? mgr->openNonAsset(static_cast<int32_t>(cookie), str.c_str(),
+ Asset::ACCESS_BUFFER) : mgr->openNonAsset(str.c_str(), Asset::ACCESS_BUFFER);
+ }
+
if (NULL == asset) {
return false;
}
@@ -234,8 +245,8 @@
{ "nAddFont", "(JLjava/nio/ByteBuffer;I)Z", (void*)FontFamily_addFont },
{ "nAddFontWeightStyle", "(JLjava/nio/ByteBuffer;ILjava/util/List;IZ)Z",
(void*)FontFamily_addFontWeightStyle },
- { "nAddFontFromAsset", "(JLandroid/content/res/AssetManager;Ljava/lang/String;)Z",
- (void*)FontFamily_addFontFromAsset },
+ { "nAddFontFromAssetManager", "(JLandroid/content/res/AssetManager;Ljava/lang/String;IZ)Z",
+ (void*)FontFamily_addFontFromAssetManager },
};
int register_android_graphics_FontFamily(JNIEnv* env)
@@ -247,9 +258,9 @@
gListClassInfo.mGet = GetMethodIDOrDie(env, listClass, "get", "(I)Ljava/lang/Object;");
gListClassInfo.mSize = GetMethodIDOrDie(env, listClass, "size", "()I");
- jclass axisClass = FindClassOrDie(env, "android/graphics/FontListParser$Axis");
- gAxisClassInfo.mTag = GetFieldIDOrDie(env, axisClass, "tag", "I");
- gAxisClassInfo.mStyleValue = GetFieldIDOrDie(env, axisClass, "styleValue", "F");
+ jclass axisClass = FindClassOrDie(env, "android/text/FontConfig$Axis");
+ gAxisClassInfo.mTag = GetFieldIDOrDie(env, axisClass, "mTag", "I");
+ gAxisClassInfo.mStyleValue = GetFieldIDOrDie(env, axisClass, "mStyleValue", "F");
return err;
}
diff --git a/core/jni/android/graphics/FontUtils.cpp b/core/jni/android/graphics/FontUtils.cpp
new file mode 100644
index 0000000..91fec2a
--- /dev/null
+++ b/core/jni/android/graphics/FontUtils.cpp
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+#include "FontUtils.h"
+
+#include "JNIHelp.h"
+#include <core_jni_helpers.h>
+
+namespace android {
+namespace {
+
+static struct {
+ jmethodID mGet;
+ jmethodID mSize;
+} gListClassInfo;
+
+static struct {
+ jfieldID mTag;
+ jfieldID mStyleValue;
+} gAxisClassInfo;
+
+} // namespace
+
+jint ListHelper::size() const {
+ return mEnv->CallIntMethod(mList, gListClassInfo.mSize);
+}
+
+jobject ListHelper::get(jint index) const {
+ return mEnv->CallObjectMethod(mList, gListClassInfo.mGet, index);
+}
+
+jint AxisHelper::getTag() const {
+ return mEnv->GetIntField(mAxis, gAxisClassInfo.mTag);
+}
+
+jfloat AxisHelper::getStyleValue() const {
+ return mEnv->GetFloatField(mAxis, gAxisClassInfo.mStyleValue);
+}
+
+void init_FontUtils(JNIEnv* env) {
+ jclass listClass = FindClassOrDie(env, "java/util/List");
+ gListClassInfo.mGet = GetMethodIDOrDie(env, listClass, "get", "(I)Ljava/lang/Object;");
+ gListClassInfo.mSize = GetMethodIDOrDie(env, listClass, "size", "()I");
+
+ jclass axisClass = FindClassOrDie(env, "android/text/FontConfig$Axis");
+ gAxisClassInfo.mTag = GetFieldIDOrDie(env, axisClass, "mTag", "I");
+ gAxisClassInfo.mStyleValue = GetFieldIDOrDie(env, axisClass, "mStyleValue", "F");
+}
+
+} // namespace android
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 9ce5670..c49287c 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -63,97 +63,87 @@
get_canvas(canvasHandle)->setBitmap(bitmap);
}
-static jboolean isOpaque(JNIEnv*, jobject, jlong canvasHandle) {
+static jboolean isOpaque(jlong canvasHandle) {
return get_canvas(canvasHandle)->isOpaque() ? JNI_TRUE : JNI_FALSE;
}
-static jint getWidth(JNIEnv*, jobject, jlong canvasHandle) {
+static jint getWidth(jlong canvasHandle) {
return static_cast<jint>(get_canvas(canvasHandle)->width());
}
-static jint getHeight(JNIEnv*, jobject, jlong canvasHandle) {
+static jint getHeight(jlong canvasHandle) {
return static_cast<jint>(get_canvas(canvasHandle)->height());
}
-static void setHighContrastText(JNIEnv*, jobject, jlong canvasHandle, jboolean highContrastText) {
+static void setHighContrastText(jlong canvasHandle, jboolean highContrastText) {
Canvas* canvas = get_canvas(canvasHandle);
canvas->setHighContrastText(highContrastText);
}
-static jint getSaveCount(JNIEnv*, jobject, jlong canvasHandle) {
- return static_cast<jint>(get_canvas(canvasHandle)->getSaveCount());
-}
-
-static jint save(JNIEnv*, jobject, jlong canvasHandle, jint flagsHandle) {
+static jint save(jlong canvasHandle, jint flagsHandle) {
SaveFlags::Flags flags = static_cast<SaveFlags::Flags>(flagsHandle);
return static_cast<jint>(get_canvas(canvasHandle)->save(flags));
}
-static jint saveLayer(JNIEnv* env, jobject, jlong canvasHandle, jfloat l, jfloat t,
+static jint saveLayer(jlong canvasHandle, jfloat l, jfloat t,
jfloat r, jfloat b, jlong paintHandle, jint flagsHandle) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
SaveFlags::Flags flags = static_cast<SaveFlags::Flags>(flagsHandle);
return static_cast<jint>(get_canvas(canvasHandle)->saveLayer(l, t, r, b, paint, flags));
}
-static jint saveLayerAlpha(JNIEnv* env, jobject, jlong canvasHandle, jfloat l, jfloat t,
+static jint saveLayerAlpha(jlong canvasHandle, jfloat l, jfloat t,
jfloat r, jfloat b, jint alpha, jint flagsHandle) {
SaveFlags::Flags flags = static_cast<SaveFlags::Flags>(flagsHandle);
return static_cast<jint>(get_canvas(canvasHandle)->saveLayerAlpha(l, t, r, b, alpha, flags));
}
-static void restore(JNIEnv* env, jobject, jlong canvasHandle, jboolean throwOnUnderflow) {
+static bool restore(jlong canvasHandle) {
Canvas* canvas = get_canvas(canvasHandle);
- if (canvas->getSaveCount() <= 1) { // cannot restore anymore
- if (throwOnUnderflow) {
- doThrowISE(env, "Underflow in restore - more restores than saves");
- }
- return; // compat behavior - return without throwing
+ if (canvas->getSaveCount() <= 1) {
+ return false; // cannot restore anymore
}
canvas->restore();
+ return true; // success
}
-static void restoreToCount(JNIEnv* env, jobject, jlong canvasHandle, jint restoreCount,
- jboolean throwOnUnderflow) {
+static void restoreToCount(jlong canvasHandle, jint saveCount) {
Canvas* canvas = get_canvas(canvasHandle);
- if (restoreCount < 1 || restoreCount > canvas->getSaveCount()) {
- if (throwOnUnderflow) {
- doThrowIAE(env, "Underflow in restoreToCount - more restores than saves");
- return;
- }
- restoreCount = 1; // compat behavior - restore as far as possible
- }
- canvas->restoreToCount(restoreCount);
+ canvas->restoreToCount(saveCount);
}
-static void getCTM(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) {
+static jint getSaveCount(jlong canvasHandle) {
+ return static_cast<jint>(get_canvas(canvasHandle)->getSaveCount());
+}
+
+static void getMatrix(jlong canvasHandle, jlong matrixHandle) {
SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
get_canvas(canvasHandle)->getMatrix(matrix);
}
-static void setMatrix(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) {
+static void setMatrix(jlong canvasHandle, jlong matrixHandle) {
const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
get_canvas(canvasHandle)->setMatrix(matrix ? *matrix : SkMatrix::I());
}
-static void concat(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) {
+static void concat(jlong canvasHandle, jlong matrixHandle) {
const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
get_canvas(canvasHandle)->concat(*matrix);
}
-static void rotate(JNIEnv*, jobject, jlong canvasHandle, jfloat degrees) {
+static void rotate(jlong canvasHandle, jfloat degrees) {
get_canvas(canvasHandle)->rotate(degrees);
}
-static void scale(JNIEnv*, jobject, jlong canvasHandle, jfloat sx, jfloat sy) {
+static void scale(jlong canvasHandle, jfloat sx, jfloat sy) {
get_canvas(canvasHandle)->scale(sx, sy);
}
-static void skew(JNIEnv*, jobject, jlong canvasHandle, jfloat sx, jfloat sy) {
+static void skew(jlong canvasHandle, jfloat sx, jfloat sy) {
get_canvas(canvasHandle)->skew(sx, sy);
}
-static void translate(JNIEnv*, jobject, jlong canvasHandle, jfloat dx, jfloat dy) {
+static void translate(jlong canvasHandle, jfloat dx, jfloat dy) {
get_canvas(canvasHandle)->translate(dx, dy);
}
@@ -171,13 +161,13 @@
return result ? JNI_TRUE : JNI_FALSE;
}
-static jboolean quickRejectRect(JNIEnv* env, jobject, jlong canvasHandle,
+static jboolean quickRejectRect(jlong canvasHandle,
jfloat left, jfloat top, jfloat right, jfloat bottom) {
bool result = get_canvas(canvasHandle)->quickRejectRect(left, top, right, bottom);
return result ? JNI_TRUE : JNI_FALSE;
}
-static jboolean quickRejectPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle) {
+static jboolean quickRejectPath(jlong canvasHandle, jlong pathHandle) {
SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
bool result = get_canvas(canvasHandle)->quickRejectPath(*path);
return result ? JNI_TRUE : JNI_FALSE;
@@ -205,14 +195,14 @@
return static_cast<SkClipOp>(rgnOp);
}
-static jboolean clipRect(JNIEnv*, jobject, jlong canvasHandle, jfloat l, jfloat t,
+static jboolean clipRect(jlong canvasHandle, jfloat l, jfloat t,
jfloat r, jfloat b, jint opHandle) {
bool nonEmptyClip = get_canvas(canvasHandle)->clipRect(l, t, r, b,
opHandleToClipOp(opHandle));
return nonEmptyClip ? JNI_TRUE : JNI_FALSE;
}
-static jboolean clipPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle,
+static jboolean clipPath(jlong canvasHandle, jlong pathHandle,
jint opHandle) {
SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
bool nonEmptyClip = get_canvas(canvasHandle)->clipPath(path, opHandleToClipOp(opHandle));
@@ -565,7 +555,7 @@
env->ReleaseStringChars(text, jchars);
}
-static void setDrawFilter(JNIEnv* env, jobject, jlong canvasHandle, jlong filterHandle) {
+static void setDrawFilter(jlong canvasHandle, jlong filterHandle) {
get_canvas(canvasHandle)->setDrawFilter(reinterpret_cast<SkDrawFilter*>(filterHandle));
}
@@ -587,6 +577,9 @@
// ------------ @FastNative ----------------
{"nSetBitmap", "(JLandroid/graphics/Bitmap;)V", (void*) CanvasJNI::setBitmap},
+ {"nGetClipBounds","(JLandroid/graphics/Rect;)Z", (void*) CanvasJNI::getClipBounds},
+
+ // ------------ @CriticalNative ----------------
{"nIsOpaque","(J)Z", (void*) CanvasJNI::isOpaque},
{"nGetWidth","(J)I", (void*) CanvasJNI::getWidth},
{"nGetHeight","(J)I", (void*) CanvasJNI::getHeight},
@@ -595,16 +588,15 @@
{"nSaveLayer","(JFFFFJI)I", (void*) CanvasJNI::saveLayer},
{"nSaveLayerAlpha","(JFFFFII)I", (void*) CanvasJNI::saveLayerAlpha},
{"nGetSaveCount","(J)I", (void*) CanvasJNI::getSaveCount},
- {"nRestore","(JZ)V", (void*) CanvasJNI::restore},
- {"nRestoreToCount","(JIZ)V", (void*) CanvasJNI::restoreToCount},
- {"nGetCTM", "(JJ)V", (void*)CanvasJNI::getCTM},
+ {"nRestore","(J)Z", (void*) CanvasJNI::restore},
+ {"nRestoreToCount","(JI)V", (void*) CanvasJNI::restoreToCount},
+ {"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix},
{"nSetMatrix","(JJ)V", (void*) CanvasJNI::setMatrix},
{"nConcat","(JJ)V", (void*) CanvasJNI::concat},
{"nRotate","(JF)V", (void*) CanvasJNI::rotate},
{"nScale","(JFF)V", (void*) CanvasJNI::scale},
{"nSkew","(JFF)V", (void*) CanvasJNI::skew},
{"nTranslate","(JFF)V", (void*) CanvasJNI::translate},
- {"nGetClipBounds","(JLandroid/graphics/Rect;)Z", (void*) CanvasJNI::getClipBounds},
{"nQuickReject","(JJ)Z", (void*) CanvasJNI::quickRejectPath},
{"nQuickReject","(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
{"nClipRect","(JFFFFI)Z", (void*) CanvasJNI::clipRect},
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
new file mode 100644
index 0000000..74527d9
--- /dev/null
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "HardwareBuffer"
+
+#include "jni.h"
+#include "JNIHelp.h"
+
+#include "android_os_Parcel.h"
+#include "android/graphics/GraphicsJNI.h"
+
+#include <android/hardware_buffer.h>
+#include <android_runtime/android_hardware_HardwareBuffer.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+
+#include <binder/Parcel.h>
+#include <gui/IGraphicBufferAlloc.h>
+#include <gui/ISurfaceComposer.h>
+#include <ui/GraphicBuffer.h>
+
+#include <private/gui/ComposerService.h>
+
+#include "core_jni_helpers.h"
+
+using namespace android;
+
+// ----------------------------------------------------------------------------
+// Defines
+// ----------------------------------------------------------------------------
+
+// Debug
+static const bool kDebugGraphicBuffer = false;
+
+// ----------------------------------------------------------------------------
+// Types
+// ----------------------------------------------------------------------------
+
+static struct {
+ jclass clazz;
+ jfieldID mNativeObject;
+ jmethodID ctor;
+} gHardwareBufferClassInfo;
+
+class GraphicBufferWrapper {
+public:
+ explicit GraphicBufferWrapper(const sp<GraphicBuffer>& buffer)
+ : buffer(buffer) {}
+
+ sp<GraphicBuffer> buffer;
+};
+
+
+// ----------------------------------------------------------------------------
+// Helper functions
+// ----------------------------------------------------------------------------
+
+static inline bool containsBits(uint64_t mask, uint64_t bitsToCheck) {
+ return (mask & bitsToCheck) == bitsToCheck;
+}
+
+// ----------------------------------------------------------------------------
+// HardwareBuffer lifecycle
+// ----------------------------------------------------------------------------
+
+static jlong android_hardware_HardwareBuffer_create(JNIEnv* env, jobject clazz,
+ jint width, jint height, jint format, jint layers, jlong usage) {
+
+ sp<ISurfaceComposer> composer(ComposerService::getComposerService());
+ sp<IGraphicBufferAlloc> alloc(composer->createGraphicBufferAlloc());
+ if (alloc == NULL) {
+ if (kDebugGraphicBuffer) {
+ ALOGW("createGraphicBufferAlloc() failed in HardwareBuffer.create()");
+ }
+ return NULL;
+ }
+
+ // TODO: update createGraphicBuffer to take two 64-bit values.
+ int pixelFormat = android_hardware_HardwareBuffer_convertToPixelFormat(format);
+ if (pixelFormat == 0) {
+ if (kDebugGraphicBuffer) {
+ ALOGW("createGraphicBufferAlloc() invalid pixel format in HardwareBuffer.create()");
+ }
+ return NULL;
+ }
+ uint32_t grallocUsage = android_hardware_HardwareBuffer_convertToGrallocUsageBits(usage, 0);
+ status_t error;
+ sp<GraphicBuffer> buffer(alloc->createGraphicBuffer(width, height, pixelFormat,
+ layers, grallocUsage, &error));
+ if (buffer == NULL) {
+ if (kDebugGraphicBuffer) {
+ ALOGW("createGraphicBuffer() failed in HardwareBuffer.create()");
+ }
+ return NULL;
+ }
+
+ GraphicBufferWrapper* wrapper = new GraphicBufferWrapper(buffer);
+ return reinterpret_cast<jlong>(wrapper);
+}
+
+static void destroyWrapper(GraphicBufferWrapper* wrapper) {
+ delete wrapper;
+}
+
+static jlong android_hardware_HardwareBuffer_getNativeFinalizer(JNIEnv* env,
+ jobject clazz) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyWrapper));
+}
+
+//----------------------------------------------------------------------------
+// Accessors
+// ----------------------------------------------------------------------------
+
+static inline GraphicBuffer* GraphicBufferWrapper_to_GraphicBuffer(
+ jlong nativeObject) {
+ return reinterpret_cast<GraphicBufferWrapper*>(nativeObject)->buffer.get();
+}
+
+static jint android_hardware_HardwareBuffer_getWidth(JNIEnv* env, jobject clazz,
+ jlong nativeObject) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+ return static_cast<jint>(buffer->getWidth());
+}
+
+static jint android_hardware_HardwareBuffer_getHeight(JNIEnv* env,
+ jobject clazz, jlong nativeObject) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+ return static_cast<jint>(buffer->getHeight());
+}
+
+static jint android_hardware_HardwareBuffer_getFormat(JNIEnv* env,
+ jobject clazz, jlong nativeObject) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+ return static_cast<jint>(android_hardware_HardwareBuffer_convertFromPixelFormat(
+ buffer->getPixelFormat()));
+}
+
+static jint android_hardware_HardwareBuffer_getLayers(JNIEnv* env,
+ jobject clazz, jlong nativeObject) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+ return static_cast<jint>(buffer->getLayerCount());
+}
+
+static jlong android_hardware_HardwareBuffer_getUsage(JNIEnv* env,
+ jobject clazz, jlong nativeObject) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+ return android_hardware_HardwareBuffer_convertFromGrallocUsageBits(
+ buffer->getUsage());
+}
+
+// ----------------------------------------------------------------------------
+// Serialization
+// ----------------------------------------------------------------------------
+
+static void android_hardware_HardwareBuffer_write(JNIEnv* env, jobject clazz,
+ jlong nativeObject, jobject dest) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+ Parcel* parcel = parcelForJavaObject(env, dest);
+ if (parcel) {
+ parcel->write(*buffer);
+ }
+}
+
+static jlong android_hardware_HardwareBuffer_read(JNIEnv* env, jobject clazz,
+ jobject in) {
+ Parcel* parcel = parcelForJavaObject(env, in);
+ if (parcel) {
+ sp<GraphicBuffer> buffer = new GraphicBuffer();
+ parcel->read(*buffer);
+ return reinterpret_cast<jlong>(new GraphicBufferWrapper(buffer));
+ }
+
+ return NULL;
+}
+
+// ----------------------------------------------------------------------------
+// Public functions
+// ----------------------------------------------------------------------------
+
+namespace android {
+
+AHardwareBuffer* android_hardware_HardwareBuffer_getNativeHardwareBuffer(
+ JNIEnv* env, jobject hardwareBufferObj) {
+ if (env->IsInstanceOf(hardwareBufferObj, gHardwareBufferClassInfo.clazz)) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(
+ env->GetLongField(hardwareBufferObj, gHardwareBufferClassInfo.mNativeObject));
+ return reinterpret_cast<AHardwareBuffer*>(buffer);
+ } else {
+ return nullptr;
+ }
+}
+
+jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
+ JNIEnv* env, AHardwareBuffer* hardwareBuffer) {
+ GraphicBuffer* buffer = reinterpret_cast<GraphicBuffer*>(hardwareBuffer);
+ GraphicBufferWrapper* wrapper = new GraphicBufferWrapper(buffer);
+ jobject hardwareBufferObj = env->NewObject(gHardwareBufferClassInfo.clazz,
+ gHardwareBufferClassInfo.ctor, reinterpret_cast<jlong>(wrapper));
+ if (hardwareBufferObj == NULL) {
+ delete wrapper;
+ if (env->ExceptionCheck()) {
+ ALOGE("Could not create instance of HardwareBuffer from AHardwareBuffer.");
+ LOGE_EX(env);
+ env->ExceptionClear();
+ }
+ return nullptr;
+ }
+ return hardwareBufferObj;
+}
+
+uint32_t android_hardware_HardwareBuffer_convertFromPixelFormat(uint32_t format) {
+ switch (format) {
+ case PIXEL_FORMAT_RGBA_8888:
+ return AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
+ case PIXEL_FORMAT_RGBX_8888:
+ return AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM;
+ case PIXEL_FORMAT_RGB_565:
+ return AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM;
+ case PIXEL_FORMAT_RGB_888:
+ return AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM;
+ case PIXEL_FORMAT_RGBA_FP16:
+ return AHARDWAREBUFFER_FORMAT_R16G16B16A16_SFLOAT;
+ default:
+ ALOGE("Unknown pixel format %u", format);
+ return 0;
+ }
+}
+
+uint32_t android_hardware_HardwareBuffer_convertToPixelFormat(uint32_t format) {
+ switch (format) {
+ case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
+ return PIXEL_FORMAT_RGBA_8888;
+ case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
+ return PIXEL_FORMAT_RGBX_8888;
+ case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
+ return PIXEL_FORMAT_RGB_565;
+ case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
+ return PIXEL_FORMAT_RGB_888;
+ case AHARDWAREBUFFER_FORMAT_R16G16B16A16_SFLOAT:
+ return PIXEL_FORMAT_RGBA_FP16;
+ default:
+ ALOGE("Unknown AHardwareBuffer format %u", format);
+ return 0;
+ }
+}
+
+uint32_t android_hardware_HardwareBuffer_convertToGrallocUsageBits(uint64_t usage0,
+ uint64_t usage1) {
+ uint32_t bits = 0;
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_CPU_READ))
+ bits |= GRALLOC_USAGE_SW_READ_RARELY;
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_CPU_READ_OFTEN))
+ bits |= GRALLOC_USAGE_SW_READ_OFTEN;
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_CPU_WRITE))
+ bits |= GRALLOC_USAGE_SW_WRITE_RARELY;
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_CPU_WRITE_OFTEN))
+ bits |= GRALLOC_USAGE_SW_WRITE_OFTEN;
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_GPU_SAMPLED_IMAGE))
+ bits |= GRALLOC_USAGE_HW_TEXTURE;
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_GPU_COLOR_OUTPUT))
+ bits |= GRALLOC_USAGE_HW_RENDER;
+ // Not sure what this should be.
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_GPU_CUBEMAP)) bits |= 0;
+ //if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_GPU_DATA_BUFFER) bits |= 0;
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_VIDEO_ENCODE))
+ bits |= GRALLOC_USAGE_HW_VIDEO_ENCODER;
+ if (containsBits(usage0, AHARDWAREBUFFER_USAGE0_PROTECTED_CONTENT))
+ bits |= GRALLOC_USAGE_PROTECTED;
+
+ (void)usage1;
+
+ return bits;
+}
+
+uint64_t android_hardware_HardwareBuffer_convertFromGrallocUsageBits(uint64_t usage0) {
+ uint64_t bits = 0;
+ if (containsBits(usage0, GRALLOC_USAGE_SW_READ_RARELY))
+ bits |= AHARDWAREBUFFER_USAGE0_CPU_READ;
+ if (containsBits(usage0, GRALLOC_USAGE_SW_READ_OFTEN))
+ bits |= AHARDWAREBUFFER_USAGE0_CPU_READ_OFTEN;
+ if (containsBits(usage0, GRALLOC_USAGE_SW_WRITE_RARELY))
+ bits |= AHARDWAREBUFFER_USAGE0_CPU_WRITE;
+ if (containsBits(usage0, GRALLOC_USAGE_SW_WRITE_OFTEN))
+ bits |= AHARDWAREBUFFER_USAGE0_CPU_WRITE_OFTEN;
+ if (containsBits(usage0, GRALLOC_USAGE_HW_TEXTURE))
+ bits |= AHARDWAREBUFFER_USAGE0_GPU_SAMPLED_IMAGE;
+ if (containsBits(usage0, GRALLOC_USAGE_HW_RENDER))
+ bits |= AHARDWAREBUFFER_USAGE0_GPU_COLOR_OUTPUT;
+ if (containsBits(usage0, GRALLOC_USAGE_HW_VIDEO_ENCODER))
+ bits |= AHARDWAREBUFFER_USAGE0_VIDEO_ENCODE;
+ if (containsBits(usage0, GRALLOC_USAGE_PROTECTED))
+ bits |= AHARDWAREBUFFER_USAGE0_PROTECTED_CONTENT;
+
+ return bits;
+}
+
+} // namespace android
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+const char* const kClassPathName = "android/hardware/HardwareBuffer";
+
+static const JNINativeMethod gMethods[] = {
+ { "nCreateHardwareBuffer", "(IIIIJ)J", (void*) android_hardware_HardwareBuffer_create },
+ { "nGetNativeFinalizer", "()J", (void*) android_hardware_HardwareBuffer_getNativeFinalizer },
+ { "nWriteHardwareBufferToParcel", "(JLandroid/os/Parcel;)V",
+ (void*) android_hardware_HardwareBuffer_write },
+ { "nReadHardwareBufferFromParcel", "(Landroid/os/Parcel;)J",
+ (void*) android_hardware_HardwareBuffer_read },
+
+ // --------------- @FastNative ----------------------
+ { "nGetWidth", "(J)I", (void*) android_hardware_HardwareBuffer_getWidth },
+ { "nGetHeight", "(J)I", (void*) android_hardware_HardwareBuffer_getHeight },
+ { "nGetFormat", "(J)I", (void*) android_hardware_HardwareBuffer_getFormat },
+ { "nGetLayers", "(J)I", (void*) android_hardware_HardwareBuffer_getLayers },
+ { "nGetUsage", "(J)J", (void*) android_hardware_HardwareBuffer_getUsage },
+};
+
+int register_android_hardware_HardwareBuffer(JNIEnv* env) {
+ int err = RegisterMethodsOrDie(env, kClassPathName, gMethods,
+ NELEM(gMethods));
+
+ jclass clazz = FindClassOrDie(env, "android/hardware/HardwareBuffer");
+ gHardwareBufferClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gHardwareBufferClassInfo.mNativeObject = GetFieldIDOrDie(env,
+ gHardwareBufferClassInfo.clazz, "mNativeObject", "J");
+ gHardwareBufferClassInfo.ctor = GetMethodIDOrDie(env,
+ gHardwareBufferClassInfo.clazz, "<init>", "(J)V");
+
+ return err;
+}
diff --git a/core/jni/android_util_EventLog.cpp b/core/jni/android_util_EventLog.cpp
index 0b4fbcc..8a7600b 100644
--- a/core/jni/android_util_EventLog.cpp
+++ b/core/jni/android_util_EventLog.cpp
@@ -144,26 +144,22 @@
return ctx.write();
}
-/*
- * In class android.util.EventLog:
- * static native void readEvents(int[] tags, Collection<Event> output)
- *
- * Reads events from the event log
- */
-static void android_util_EventLog_readEvents(JNIEnv* env, jobject clazz UNUSED,
- jintArray tags,
- jobject out) {
-
- if (tags == NULL || out == NULL) {
- jniThrowNullPointerException(env, NULL);
+static void readEvents(JNIEnv* env, int loggerMode, jintArray tags, jlong startTime, jobject out) {
+ struct logger_list *logger_list;
+ if (startTime) {
+ logger_list = android_logger_list_alloc_time(loggerMode,
+ log_time(startTime / NS_PER_SEC, startTime % NS_PER_SEC), 0);
+ } else {
+ logger_list = android_logger_list_alloc(loggerMode, 0, 0);
+ }
+ if (!logger_list) {
+ jniThrowIOException(env, errno);
return;
}
- struct logger_list *logger_list = android_logger_list_open(
- LOG_ID_EVENTS, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 0, 0);
-
- if (!logger_list) {
+ if (!android_logger_open(logger_list, LOG_ID_EVENTS)) {
jniThrowIOException(env, errno);
+ android_logger_list_free(logger_list);
return;
}
@@ -228,6 +224,41 @@
}
/*
+ * In class android.util.EventLog:
+ * static native void readEvents(int[] tags, Collection<Event> output)
+ *
+ * Reads events from the event log
+ */
+static void android_util_EventLog_readEvents(JNIEnv* env, jobject clazz UNUSED,
+ jintArray tags,
+ jobject out) {
+
+ if (tags == NULL || out == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return;
+ }
+
+ readEvents(env, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, tags, 0, out);
+ }
+/*
+ * In class android.util.EventLog:
+ * static native void readEventsOnWrapping(int[] tags, long timestamp, Collection<Event> output)
+ *
+ * Reads events from the event log, blocking until events after timestamp are to be overwritten.
+ */
+static void android_util_EventLog_readEventsOnWrapping(JNIEnv* env, jobject clazz UNUSED,
+ jintArray tags,
+ jlong timestamp,
+ jobject out) {
+ if (tags == NULL || out == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return;
+ }
+ readEvents(env, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK | ANDROID_LOG_WRAP,
+ tags, timestamp, out);
+}
+
+/*
* JNI registration.
*/
static const JNINativeMethod gRegisterMethods[] = {
@@ -247,6 +278,10 @@
"([ILjava/util/Collection;)V",
(void*) android_util_EventLog_readEvents
},
+ { "readEventsOnWrapping",
+ "([IJLjava/util/Collection;)V",
+ (void*) android_util_EventLog_readEventsOnWrapping
+ },
};
static struct { const char *name; jclass *clazz; } gClasses[] = {
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 3eccc42..d75d5c1 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -88,7 +88,6 @@
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/jni/include/android_runtime/android_hardware_HardwareBuffer.h b/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
new file mode 100644
index 0000000..60e065c
--- /dev/null
+++ b/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_HARDWARE_HARDWAREBUFFER_H
+#define _ANDROID_HARDWARE_HARDWAREBUFFER_H
+
+#include <android/hardware_buffer.h>
+
+#include "jni.h"
+
+namespace android {
+
+/* Gets the underlying AHardwareBuffer for a HardwareBuffer. */
+extern AHardwareBuffer* android_hardware_HardwareBuffer_getNativeHardwareBuffer(
+ JNIEnv* env, jobject hardwareBufferObj);
+
+/* Returns a HardwareBuffer wrapper for the underlying AHardwareBuffer. */
+extern jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
+ JNIEnv* env, AHardwareBuffer* hardwareBuffer);
+
+/* Convert from HAL_PIXEL_FORMAT values to AHARDWAREBUFFER_FORMAT values. */
+extern uint32_t android_hardware_HardwareBuffer_convertFromPixelFormat(
+ uint32_t format);
+
+/* Convert from AHARDWAREBUFFER_FORMAT values to HAL_PIXEL_FORMAT values. */
+extern uint32_t android_hardware_HardwareBuffer_convertToPixelFormat(
+ uint32_t format);
+
+/* Convert from AHARDWAREBUFFER_USAGE* flags to to gralloc usage flags. */
+extern uint32_t android_hardware_HardwareBuffer_convertToGrallocUsageBits(
+ uint64_t usage0, uint64_t usage1);
+
+/* Convert from gralloc usage flags to to AHARDWAREBUFFER_USAGE0* flags. */
+extern uint64_t android_hardware_HardwareBuffer_convertFromGrallocUsageBits(
+ uint64_t usage0);
+
+} // namespace android
+
+#endif // _ANDROID_HARDWARE_HARDWAREBUFFER_H
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index c3b0ff1..ac9ebe0 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -22,6 +22,7 @@
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/service/fingerprint.proto";
import "frameworks/base/core/proto/android/service/netstats.proto";
+import "frameworks/base/core/proto/android/providers/settings.proto";
package android.os;
@@ -51,4 +52,5 @@
// System Services
android.service.fingerprint.FingerprintServiceDumpProto fingerprint = 3000;
android.service.NetworkStatsServiceDumpProto netstats = 3001;
+ android.providers.settings.SettingsServiceDumpProto settings = 3002;
}
diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto
new file mode 100644
index 0000000..7674d7a
--- /dev/null
+++ b/core/proto/android/providers/settings.proto
@@ -0,0 +1,605 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+package android.providers.settings;
+
+option java_multiple_files = true;
+option java_outer_classname = "SettingsServiceProto";
+
+message SettingsServiceDumpProto {
+ // Per user settings
+ repeated UserSettingsProto user_settings = 1;
+
+ // Global settings
+ GlobalSettingsProto global_settings = 2;
+}
+
+message UserSettingsProto {
+ // Should be 0, 10, 11, 12, etc. where 0 is the owner.
+ int32 user_id = 1;
+
+ // The secure settings for this user
+ SecureSettingsProto secure_settings = 2;
+
+ // The system settings for this user
+ SystemSettingsProto system_settings = 3;
+}
+
+message GlobalSettingsProto {
+ // Historical operations
+ repeated SettingsOperationProto historical_op = 1;
+
+ SettingProto add_users_when_locked = 2;
+ SettingProto enable_accessibility_global_gesture_enabled = 3;
+ SettingProto airplane_mode_on = 4;
+ SettingProto theater_mode_on = 5;
+ SettingProto radio_bluetooth = 6;
+ SettingProto radio_wifi = 7;
+ SettingProto radio_wimax = 8;
+ SettingProto radio_cell = 9;
+ SettingProto radio_nfc = 10;
+ SettingProto airplane_mode_radios = 11;
+ SettingProto airplane_mode_toggleable_radios = 12;
+ SettingProto bluetooth_disabled_profiles = 13;
+ SettingProto bluetooth_interoperability_list = 14;
+ SettingProto wifi_sleep_policy = 15;
+ SettingProto auto_time = 16;
+ SettingProto auto_time_zone = 17;
+ SettingProto car_dock_sound = 18;
+ SettingProto car_undock_sound = 19;
+ SettingProto desk_dock_sound = 20;
+ SettingProto desk_undock_sound = 21;
+ SettingProto dock_sounds_enabled = 22;
+ SettingProto dock_sounds_enabled_when_accessibility = 23;
+ SettingProto lock_sound = 24;
+ SettingProto unlock_sound = 25;
+ SettingProto trusted_sound = 26;
+ SettingProto low_battery_sound = 27;
+ SettingProto power_sounds_enabled = 28;
+ SettingProto wireless_charging_started_sound = 29;
+ SettingProto charging_sounds_enabled = 30;
+ SettingProto stay_on_while_plugged_in = 31;
+ SettingProto bugreport_in_power_menu = 32;
+ SettingProto adb_enabled = 33;
+ SettingProto debug_view_attributes = 34;
+ SettingProto assisted_gps_enabled = 35;
+ SettingProto bluetooth_on = 36;
+ SettingProto cdma_cell_broadcast_sms = 37;
+ SettingProto cdma_roaming_mode = 38;
+ SettingProto cdma_subscription_mode = 39;
+ SettingProto data_activity_timeout_mobile = 40;
+ SettingProto data_activity_timeout_wifi = 41;
+ SettingProto data_roaming = 42;
+ SettingProto mdc_initial_max_retry = 43;
+ SettingProto force_allow_on_external = 44;
+ SettingProto development_force_resizable_activities = 45;
+ SettingProto development_enable_freeform_windows_support = 46;
+ SettingProto development_settings_enabled = 47;
+ SettingProto device_provisioned = 48;
+ SettingProto device_provisioning_mobile_data_enabled = 49;
+ SettingProto display_size_forced = 50;
+ SettingProto display_scaling_force = 51;
+ SettingProto download_max_bytes_over_mobile = 52;
+ SettingProto download_recommended_max_bytes_over_mobile = 53;
+ SettingProto hdmi_control_enabled = 54;
+ SettingProto hdmi_system_audio_enabled = 55;
+ SettingProto hdmi_control_auto_wakeup_enabled = 56;
+ SettingProto hdmi_control_auto_device_off_enabled = 57;
+ SettingProto mhl_input_switching_enabled = 58;
+ SettingProto mhl_power_charge_enabled = 59;
+ SettingProto mobile_data = 60;
+ SettingProto mobile_data_always_on = 61;
+ SettingProto connectivity_metrics_buffer_size = 62;
+ SettingProto netstats_enabled = 63;
+ SettingProto netstats_poll_interval = 64;
+ SettingProto netstats_time_cache_max_age = 65;
+ SettingProto netstats_global_alert_bytes = 66;
+ SettingProto netstats_sample_enabled = 67;
+ SettingProto netstats_dev_bucket_duration = 68;
+ SettingProto netstats_dev_persist_bytes = 69;
+ SettingProto netstats_dev_rotate_age = 70;
+ SettingProto netstats_dev_delete_age = 71;
+ SettingProto netstats_uid_bucket_duration = 72;
+ SettingProto netstats_uid_persist_bytes = 73;
+ SettingProto netstats_uid_rotate_age = 74;
+ SettingProto netstats_uid_delete_age = 75;
+ SettingProto netstats_uid_tag_bucket_duration = 76;
+ SettingProto netstats_uid_tag_persist_bytes = 77;
+ SettingProto netstats_uid_tag_rotate_age = 78;
+ SettingProto netstats_uid_tag_delete_age = 79;
+ SettingProto network_preference = 80;
+ SettingProto network_scorer_app = 81;
+ SettingProto nitz_update_diff = 82;
+ SettingProto nitz_update_spacing = 83;
+ SettingProto ntp_server = 84;
+ SettingProto ntp_timeout = 85;
+ SettingProto storage_benchmark_interval = 86;
+ SettingProto dns_resolver_sample_validity_seconds = 87;
+ SettingProto dns_resolver_success_threshold_percent = 88;
+ SettingProto dns_resolver_min_samples = 89;
+ SettingProto dns_resolver_max_samples = 90;
+ SettingProto ota_disable_automatic_update = 91;
+ SettingProto package_verifier_enable = 92;
+ SettingProto package_verifier_timeout = 93;
+ SettingProto package_verifier_default_response = 94;
+ SettingProto package_verifier_setting_visible = 95;
+ SettingProto package_verifier_include_adb = 96;
+ SettingProto fstrim_mandatory_interval = 97;
+ SettingProto pdp_watchdog_poll_interval_ms = 98;
+ SettingProto pdp_watchdog_long_poll_interval_ms = 99;
+ SettingProto pdp_watchdog_error_poll_interval_ms = 100;
+ SettingProto pdp_watchdog_trigger_packet_count = 101;
+ SettingProto pdp_watchdog_error_poll_count = 102;
+ SettingProto pdp_watchdog_max_pdp_reset_fail_count = 103;
+ SettingProto sampling_profiler_ms = 104;
+ SettingProto setup_prepaid_data_service_url = 105;
+ SettingProto setup_prepaid_detection_target_url = 106;
+ SettingProto setup_prepaid_detection_redir_host = 107;
+ SettingProto sms_outgoing_check_interval_ms = 108;
+ SettingProto sms_outgoing_check_max_count = 109;
+ SettingProto sms_short_code_confirmation = 110;
+ SettingProto sms_short_code_rule = 111;
+ SettingProto tcp_default_init_rwnd = 112;
+ SettingProto tether_supported = 113;
+ SettingProto tether_dun_required = 114;
+ SettingProto tether_dun_apn = 115;
+ SettingProto carrier_app_whitelist = 116;
+ SettingProto usb_mass_storage_enabled = 117;
+ SettingProto use_google_mail = 118;
+ SettingProto webview_data_reduction_proxy_key = 119;
+ SettingProto webview_fallback_logic_enabled = 120;
+ SettingProto webview_provider = 121;
+ SettingProto webview_multiprocess = 122;
+ SettingProto network_switch_notification_daily_limit = 123;
+ SettingProto network_switch_notification_rate_limit_millis = 124;
+ SettingProto network_avoid_bad_wifi = 125;
+ SettingProto wifi_display_on = 126;
+ SettingProto wifi_display_certification_on = 127;
+ SettingProto wifi_display_wps_config = 128;
+ SettingProto wifi_networks_available_notification_on = 129;
+ SettingProto wimax_networks_available_notification_on = 130;
+ SettingProto wifi_networks_available_repeat_delay = 131;
+ SettingProto wifi_country_code = 132;
+ SettingProto wifi_framework_scan_interval_ms = 133;
+ SettingProto wifi_idle_ms = 134;
+ SettingProto wifi_num_open_networks_kept = 135;
+ SettingProto wifi_on = 136;
+ SettingProto wifi_scan_always_available = 137;
+ SettingProto wifi_wakeup_enabled = 138;
+ SettingProto network_recommendations_enabled = 139;
+ SettingProto ble_scan_always_available = 140;
+ SettingProto wifi_saved_state = 141;
+ SettingProto wifi_supplicant_scan_interval_ms = 142;
+ SettingProto wifi_enhanced_auto_join = 143;
+ SettingProto wifi_network_show_rssi = 144;
+ SettingProto wifi_scan_interval_when_p2p_connected_ms = 145;
+ SettingProto wifi_watchdog_on = 146;
+ SettingProto wifi_watchdog_poor_network_test_enabled = 147;
+ SettingProto wifi_suspend_optimizations_enabled = 148;
+ SettingProto wifi_verbose_logging_enabled = 149;
+ SettingProto wifi_max_dhcp_retry_count = 150;
+ SettingProto wifi_mobile_data_transition_wakelock_timeout_ms = 151;
+ SettingProto wifi_device_owner_configs_lockdown = 152;
+ SettingProto wifi_frequency_band = 153;
+ SettingProto wifi_p2p_device_name = 154;
+ SettingProto wifi_reenable_delay_ms = 155;
+ SettingProto wifi_ephemeral_out_of_range_timeout_ms = 156;
+ SettingProto data_stall_alarm_non_aggressive_delay_in_ms = 157;
+ SettingProto data_stall_alarm_aggressive_delay_in_ms = 158;
+ SettingProto provisioning_apn_alarm_delay_in_ms = 159;
+ SettingProto gprs_register_check_period_ms = 160;
+ SettingProto wtf_is_fatal = 161;
+ SettingProto mode_ringer = 162;
+ SettingProto overlay_display_devices = 163;
+ SettingProto battery_discharge_duration_threshold = 164;
+ SettingProto battery_discharge_threshold = 165;
+ SettingProto send_action_app_error = 166;
+ SettingProto dropbox_age_seconds = 167;
+ SettingProto dropbox_max_files = 168;
+ SettingProto dropbox_quota_kb = 169;
+ SettingProto dropbox_quota_percent = 170;
+ SettingProto dropbox_reserve_percent = 171;
+ SettingProto dropbox_tag_prefix = 172;
+ SettingProto error_logcat_prefix = 173;
+ SettingProto sys_free_storage_log_interval = 174;
+ SettingProto disk_free_change_reporting_threshold = 175;
+ SettingProto sys_storage_threshold_percentage = 176;
+ SettingProto sys_storage_threshold_max_bytes = 177;
+ SettingProto sys_storage_full_threshold_bytes = 178;
+ SettingProto sync_max_retry_delay_in_seconds = 179;
+ SettingProto connectivity_change_delay = 180;
+ SettingProto connectivity_sampling_interval_in_seconds = 181;
+ SettingProto pac_change_delay = 182;
+ SettingProto captive_portal_mode = 183;
+ SettingProto captive_portal_server = 184;
+ SettingProto captive_portal_https_url = 185;
+ SettingProto captive_portal_http_url = 186;
+ SettingProto captive_portal_fallback_url = 187;
+ SettingProto captive_portal_use_https = 188;
+ SettingProto captive_portal_user_agent = 189;
+ SettingProto nsd_on = 190;
+ SettingProto set_install_location = 191;
+ SettingProto default_install_location = 192;
+ SettingProto inet_condition_debounce_up_delay = 193;
+ SettingProto inet_condition_debounce_down_delay = 194;
+ SettingProto read_external_storage_enforced_default = 195;
+ SettingProto http_proxy = 196;
+ SettingProto global_http_proxy_host = 197;
+ SettingProto global_http_proxy_port = 198;
+ SettingProto global_http_proxy_exclusion_list = 199;
+ SettingProto global_http_proxy_pac = 200;
+ SettingProto set_global_http_proxy = 201;
+ SettingProto default_dns_server = 202;
+ SettingProto bluetooth_headset_priority_prefix = 203;
+ SettingProto bluetooth_a2dp_sink_priority_prefix = 204;
+ SettingProto bluetooth_a2dp_src_priority_prefix = 205;
+ SettingProto bluetooth_input_device_priority_prefix = 206;
+ SettingProto bluetooth_map_priority_prefix = 207;
+ SettingProto bluetooth_map_client_priority_prefix = 208;
+ SettingProto bluetooth_pbap_client_priority_prefix = 209;
+ SettingProto bluetooth_sap_priority_prefix = 210;
+ SettingProto bluetooth_pan_priority_prefix = 211;
+ SettingProto device_idle_constants = 212;
+ SettingProto device_idle_constants_watch = 213;
+ SettingProto app_idle_constants = 214;
+ SettingProto alarm_manager_constants = 215;
+ SettingProto job_scheduler_constants = 216;
+ SettingProto shortcut_manager_constants = 217;
+ SettingProto window_animation_scale = 218;
+ SettingProto transition_animation_scale = 219;
+ SettingProto animator_duration_scale = 220;
+ SettingProto fancy_ime_animations = 221;
+ SettingProto compatibility_mode = 222;
+ SettingProto emergency_tone = 223;
+ SettingProto call_auto_retry = 224;
+ SettingProto emergency_affordance_needed = 225;
+ SettingProto preferred_network_mode = 226;
+ SettingProto debug_app = 227;
+ SettingProto wait_for_debugger = 228;
+ SettingProto low_power_mode = 229;
+ SettingProto low_power_mode_trigger_level = 230;
+ SettingProto always_finish_activities = 231;
+ SettingProto dock_audio_media_enabled = 232;
+ SettingProto encoded_surround_output = 233;
+ SettingProto audio_safe_volume_state = 234;
+ SettingProto tzinfo_update_content_url = 235;
+ SettingProto tzinfo_update_metadata_url = 236;
+ SettingProto selinux_update_content_url = 237;
+ SettingProto selinux_update_metadata_url = 238;
+ SettingProto sms_short_codes_update_content_url = 239;
+ SettingProto sms_short_codes_update_metadata_url = 240;
+ SettingProto apn_db_update_content_url = 241;
+ SettingProto apn_db_update_metadata_url = 242;
+ SettingProto cert_pin_update_content_url = 243;
+ SettingProto cert_pin_update_metadata_url = 244;
+ SettingProto intent_firewall_update_content_url = 245;
+ SettingProto intent_firewall_update_metadata_url = 246;
+ SettingProto selinux_status = 247;
+ SettingProto development_force_rtl = 248;
+ SettingProto low_battery_sound_timeout = 249;
+ SettingProto wifi_bounce_delay_override_ms = 250;
+ SettingProto policy_control = 251;
+ SettingProto zen_mode = 252;
+ SettingProto zen_mode_ringer_level = 253;
+ SettingProto zen_mode_config_etag = 254;
+ SettingProto heads_up_notifications_enabled = 255;
+ SettingProto device_name = 256;
+ SettingProto network_scoring_provisioned = 257;
+ SettingProto require_password_to_decrypt = 258;
+ SettingProto enhanced_4g_mode_enabled = 259;
+ SettingProto vt_ims_enabled = 260;
+ SettingProto wfc_ims_enabled = 261;
+ SettingProto wfc_ims_mode = 262;
+ SettingProto wfc_ims_roaming_mode = 263;
+ SettingProto wfc_ims_roaming_enabled = 264;
+ SettingProto lte_service_forced = 265;
+ SettingProto ephemeral_cookie_max_size_bytes = 266;
+ SettingProto enable_ephemeral_feature = 267;
+ SettingProto uninstalled_ephemeral_app_cache_duration_millis = 268;
+ SettingProto allow_user_switching_when_system_user_locked = 269;
+ SettingProto boot_count = 270;
+ SettingProto safe_boot_disallowed = 271;
+ SettingProto device_demo_mode = 272;
+ SettingProto retail_demo_mode_constants = 273;
+ SettingProto database_downgrade_reason = 274;
+ SettingProto contacts_database_wal_enabled = 275;
+ SettingProto multi_sim_voice_call_subscription = 276;
+ SettingProto multi_sim_voice_prompt = 277;
+ SettingProto multi_sim_data_call_subscription = 278;
+ SettingProto multi_sim_sms_subscription = 279;
+ SettingProto multi_sim_sms_prompt = 280;
+ SettingProto new_contact_aggregator = 281;
+ SettingProto contact_metadata_sync_enabled = 282;
+ SettingProto enable_cellular_on_boot = 283;
+ SettingProto max_notification_enqueue_rate = 284;
+ SettingProto cell_on = 285;
+}
+
+message SecureSettingsProto {
+ // Historical operations
+ repeated SettingsOperationProto historical_op = 1;
+
+ SettingProto android_id = 2;
+ SettingProto default_input_method = 3;
+ SettingProto selected_input_method_subtype = 4;
+ SettingProto input_methods_subtype_history = 5;
+ SettingProto input_method_selector_visibility = 6;
+ SettingProto voice_interaction_service = 7;
+ SettingProto auto_fill_service = 8;
+ SettingProto bluetooth_hci_log = 9;
+ SettingProto user_setup_complete = 10;
+ SettingProto completed_category_prefix = 11;
+ SettingProto enabled_input_methods = 12;
+ SettingProto disabled_system_input_methods = 13;
+ SettingProto show_ime_with_hard_keyboard = 14;
+ SettingProto always_on_vpn_app = 15;
+ SettingProto always_on_vpn_lockdown = 16;
+ SettingProto install_non_market_apps = 17;
+ SettingProto location_mode = 18;
+ SettingProto location_previous_mode = 19;
+ SettingProto lock_to_app_exit_locked = 20;
+ SettingProto lock_screen_lock_after_timeout = 21;
+ SettingProto lock_screen_allow_remote_input = 22;
+ SettingProto show_note_about_notification_hiding = 23;
+ SettingProto trust_agents_initialized = 24;
+ SettingProto parental_control_enabled = 25;
+ SettingProto parental_control_last_update = 26;
+ SettingProto parental_control_redirect_url = 27;
+ SettingProto settings_classname = 28;
+ SettingProto accessibility_enabled = 29;
+ SettingProto touch_exploration_enabled = 30;
+ SettingProto enabled_accessibility_services = 31;
+ SettingProto touch_exploration_granted_accessibility_services = 32;
+ SettingProto accessibility_speak_password = 33;
+ SettingProto accessibility_high_text_contrast_enabled = 34;
+ SettingProto accessibility_script_injection = 35;
+ SettingProto accessibility_screen_reader_url = 36;
+ SettingProto accessibility_web_content_key_bindings = 37;
+ SettingProto accessibility_display_magnification_enabled = 38;
+ SettingProto accessibility_display_magnification_scale = 39;
+ SettingProto accessibility_soft_keyboard_mode = 40;
+ SettingProto accessibility_captioning_enabled = 41;
+ SettingProto accessibility_captioning_locale = 42;
+ SettingProto accessibility_captioning_preset = 43;
+ SettingProto accessibility_captioning_background_color = 44;
+ SettingProto accessibility_captioning_foreground_color = 45;
+ SettingProto accessibility_captioning_edge_type = 46;
+ SettingProto accessibility_captioning_edge_color = 47;
+ SettingProto accessibility_captioning_window_color = 48;
+ SettingProto accessibility_captioning_typeface = 49;
+ SettingProto accessibility_captioning_font_scale = 50;
+ SettingProto accessibility_display_inversion_enabled = 51;
+ SettingProto accessibility_display_daltonizer_enabled = 52;
+ SettingProto accessibility_display_daltonizer = 53;
+ SettingProto accessibility_autoclick_enabled = 54;
+ SettingProto accessibility_autoclick_delay = 55;
+ SettingProto accessibility_large_pointer_icon = 56;
+ SettingProto long_press_timeout = 57;
+ SettingProto multi_press_timeout = 58;
+ SettingProto enabled_print_services = 59;
+ SettingProto disabled_print_services = 60;
+ SettingProto display_density_forced = 61;
+ SettingProto tts_default_rate = 62;
+ SettingProto tts_default_pitch = 63;
+ SettingProto tts_default_synth = 64;
+ SettingProto tts_default_locale = 65;
+ SettingProto tts_enabled_plugins = 66;
+ SettingProto connectivity_release_pending_intent_delay_ms = 67;
+ SettingProto allowed_geolocation_origins = 68;
+ SettingProto preferred_tty_mode = 69;
+ SettingProto enhanced_voice_privacy_enabled = 70;
+ SettingProto tty_mode_enabled = 71;
+ SettingProto backup_enabled = 72;
+ SettingProto backup_auto_restore = 73;
+ SettingProto backup_provisioned = 74;
+ SettingProto backup_transport = 75;
+ SettingProto last_setup_shown = 76;
+ SettingProto search_global_search_activity = 77;
+ SettingProto search_num_promoted_sources = 78;
+ SettingProto search_max_results_to_display = 79;
+ SettingProto search_max_results_per_source = 80;
+ SettingProto search_web_results_override_limit = 81;
+ SettingProto search_promoted_source_deadline_millis = 82;
+ SettingProto search_source_timeout_millis = 83;
+ SettingProto search_prefill_millis = 84;
+ SettingProto search_max_stat_age_millis = 85;
+ SettingProto search_max_source_event_age_millis = 86;
+ SettingProto search_min_impressions_for_source_ranking = 87;
+ SettingProto search_min_clicks_for_source_ranking = 88;
+ SettingProto search_max_shortcuts_returned = 89;
+ SettingProto search_query_thread_core_pool_size = 90;
+ SettingProto search_query_thread_max_pool_size = 91;
+ SettingProto search_shortcut_refresh_core_pool_size = 92;
+ SettingProto search_shortcut_refresh_max_pool_size = 93;
+ SettingProto search_thread_keepalive_seconds = 94;
+ SettingProto search_per_source_concurrent_query_limit = 95;
+ SettingProto mount_play_notification_snd = 96;
+ SettingProto mount_ums_autostart = 97;
+ SettingProto mount_ums_prompt = 98;
+ SettingProto mount_ums_notify_enabled = 99;
+ SettingProto anr_show_background = 100;
+ SettingProto voice_recognition_service = 101;
+ SettingProto package_verifier_user_consent = 102;
+ SettingProto selected_spell_checker = 103;
+ SettingProto selected_spell_checker_subtype = 104;
+ SettingProto spell_checker_enabled = 105;
+ SettingProto incall_power_button_behavior = 106;
+ SettingProto incall_back_button_behavior = 107;
+ SettingProto wake_gesture_enabled = 108;
+ SettingProto doze_enabled = 109;
+ SettingProto doze_always_on = 110;
+ SettingProto doze_pulse_on_pick_up = 111;
+ SettingProto doze_pulse_on_double_tap = 112;
+ SettingProto ui_night_mode = 113;
+ SettingProto screensaver_enabled = 114;
+ SettingProto screensaver_components = 115;
+ SettingProto screensaver_activate_on_dock = 116;
+ SettingProto screensaver_activate_on_sleep = 117;
+ SettingProto screensaver_default_component = 118;
+ SettingProto nfc_payment_default_component = 119;
+ SettingProto nfc_payment_foreground = 120;
+ SettingProto sms_default_application = 121;
+ SettingProto dialer_default_application = 122;
+ SettingProto emergency_assistance_application = 123;
+ SettingProto assist_structure_enabled = 124;
+ SettingProto assist_screenshot_enabled = 125;
+ SettingProto assist_disclosure_enabled = 126;
+ SettingProto enabled_notification_assistant = 127;
+ SettingProto enabled_notification_listeners = 128;
+ SettingProto enabled_notification_policy_access_packages = 129;
+ SettingProto sync_parent_sounds = 130;
+ SettingProto immersive_mode_confirmations = 131;
+ SettingProto print_service_search_uri = 132;
+ SettingProto payment_service_search_uri = 133;
+ SettingProto skip_first_use_hints = 134;
+ SettingProto unsafe_volume_music_active_ms = 135;
+ SettingProto lock_screen_show_notifications = 136;
+ SettingProto tv_input_hidden_inputs = 137;
+ SettingProto tv_input_custom_labels = 138;
+ SettingProto usb_audio_automatic_routing_disabled = 139;
+ SettingProto sleep_timeout = 140;
+ SettingProto double_tap_to_wake = 141;
+ SettingProto assistant = 142;
+ SettingProto camera_gesture_disabled = 143;
+ SettingProto camera_double_tap_power_gesture_disabled = 144;
+ SettingProto camera_double_twist_to_flip_enabled = 145;
+ SettingProto night_display_activated = 146;
+ SettingProto night_display_auto_mode = 147;
+ SettingProto night_display_custom_start_time = 148;
+ SettingProto night_display_custom_end_time = 149;
+ SettingProto brightness_use_twilight = 150;
+ SettingProto enabled_vr_listeners = 151;
+ SettingProto vr_display_mode = 152;
+ SettingProto carrier_apps_handled = 153;
+ SettingProto managed_profile_contact_remote_search = 154;
+ SettingProto automatic_storage_manager_enabled = 155;
+ SettingProto automatic_storage_manager_days_to_retain = 156;
+ SettingProto automatic_storage_manager_bytes_cleared = 157;
+ SettingProto automatic_storage_manager_last_run = 158;
+ SettingProto system_navigation_keys_enabled = 159;
+ SettingProto downloads_backup_enabled = 160;
+ SettingProto downloads_backup_allow_metered = 161;
+ SettingProto downloads_backup_charging_only = 162;
+ SettingProto automatic_storage_manager_downloads_days_to_retain = 163;
+ SettingProto qs_tiles = 164;
+ SettingProto demo_user_setup_complete = 165;
+ SettingProto web_action_enabled = 166;
+ SettingProto device_paired = 167;
+}
+
+message SystemSettingsProto {
+ // Historical operations
+ repeated SettingsOperationProto historical_op = 1;
+
+ SettingProto end_button_behavior = 2;
+ SettingProto advanced_settings = 3;
+ SettingProto bluetooth_discoverability = 4;
+ SettingProto bluetooth_discoverability_timeout = 5;
+ SettingProto font_scale = 6;
+ SettingProto system_locales = 7;
+ SettingProto screen_off_timeout = 8;
+ SettingProto screen_brightness = 9;
+ SettingProto screen_brightness_for_vr = 10;
+ SettingProto screen_brightness_mode = 11;
+ SettingProto screen_auto_brightness_adj = 12;
+ SettingProto mode_ringer_streams_affected = 13;
+ SettingProto mute_streams_affected = 14;
+ SettingProto vibrate_on = 15;
+ SettingProto vibrate_input_devices = 16;
+ SettingProto volume_ring = 17;
+ SettingProto volume_system = 18;
+ SettingProto volume_voice = 19;
+ SettingProto volume_music = 20;
+ SettingProto volume_alarm = 21;
+ SettingProto volume_notification = 22;
+ SettingProto volume_bluetooth_sco = 23;
+ SettingProto volume_master = 24;
+ SettingProto master_mono = 25;
+ SettingProto vibrate_in_silent = 26;
+ SettingProto append_for_last_audible = 27;
+ SettingProto ringtone = 28;
+ SettingProto ringtone_cache = 29;
+ SettingProto notification_sound = 30;
+ SettingProto notification_sound_cache = 31;
+ SettingProto alarm_alert = 32;
+ SettingProto alarm_alert_cache = 33;
+ SettingProto media_button_receiver = 34;
+ SettingProto text_auto_replace = 35;
+ SettingProto text_auto_caps = 36;
+ SettingProto text_auto_punctuate = 37;
+ SettingProto text_show_password = 38;
+ SettingProto show_gtalk_service_status = 39;
+ SettingProto time_12_24 = 40;
+ SettingProto date_format = 41;
+ SettingProto setup_wizard_has_run = 42;
+ SettingProto accelerometer_rotation = 43;
+ SettingProto user_rotation = 44;
+ SettingProto hide_rotation_lock_toggle_for_accessibility = 45;
+ SettingProto vibrate_when_ringing = 46;
+ SettingProto dtmf_tone_when_dialing = 47;
+ SettingProto dtmf_tone_type_when_dialing = 48;
+ SettingProto hearing_aid = 49;
+ SettingProto tty_mode = 50;
+ SettingProto sound_effects_enabled = 51;
+ SettingProto haptic_feedback_enabled = 52;
+ SettingProto notification_light_pulse = 53;
+ SettingProto pointer_location = 54;
+ SettingProto show_touches = 55;
+ SettingProto window_orientation_listener_log = 56;
+ SettingProto lockscreen_sounds_enabled = 57;
+ SettingProto lockscreen_disabled = 58;
+ SettingProto sip_receive_calls = 59;
+ SettingProto sip_call_options = 60;
+ SettingProto sip_always = 61;
+ SettingProto sip_address_only = 62;
+ SettingProto pointer_speed = 63;
+ SettingProto lock_to_app_enabled = 64;
+ SettingProto egg_mode = 65;
+ SettingProto when_to_make_wifi_calls = 66;
+}
+
+message SettingProto {
+ // ID of the setting
+ string id = 1;
+
+ // Name of the setting
+ string name = 2;
+
+ // Package name of the setting
+ string pkg = 3;
+
+ // Value of this setting
+ string value = 4;
+
+ // Default value of this setting
+ string default_value = 5;
+
+ // Whether the default is set by the system
+ bool default_from_system = 6;
+}
+
+message SettingsOperationProto {
+ // When the operation happened
+ int64 timestamp = 1;
+
+ // Type of the operation
+ string operation = 2;
+
+ // Name of the setting that was affected (optional)
+ string setting = 3;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2692bf2..6d48862 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1963,7 +1963,7 @@
{@link android.content.pm.PackageManager#addPackageToPreferred}
for details. -->
<permission android:name="android.permission.SET_PREFERRED_APPLICATIONS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|verifier" />
<!-- Allows an application to receive the
{@link android.content.Intent#ACTION_BOOT_COMPLETED} that is
diff --git a/core/res/res/anim/app_starting_exit.xml b/core/res/res/anim/app_starting_exit.xml
index aaf7f15..dfa42e2 100644
--- a/core/res/res/anim/app_starting_exit.xml
+++ b/core/res/res/anim/app_starting_exit.xml
@@ -21,8 +21,8 @@
<alpha
xmlns:android="http://schemas.android.com/apk/res/android"
android:detachWallpaper="true"
- android:interpolator="@interpolator/decelerate_quad"
+ android:interpolator="@interpolator/linear"
android:fromAlpha="1.0"
android:toAlpha="0.0"
- android:duration="160" />
+ android:duration="150" />
diff --git a/core/res/res/values-mcc214-mnc01/config.xml b/core/res/res/values-mcc214-mnc01/config.xml
index 895b770..24150a7 100644
--- a/core/res/res/values-mcc214-mnc01/config.xml
+++ b/core/res/res/values-mcc214-mnc01/config.xml
@@ -40,27 +40,4 @@
<item>INTERNET,airtelnet.es,,,vodafone,vodafone,,,,,214,01,1,DUN</item>
</string-array>
- <string-array translatable="false" name="config_operatorConsideredNonRoaming">
- <item>21402</item>
- <item>21403</item>
- <item>21404</item>
- <item>21405</item>
- <item>21406</item>
- <item>21407</item>
- <item>21408</item>
- <item>21409</item>
- <item>21410</item>
- <item>21411</item>
- <item>21412</item>
- <item>21413</item>
- <item>21414</item>
- <item>21415</item>
- <item>21416</item>
- <item>21417</item>
- <item>21418</item>
- <item>21419</item>
- <item>21420</item>
- <item>21421</item>
- </string-array>
-
</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a401314..0789241 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.
@@ -4707,7 +4703,8 @@
screens with limited space for text. -->
<enum name="full" value="2" />
</attr>
- <!-- Specify the type of auto-size. -->
+ <!-- Specify the type of auto-size. Note that this feature is not supported by EditText,
+ works only for TextView -->
<attr name="autoSizeText" format="enum">
<!-- No auto-sizing (default). -->
<enum name="none" value="0" />
@@ -7918,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">
@@ -8476,4 +8472,14 @@
<attr name="font" format="reference" />
<attr name="fontWeight" format="integer" />
</declare-styleable>
+
+ <!-- @hide -->
+ <declare-styleable name="RecyclerView">
+ <attr name="layoutManager" format="string" />
+ <attr name="orientation" />
+ <attr name="descendantFocusability" />
+ <attr name="spanCount" format="integer"/>
+ <attr name="reverseLayout" format="boolean" />
+ <attr name="stackFromEnd" format="boolean" />
+ </declare-styleable>
</resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 5235116..dfa672d 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1240,6 +1240,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 +1827,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 +1919,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 +2044,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 7de48d3..c36279c 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/dimens.xml b/core/res/res/values/dimens.xml
index bd19521..e6358a3 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -509,4 +509,10 @@
<dimen name="tooltip_precise_anchor_threshold">96dp</dimen>
<!-- Extra tooltip offset used when anchoring to the mouse/touch position -->
<dimen name="tooltip_precise_anchor_extra_offset">8dp</dimen>
+
+ <!-- The max amount of scroll ItemTouchHelper will trigger if dragged view is out of
+ RecyclerView's bounds.-->
+ <dimen name="item_touch_helper_max_drag_scroll_per_frame">20dp</dimen>
+ <dimen name="item_touch_helper_swipe_escape_velocity">120dp</dimen>
+ <dimen name="item_touch_helper_swipe_escape_max_velocity">800dp</dimen>
</resources>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 5547706..613616f 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -97,6 +97,7 @@
<item type="id" name="redo" />
<item type="id" name="replaceText" />
<item type="id" name="shareText" />
+ <item type="id" name="textAssist" />
<item type="id" name="selection_start_handle" />
<item type="id" name="selection_end_handle" />
<item type="id" name="insertion_handle" />
@@ -131,4 +132,7 @@
<item type="id" name="cross_task_transition" />
<item type="id" name="accessibilityActionClickOnClickableSpan" />
+
+ <!-- ItemTouchHelper uses this id to save a View's original elevation. -->
+ <item type="id" name="item_touch_helper_previous_elevation"/>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 060c59e..66dd1274 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 d09b190..eece9fc 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 c370ef7..16356c7 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" />
@@ -2806,4 +2809,10 @@
<java-symbol type="string" name="disable_accessibility_shortcut" />
<java-symbol type="string" name="leave_accessibility_shortcut_on" />
<java-symbol type="string" name="config_defaultAccessibilityService" />
+
+ <!-- com.android.internal.widget.RecyclerView -->
+ <java-symbol type="id" name="item_touch_helper_previous_elevation"/>
+ <java-symbol type="dimen" name="item_touch_helper_max_drag_scroll_per_frame"/>
+ <java-symbol type="dimen" name="item_touch_helper_swipe_escape_velocity"/>
+ <java-symbol type="dimen" name="item_touch_helper_swipe_escape_max_velocity"/>
</resources>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index cba485a..91ce7a4 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1168,6 +1168,7 @@
<activity android:name="android.app.EmptyActivity">
</activity>
<activity android:name="com.android.internal.app.ChooserWrapperActivity"/>
+ <activity android:name="com.android.internal.app.ResolverWrapperActivity"/>
<receiver android:name="android.app.activity.AbortReceiver">
<intent-filter android:priority="1">
diff --git a/core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java b/core/tests/coretests/src/android/metrics/LogMakerTest.java
similarity index 80%
rename from core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java
rename to core/tests/coretests/src/android/metrics/LogMakerTest.java
index a340559..0f75cb6 100644
--- a/core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java
+++ b/core/tests/coretests/src/android/metrics/LogMakerTest.java
@@ -13,15 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.internal.logging;
+package android.metrics;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import junit.framework.TestCase;
-public class LogBuilderTest extends TestCase {
+public class LogMakerTest extends TestCase {
public void testSerialize() {
- LogBuilder builder = new LogBuilder(0);
+ LogMaker builder = new LogMaker(0);
builder.addTaggedData(1, "one");
builder.addTaggedData(2, "two");
Object[] out = builder.serialize();
@@ -41,7 +41,7 @@
int bucket = 13;
int value = 14;
- LogBuilder builder = new LogBuilder(category);
+ LogMaker builder = new LogMaker(category);
builder.setType(type);
builder.setSubtype(subtype);
builder.setTimestamp(timestamp);
@@ -53,7 +53,7 @@
builder.addTaggedData(2, "two");
Object[] out = builder.serialize();
- LogBuilder parsed = new LogBuilder(out);
+ LogMaker parsed = new LogMaker(out);
assertEquals(category, parsed.getCategory());
assertEquals(type, parsed.getType());
@@ -68,7 +68,7 @@
}
public void testIntBucket() {
- LogBuilder builder = new LogBuilder(0);
+ LogMaker builder = new LogMaker(0);
builder.setCounterBucket(100);
assertEquals(100, builder.getCounterBucket());
assertEquals(false, builder.isLongCounterBucket());
@@ -76,14 +76,14 @@
public void testLongBucket() {
long longBucket = Long.MAX_VALUE;
- LogBuilder builder = new LogBuilder(0);
+ LogMaker builder = new LogMaker(0);
builder.setCounterBucket(longBucket);
assertEquals(longBucket, builder.getCounterBucket());
assertEquals(true, builder.isLongCounterBucket());
}
public void testInvalidInputThrows() {
- LogBuilder builder = new LogBuilder(0);
+ LogMaker builder = new LogMaker(0);
boolean threw = false;
try {
builder.addTaggedData(0, new Object());
@@ -95,7 +95,7 @@
}
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 @@
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/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index aab4698..daebf88 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -222,7 +222,7 @@
private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
- infoList.add(ChooserDataProvider.createResolvedComponentInfo(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
}
return infoList;
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 41016e1..c446f3c 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -24,11 +24,10 @@
import static org.mockito.Mockito.mock;
-
-/**
- * Simple wrapper around chooser activity to be able to initiate it under test
- */
public class ChooserWrapperActivity extends ChooserActivity {
+ /*
+ * Simple wrapper around chooser activity to be able to initiate it under test
+ */
static final OverrideData sOverrides = new OverrideData();
private UsageStatsManager mUsm;
@@ -94,4 +93,4 @@
resolverListController = mock(ResolverListController.class);
}
}
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
new file mode 100644
index 0000000..84b844a
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import com.android.internal.R;
+import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManager;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static android.os.SystemClock.sleep;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Resolver activity instrumentation tests
+ */
+@RunWith(AndroidJUnit4.class)
+public class ResolverActivityTest {
+ @Rule
+ public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
+ new ActivityTestRule<>(ResolverWrapperActivity.class, false,
+ false);
+
+ @Before
+ public void cleanOverrideData() {
+ sOverrides.reset();
+ }
+
+ @Test
+ public void twoOptionsAndUserSelectsOne() throws InterruptedException {
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ assertThat(activity.getAdapter().getCount(), is(2));
+
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+ onView(withText(toChoose.activityInfo.name))
+ .perform(click());
+ onView(withId(R.id.button_once))
+ .perform(click());
+ waitForIdle();
+ assertThat(chosen[0], is(toChoose));
+ }
+
+ @Test
+ public void hasLastChosenActivity() throws Exception {
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ when(sOverrides.resolverListController.getLastChosen())
+ .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ // The other entry is filtered to the last used slot
+ assertThat(activity.getAdapter().getCount(), is(1));
+
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+ onView(withId(R.id.title)).perform(click());
+ onView(withId(R.id.button_once))
+ .perform(click());
+ waitForIdle();
+ assertThat(chosen[0], is(toChoose));
+ }
+
+ private Intent createSendImageIntent() {
+ Intent sendIntent = new Intent();
+ sendIntent.setAction(Intent.ACTION_SEND);
+ sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
+ sendIntent.setType("image/jpeg");
+ return sendIntent;
+ }
+
+ private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < numberOfResults; i++) {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ }
+ return infoList;
+ }
+
+ private void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserDataProvider.java b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
similarity index 94%
rename from core/tests/coretests/src/com/android/internal/app/ChooserDataProvider.java
rename to core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
index f6f63f1..ae06306 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserDataProvider.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
@@ -22,16 +22,15 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.os.UserHandle;
-import android.service.chooser.ChooserTarget;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
/**
- * Utility class used by chooser tests to create mock data
+ * Utility class used by resolver tests to create mock data
*/
-class ChooserDataProvider {
+class ResolverDataProvider {
static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfo(int i) {
return new ResolverActivity.ResolvedComponentInfo(createComponentName(i),
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
new file mode 100644
index 0000000..163211e
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.usage.UsageStatsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+
+import java.util.function.Function;
+
+import static org.mockito.Mockito.mock;
+
+
+/*
+ * Simple wrapper around chooser activity to be able to initiate it under test
+ */
+public class ResolverWrapperActivity extends ResolverActivity {
+ static final OverrideData sOverrides = new OverrideData();
+ private UsageStatsManager mUsm;
+
+ ResolveListAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ @Override
+ public boolean isVoiceInteraction() {
+ if (sOverrides.isVoiceInteraction != null) {
+ return sOverrides.isVoiceInteraction;
+ }
+ return super.isVoiceInteraction();
+ }
+
+ @Override
+ public void safelyStartActivity(TargetInfo cti) {
+ if (sOverrides.onSafelyStartCallback != null &&
+ sOverrides.onSafelyStartCallback.apply(cti)) {
+ return;
+ }
+ super.safelyStartActivity(cti);
+ }
+
+ @Override
+ protected ResolverListController createListController() {
+ return sOverrides.resolverListController;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ if (sOverrides.createPackageManager != null) {
+ return sOverrides.createPackageManager.apply(super.getPackageManager());
+ }
+ return super.getPackageManager();
+ }
+
+ /**
+ * We cannot directly mock the activity created since instrumentation creates it.
+ * <p>
+ * Instead, we use static instances of this object to modify behavior.
+ */
+ static class OverrideData {
+ @SuppressWarnings("Since15")
+ public Function<PackageManager, PackageManager> createPackageManager;
+ public Function<TargetInfo, Boolean> onSafelyStartCallback;
+ public ResolverListController resolverListController;
+ public Boolean isVoiceInteraction;
+
+ public void reset() {
+ onSafelyStartCallback = null;
+ isVoiceInteraction = null;
+ createPackageManager = null;
+ resolverListController = mock(ResolverListController.class);
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index 34c34d7..dc75417 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -33,7 +33,8 @@
import java.util.List;
public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTestCase {
- private static final String DUMMY_PACKAGE_NAME = "dymmy package name";
+ private static final String DUMMY_PACKAGE_NAME = "dummy package name";
+ private static final String DUMMY_IME_LABEL = "dummy ime label";
private static final String DUMMY_SETTING_ACTIVITY_NAME = "";
private static final boolean DUMMY_IS_AUX_IME = false;
private static final boolean DUMMY_FORCE_DEFAULT = false;
@@ -88,6 +89,35 @@
}
}
+ private static ImeSubtypeListItem createDummyItem(String imeName,
+ String subtypeName, String subtypeLocale, int subtypeIndex, String systemLocale) {
+ final ResolveInfo ri = new ResolveInfo();
+ final ServiceInfo si = new ServiceInfo();
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = DUMMY_PACKAGE_NAME;
+ ai.enabled = true;
+ si.applicationInfo = ai;
+ si.enabled = true;
+ si.packageName = DUMMY_PACKAGE_NAME;
+ si.name = imeName;
+ si.exported = true;
+ si.nonLocalizedLabel = DUMMY_IME_LABEL;
+ ri.serviceInfo = si;
+ ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+ subtypes.add(new InputMethodSubtypeBuilder()
+ .setSubtypeNameResId(0)
+ .setSubtypeIconResId(0)
+ .setSubtypeLocale(subtypeLocale)
+ .setIsAsciiCapable(true)
+ .build());
+ final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME,
+ DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID,
+ DUMMY_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */,
+ false /* supportsDismissingWindow */);
+ return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale,
+ systemLocale);
+ }
+
private static List<ImeSubtypeListItem> createEnabledImeSubtypes() {
final List<ImeSubtypeListItem> items = new ArrayList<>();
addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"),
@@ -329,4 +359,56 @@
assertFalse(item_e.mIsSystemLocale);
assertFalse(item_EN_US.mIsSystemLocale);
}
+
+ @SmallTest
+ public void testImeSubtypeListComparator() throws Exception {
+ {
+ final List<ImeSubtypeListItem> items = Arrays.asList(
+ createDummyItem("X", "A", "en_US", 0, "en_US"),
+ createDummyItem("X", "A", "en", 1, "en_US"),
+ createDummyItem("X", "A", "ja", 2, "en_US"),
+ createDummyItem("X", "Z", "en_US", 3, "en_US"),
+ createDummyItem("X", "Z", "en", 4, "en_US"),
+ createDummyItem("X", "Z", "ja", 5, "en_US"),
+ createDummyItem("X", "", "en_US", 6, "en_US"),
+ createDummyItem("X", "", "en", 7, "en_US"),
+ createDummyItem("X", "", "ja", 8, "en_US"),
+ createDummyItem("Y", "A", "en_US", 9, "en_US"),
+ createDummyItem("Y", "A", "en", 10, "en_US"),
+ createDummyItem("Y", "A", "ja", 11, "en_US"),
+ createDummyItem("Y", "Z", "en_US", 12, "en_US"),
+ createDummyItem("Y", "Z", "en", 13, "en_US"),
+ createDummyItem("Y", "Z", "ja", 14, "en_US"),
+ createDummyItem("Y", "", "en_US", 15, "en_US"),
+ createDummyItem("Y", "", "en", 16, "en_US"),
+ createDummyItem("Y", "", "ja", 17, "en_US"),
+ createDummyItem("", "A", "en_US", 18, "en_US"),
+ createDummyItem("", "A", "en", 19, "en_US"),
+ createDummyItem("", "A", "ja", 20, "en_US"),
+ createDummyItem("", "Z", "en_US", 21, "en_US"),
+ createDummyItem("", "Z", "en", 22, "en_US"),
+ createDummyItem("", "Z", "ja", 23, "en_US"),
+ createDummyItem("", "", "en_US", 24, "en_US"),
+ createDummyItem("", "", "en", 25, "en_US"),
+ createDummyItem("", "", "ja", 26, "en_US"));
+
+ for (int i = 0; i < items.size(); ++i) {
+ assertEquals(0, items.get(i).compareTo(items.get(i)));
+ for (int j = i + 1; j < items.size(); ++j) {
+ assertTrue(items.get(i).compareTo(items.get(j)) < 0);
+ assertTrue(items.get(j).compareTo(items.get(i)) > 0);
+ }
+ }
+ }
+
+ {
+ // Following two items have the same priority.
+ final ImeSubtypeListItem nonSystemLocale1 =
+ createDummyItem("X", "A", "ja_JP", 0, "en_us");
+ final ImeSubtypeListItem nonSystemLocale2 =
+ createDummyItem("X", "A", "hi_IN", 1, "en_us");
+ assertEquals(0, nonSystemLocale1.compareTo(nonSystemLocale2));
+ assertEquals(0, nonSystemLocale2.compareTo(nonSystemLocale1));
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/LockscreenGestureParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/LockscreenGestureParserTest.java
index 0bff850..c023b57 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/LockscreenGestureParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/LockscreenGestureParserTest.java
@@ -19,7 +19,7 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class LockscreenGestureParserTest extends ParserTest {
@@ -79,7 +79,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(view, proto.getCategory());
assertEquals(MetricsEvent.TYPE_ACTION, proto.getType());
@@ -95,6 +95,6 @@
mParser.parseEvent(mLogger, t, objects);
- verify(mLogger, times(1)).addEvent((LogBuilder) anyObject());
+ verify(mLogger, times(1)).addEvent((LogMaker) anyObject());
}
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java
index 2119c25..f05205d 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java
@@ -20,7 +20,7 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class NotificationActionClickedParserTest extends ParserTest {
@@ -49,12 +49,12 @@
validateGoodData(t, mTag, index, objects);
}
- private LogBuilder validateGoodData(int t, String tag, int index, Object[] objects) {
+ private LogMaker validateGoodData(int t, String tag, int index, Object[] objects) {
mParser.parseEvent(mLogger, t, objects);
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_ITEM_ACTION, proto.getCategory());
assertEquals(mKeyPackage, proto.getPackageName());
@@ -69,7 +69,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testWrongType() throws Throwable {
@@ -79,7 +79,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testBadKey() throws Throwable {
@@ -89,7 +89,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testMncTimestamps() throws Throwable {
@@ -102,7 +102,7 @@
objects[3] = mSinceUpdateMillis;
objects[4] = mSinceVisibleMillis;
- LogBuilder proto = validateGoodData(t, "", index, objects);
+ LogMaker proto = validateGoodData(t, "", index, objects);
validateNotificationTimes(proto, mSinceCreationMillis, mSinceUpdateMillis,
mSinceVisibleMillis);
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java
index 1e117ee..7771e84 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java
@@ -21,12 +21,9 @@
import static org.mockito.Mockito.when;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import java.util.Collections;
-import java.util.List;
-
import org.mockito.ArgumentCaptor;
public class NotificationAlertParserTest extends ParserTest {
@@ -126,7 +123,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(mTime, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_ALERT, proto.getCategory());
assertEquals(mKeyPackage, proto.getPackageName());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java
index de16919..77b2ed6 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java
@@ -21,11 +21,9 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import java.util.List;
-
public class NotificationCanceledParserTest extends ParserTest {
public NotificationCanceledParserTest() {
@@ -57,7 +55,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory());
assertEquals(mKeyPackage, proto.getPackageName());
@@ -108,7 +106,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
validateNotificationTimes(proto, life, freshness, exposure);
}
@@ -121,7 +119,7 @@
mParser.parseEvent(mLogger, 0, objects);
if (intentional) {
- verify(mLogger, times(1)).addEvent((LogBuilder) anyObject());
+ verify(mLogger, times(1)).addEvent((LogMaker) anyObject());
}
}
@@ -164,7 +162,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testWrongType() throws Throwable {
@@ -174,7 +172,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testBadKey() throws Throwable {
@@ -184,7 +182,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testIgnoreUnexpectedData() throws Throwable {
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java
index 2f61dd7..cc65132 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java
@@ -21,7 +21,7 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class NotificationClickedParserTest extends ParserTest {
@@ -46,12 +46,12 @@
validateGoodData(t, mTag, objects);
}
- private LogBuilder validateGoodData(int t, String tag, Object[] objects) {
+ private LogMaker validateGoodData(int t, String tag, Object[] objects) {
mParser.parseEvent(mLogger, t, objects);
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory());
assertEquals(mKeyPackage, proto.getPackageName());
@@ -66,7 +66,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testWrongType() throws Throwable {
@@ -75,7 +75,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testBadKey() throws Throwable {
@@ -84,7 +84,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testMncTimestamps() throws Throwable {
@@ -95,7 +95,7 @@
objects[2] = mSinceUpdateMillis;
objects[3] = mSinceVisibleMillis;
- LogBuilder proto = validateGoodData(t, "", objects);
+ LogMaker proto = validateGoodData(t, "", objects);
validateNotificationTimes(proto, mSinceCreationMillis, mSinceUpdateMillis,
mSinceVisibleMillis);
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java
index 86b4a45..f337f91 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java
@@ -21,7 +21,7 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class NotificationExpansionParserTest extends ParserTest {
@@ -54,12 +54,12 @@
validateGoodData(t, mTag, objects);
}
- private LogBuilder validateGoodData(int t, String tag, Object[] objects) {
+ private LogMaker validateGoodData(int t, String tag, Object[] objects) {
mParser.parseEvent(mLogger, t, objects);
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory());
assertEquals(mKeyPackage, proto.getPackageName());
@@ -79,7 +79,7 @@
mParser.parseEvent(mLogger, t, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testCollapsedByUser() throws Throwable {
@@ -93,7 +93,7 @@
mParser.parseEvent(mLogger, t, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testAutoCollapsed() throws Throwable {
@@ -107,7 +107,7 @@
mParser.parseEvent(mLogger, t, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testMissingData() throws Throwable {
@@ -115,7 +115,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testWrongType() throws Throwable {
@@ -126,7 +126,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testBadKey() throws Throwable {
@@ -137,7 +137,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
}
public void testMncTimestamps() throws Throwable {
@@ -152,7 +152,7 @@
objects[4] = mSinceUpdateMillis;
objects[5] = mSinceVisibleMillis;
- LogBuilder proto = validateGoodData(t, "", objects);
+ LogMaker proto = validateGoodData(t, "", objects);
validateNotificationTimes(proto, mSinceCreationMillis, mSinceUpdateMillis,
mSinceVisibleMillis);
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java
index 3efc48f..ce6f1f4 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java
@@ -19,7 +19,7 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class NotificationPanelHiddenParserTest extends ParserTest {
@@ -48,7 +48,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_PANEL, proto.getCategory());
assertEquals(MetricsEvent.TYPE_CLOSE, proto.getType());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java
index 34dda98..9e15812 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java
@@ -20,7 +20,7 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class NotificationPanelRevealedParserTest extends ParserTest {
@@ -37,7 +37,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_PANEL, proto.getCategory());
assertEquals(MetricsEvent.TYPE_OPEN, proto.getType());
@@ -57,7 +57,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_PANEL, proto.getCategory());
assertEquals(MetricsEvent.TYPE_OPEN, proto.getType());
@@ -69,7 +69,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, times(1)).addEvent((LogBuilder) anyObject());
+ verify(mLogger, times(1)).addEvent((LogMaker) anyObject());
}
public void testIgnoreUnexpectedData() throws Throwable {
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java
index cc5421c..7fef929 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java
@@ -16,17 +16,13 @@
package com.android.internal.logging.legacy;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import org.mockito.ArgumentCaptor;
-
public class NotificationVisibilityParserTest extends ParserTest {
private final int mCreationTime = 23124;
private final int mUpdateTime = 3412;
@@ -84,7 +80,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(mTime, proto.getTimestamp());
assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory());
assertEquals(mKeyPackage, proto.getPackageName());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/ParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/ParserTest.java
index 2e5c6d2..4adf629 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/ParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/ParserTest.java
@@ -15,7 +15,7 @@
*/
package com.android.internal.logging.legacy;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import static org.mockito.Matchers.any;
@@ -39,8 +39,8 @@
protected TagParser mParser;
- protected LogBuilder[] mProto;
- protected ArgumentCaptor<LogBuilder> mProtoCaptor;
+ protected LogMaker[] mProto;
+ protected ArgumentCaptor<LogMaker> mProtoCaptor;
protected ArgumentCaptor<String> mNameCaptor;
protected ArgumentCaptor<Integer> mCountCaptor;
protected String mKey = "0|com.android.example.notificationshowcase|31338|null|10090";
@@ -54,9 +54,9 @@
public ParserTest() {
- mProto = new LogBuilder[5];
+ mProto = new LogMaker[5];
for (int i = 0; i < mProto.length; i++) {
- mProto[i] = new LogBuilder(MetricsEvent.VIEW_UNKNOWN);
+ mProto[i] = new LogMaker(MetricsEvent.VIEW_UNKNOWN);
}
}
@@ -66,19 +66,19 @@
MockitoAnnotations.initMocks(this);
- mProtoCaptor = ArgumentCaptor.forClass(LogBuilder.class);
+ mProtoCaptor = ArgumentCaptor.forClass(LogMaker.class);
mNameCaptor = ArgumentCaptor.forClass(String.class);
mCountCaptor = ArgumentCaptor.forClass(Integer.class);
- OngoingStubbing<LogBuilder> stub = when(mLogger.obtain()).thenReturn(mProto[0]);
+ OngoingStubbing<LogMaker> stub = when(mLogger.obtain()).thenReturn(mProto[0]);
for (int i = 1; i < mProto.length; i++) {
stub.thenReturn(mProto[i]);
}
- doNothing().when(mLogger).addEvent(any(LogBuilder.class));
+ doNothing().when(mLogger).addEvent(any(LogMaker.class));
doNothing().when(mLogger).incrementBy(anyString(), anyInt());
}
- protected void validateNotificationTimes(LogBuilder proto, int life, int freshness,
+ protected void validateNotificationTimes(LogMaker proto, int life, int freshness,
int exposure) {
validateNotificationTimes(proto, life, freshness);
if (exposure != 0) {
@@ -89,7 +89,7 @@
}
}
- protected void validateNotificationTimes(LogBuilder proto, int life, int freshness) {
+ protected void validateNotificationTimes(LogMaker proto, int life, int freshness) {
if (life != 0) {
assertEquals(life,
proto.getTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS));
@@ -104,7 +104,7 @@
}
}
- protected void validateNotificationIdAndTag(LogBuilder proto, int id, String tag) {
+ protected void validateNotificationIdAndTag(LogMaker proto, int id, String tag) {
assertEquals(tag, proto.getTaggedData(MetricsEvent.NOTIFICATION_TAG));
assertEquals(id, proto.getTaggedData(MetricsEvent.NOTIFICATION_ID));
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/PowerScreenStateParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/PowerScreenStateParserTest.java
index be918cd..b480e61 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/PowerScreenStateParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/PowerScreenStateParserTest.java
@@ -19,7 +19,7 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class PowerScreenStateParserTest extends ParserTest {
@@ -60,7 +60,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(type, proto.getType());
assertEquals(MetricsEvent.SCREEN, proto.getCategory());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/StatusBarStateParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/StatusBarStateParserTest.java
index 906ec04..def9628 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/StatusBarStateParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/StatusBarStateParserTest.java
@@ -19,7 +19,7 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class StatusBarStateParserTest extends ParserTest {
@@ -64,7 +64,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(view, proto.getCategory());
assertEquals(type, proto.getType());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java
index 111909f..2ad76c1 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java
@@ -23,7 +23,7 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class SysuiActionParserTest extends ParserTest {
@@ -47,7 +47,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(view, proto.getCategory());
assertEquals(MetricsEvent.TYPE_ACTION, proto.getType());
@@ -66,7 +66,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(view, proto.getCategory());
assertEquals(packageName, proto.getPackageName());
@@ -117,7 +117,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(view, proto.getCategory());
assertEquals(expectedValue, proto.getSubtype());
@@ -130,7 +130,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
}
@@ -141,7 +141,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
}
@@ -151,7 +151,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiMultiActionParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiMultiActionParserTest.java
index 7853f77..e7a05d8 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiMultiActionParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiMultiActionParserTest.java
@@ -15,16 +15,11 @@
*/
package com.android.internal.logging.legacy;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import android.metrics.LogMaker;
public class SysuiMultiActionParserTest extends ParserTest {
@@ -41,7 +36,7 @@
String counterName = "sheep";
int bucket = 13;
int value = 14;
- LogBuilder builder = new LogBuilder(category);
+ LogMaker builder = new LogMaker(category);
builder.setType(type);
builder.setSubtype(subtype);
builder.setPackageName(packageName);
@@ -57,7 +52,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(category, proto.getCategory());
assertEquals(type, proto.getType());
assertEquals(subtype, proto.getSubtype());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java
index 1291508..64d69a4 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java
@@ -23,7 +23,7 @@
import static org.mockito.Mockito.verify;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class SysuiViewVisibilityParserTest extends ParserTest {
@@ -47,7 +47,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(t, proto.getTimestamp());
assertEquals(view, proto.getCategory());
assertEquals(MetricsEvent.TYPE_OPEN, proto.getType());
@@ -64,7 +64,7 @@
verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
- LogBuilder proto = mProtoCaptor.getValue();
+ LogMaker proto = mProtoCaptor.getValue();
assertEquals(MetricsEvent.TYPE_CLOSE, proto.getType());
}
@@ -73,7 +73,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
}
@@ -84,7 +84,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
}
@@ -95,7 +95,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
}
@@ -105,7 +105,7 @@
mParser.parseEvent(mLogger, 0, objects);
- verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+ verify(mLogger, never()).addEvent((LogMaker) anyObject());
verify(mLogger, never()).incrementBy(anyString(), anyInt());
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index f1be4a9..c5961ab 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -303,6 +303,7 @@
<permission name="android.permission.BIND_APPWIDGET"/>
<permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
+ <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
<permission name="android.permission.CONNECTIVITY_INTERNAL"/>
<permission name="android.permission.CONTROL_VPN"/>
<permission name="android.permission.DUMP"/>
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index cc5cc7b..8572345 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -21,11 +21,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
-import android.text.GraphicsOperations;
-import android.text.SpannableString;
-import android.text.SpannedString;
-import android.text.TextUtils;
+import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
import libcore.util.NativeAllocationRegistry;
@@ -501,8 +498,10 @@
* an error to call restore() more times than save() was called.
*/
public void restore() {
- boolean throwOnUnderflow = !sCompatibilityRestore || !isHardwareAccelerated();
- nRestore(mNativeCanvasWrapper, throwOnUnderflow);
+ if (!nRestore(mNativeCanvasWrapper)
+ && (!sCompatibilityRestore || !isHardwareAccelerated())) {
+ throw new IllegalStateException("Underflow in restore - more restores than saves");
+ }
}
/**
@@ -527,8 +526,16 @@
* @param saveCount The save level to restore to.
*/
public void restoreToCount(int saveCount) {
- boolean throwOnUnderflow = !sCompatibilityRestore || !isHardwareAccelerated();
- nRestoreToCount(mNativeCanvasWrapper, saveCount, throwOnUnderflow);
+ if (saveCount < 1) {
+ if (!sCompatibilityRestore || !isHardwareAccelerated()) {
+ // do nothing and throw without restoring
+ throw new IllegalArgumentException(
+ "Underflow in restoreToCount - more restores than saves");
+ }
+ // compat behavior - restore as far as possible
+ saveCount = 1;
+ }
+ nRestoreToCount(mNativeCanvasWrapper, saveCount);
}
/**
@@ -643,7 +650,7 @@
*/
@Deprecated
public void getMatrix(@NonNull Matrix ctm) {
- nGetCTM(mNativeCanvasWrapper, ctm.native_instance);
+ nGetMatrix(mNativeCanvasWrapper, ctm.native_instance);
}
/**
@@ -1059,79 +1066,66 @@
// ---------------- @FastNative -------------------
@FastNative
- private static native void nSetBitmap(long canvasHandle,
- Bitmap bitmap);
+ private static native void nSetBitmap(long canvasHandle, Bitmap bitmap);
+
@FastNative
+ private static native boolean nGetClipBounds(long nativeCanvas, Rect bounds);
+
+ // ---------------- @CriticalNative -------------------
+
+ @CriticalNative
private static native boolean nIsOpaque(long canvasHandle);
- @FastNative
+ @CriticalNative
private static native void nSetHighContrastText(long renderer, boolean highContrastText);
- @FastNative
+ @CriticalNative
private static native int nGetWidth(long canvasHandle);
- @FastNative
+ @CriticalNative
private static native int nGetHeight(long canvasHandle);
- @FastNative
+ @CriticalNative
private static native int nSave(long canvasHandle, int saveFlags);
- @FastNative
- private static native int nSaveLayer(long nativeCanvas, float l,
- float t, float r, float b,
- long nativePaint,
- int layerFlags);
- @FastNative
- private static native int nSaveLayerAlpha(long nativeCanvas, float l,
- float t, float r, float b,
- int alpha, int layerFlags);
- @FastNative
- private static native void nRestore(long canvasHandle, boolean tolerateUnderflow);
- @FastNative
- private static native void nRestoreToCount(long canvasHandle,
- int saveCount,
- boolean tolerateUnderflow);
- @FastNative
+ @CriticalNative
+ private static native int nSaveLayer(long nativeCanvas, float l, float t, float r, float b,
+ long nativePaint, int layerFlags);
+ @CriticalNative
+ private static native int nSaveLayerAlpha(long nativeCanvas, float l, float t, float r, float b,
+ int alpha, int layerFlags);
+ @CriticalNative
+ private static native boolean nRestore(long canvasHandle);
+ @CriticalNative
+ private static native void nRestoreToCount(long canvasHandle, int saveCount);
+ @CriticalNative
private static native int nGetSaveCount(long canvasHandle);
- @FastNative
- private static native void nTranslate(long canvasHandle,
- float dx, float dy);
- @FastNative
- private static native void nScale(long canvasHandle,
- float sx, float sy);
- @FastNative
+ @CriticalNative
+ private static native void nTranslate(long canvasHandle, float dx, float dy);
+ @CriticalNative
+ private static native void nScale(long canvasHandle, float sx, float sy);
+ @CriticalNative
private static native void nRotate(long canvasHandle, float degrees);
- @FastNative
- private static native void nSkew(long canvasHandle,
- float sx, float sy);
- @FastNative
- private static native void nConcat(long nativeCanvas,
- long nativeMatrix);
- @FastNative
- private static native void nSetMatrix(long nativeCanvas,
- long nativeMatrix);
- @FastNative
+ @CriticalNative
+ private static native void nSkew(long canvasHandle, float sx, float sy);
+ @CriticalNative
+ private static native void nConcat(long nativeCanvas, long nativeMatrix);
+ @CriticalNative
+ private static native void nSetMatrix(long nativeCanvas, long nativeMatrix);
+ @CriticalNative
private static native boolean nClipRect(long nativeCanvas,
- float left, float top,
- float right, float bottom,
- int regionOp);
- @FastNative
- private static native boolean nClipPath(long nativeCanvas,
- long nativePath,
- int regionOp);
- @FastNative
- private static native void nSetDrawFilter(long nativeCanvas,
- long nativeFilter);
- @FastNative
- private static native boolean nGetClipBounds(long nativeCanvas,
- Rect bounds);
- @FastNative
- private static native void nGetCTM(long nativeCanvas,
- long nativeMatrix);
- @FastNative
- private static native boolean nQuickReject(long nativeCanvas,
- long nativePath);
- @FastNative
- private static native boolean nQuickReject(long nativeCanvas,
- float left, float top,
- float right, float bottom);
+ float left, float top, float right, float bottom, int regionOp);
+ @CriticalNative
+ private static native boolean nClipPath(long nativeCanvas, long nativePath, int regionOp);
+ @CriticalNative
+ private static native void nSetDrawFilter(long nativeCanvas, long nativeFilter);
+ @CriticalNative
+ private static native void nGetMatrix(long nativeCanvas, long nativeMatrix);
+ @CriticalNative
+ private static native boolean nQuickReject(long nativeCanvas, long nativePath);
+ @CriticalNative
+ private static native boolean nQuickReject(long nativeCanvas, float left, float top,
+ float right, float bottom);
+
+
+ // ---------------- Draw Methods -------------------
/**
* <p>
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index e48bf79..8673e0b 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.content.res.AssetManager;
+import android.text.FontConfig;
import android.util.Log;
import java.io.FileInputStream;
@@ -80,21 +81,22 @@
}
}
- public boolean addFontWeightStyle(ByteBuffer font, int ttcIndex, List<FontListParser.Axis> axes,
+ public boolean addFontWeightStyle(ByteBuffer font, int ttcIndex, List<FontConfig.Axis> axes,
int weight, boolean style) {
return nAddFontWeightStyle(mNativePtr, font, ttcIndex, axes, weight, style);
}
- public boolean addFontFromAsset(AssetManager mgr, String path) {
- return nAddFontFromAsset(mNativePtr, mgr, path);
+ public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
+ boolean isAsset) {
+ return nAddFontFromAssetManager(mNativePtr, mgr, path, cookie, isAsset);
}
private static native long nCreateFamily(String lang, int variant);
private static native void nUnrefFamily(long nativePtr);
private static native boolean nAddFont(long nativeFamily, ByteBuffer font, int ttcIndex);
private static native boolean nAddFontWeightStyle(long nativeFamily, ByteBuffer font,
- int ttcIndex, List<FontListParser.Axis> listOfAxis,
+ int ttcIndex, List<FontConfig.Axis> listOfAxis,
int weight, boolean isItalic);
- private static native boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr,
- String path);
+ private static native boolean nAddFontFromAssetManager(long nativeFamily, AssetManager mgr,
+ String path, int cookie, boolean isAsset);
}
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 9490436..4ec564a 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.text.FontConfig;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
@@ -36,61 +37,8 @@
*/
public class FontListParser {
- public static class Config {
- Config() {
- families = new ArrayList<Family>();
- aliases = new ArrayList<Alias>();
- }
- public List<Family> families;
- public List<Alias> aliases;
- }
-
- public static class Axis {
- Axis(int tag, float styleValue) {
- this.tag = tag;
- this.styleValue = styleValue;
- }
- public final int tag;
- public final float styleValue;
- }
-
- public static class Font {
- Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic) {
- this.fontName = fontName;
- this.ttcIndex = ttcIndex;
- this.axes = axes;
- this.weight = weight;
- this.isItalic = isItalic;
- }
- public String fontName;
- public int ttcIndex;
- public final List<Axis> axes;
- public int weight;
- public boolean isItalic;
- }
-
- public static class Alias {
- public String name;
- public String toName;
- public int weight;
- }
-
- public static class Family {
- public Family(String name, List<Font> fonts, String lang, String variant) {
- this.name = name;
- this.fonts = fonts;
- this.lang = lang;
- this.variant = variant;
- }
-
- public String name;
- public List<Font> fonts;
- public String lang;
- public String variant;
- }
-
/* Parse fallback list (no names) */
- public static Config parse(InputStream in) throws XmlPullParserException, IOException {
+ public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException {
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, null);
@@ -104,9 +52,9 @@
// Note that a well-formed variation contains a four-character tag and a float as styleValue,
// with spacers in between. The tag is enclosd either by double quotes or single quotes.
@VisibleForTesting
- public static Axis[] parseFontVariationSettings(String settings) {
+ public static FontConfig.Axis[] parseFontVariationSettings(String settings) {
String[] settingList = settings.split(",");
- ArrayList<Axis> axisList = new ArrayList<>();
+ ArrayList<FontConfig.Axis> axisList = new ArrayList<>();
settingLoop:
for (String setting : settingList) {
int pos = 0;
@@ -148,9 +96,9 @@
}
int tag = makeTag(tagString.charAt(0), tagString.charAt(1), tagString.charAt(2),
tagString.charAt(3));
- axisList.add(new Axis(tag, styleValue));
+ axisList.add(new FontConfig.Axis(tag, styleValue));
}
- return axisList.toArray(new Axis[axisList.size()]);
+ return axisList.toArray(new FontConfig.Axis[axisList.size()]);
}
@VisibleForTesting
@@ -162,17 +110,17 @@
return c == ' ' || c == '\r' || c == '\t' || c == '\n';
}
- private static Config readFamilies(XmlPullParser parser)
+ private static FontConfig readFamilies(XmlPullParser parser)
throws XmlPullParserException, IOException {
- Config config = new Config();
+ FontConfig config = new FontConfig();
parser.require(XmlPullParser.START_TAG, null, "familyset");
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
if (tag.equals("family")) {
- config.families.add(readFamily(parser));
+ config.getFamilies().add(readFamily(parser));
} else if (tag.equals("alias")) {
- config.aliases.add(readAlias(parser));
+ config.getAliases().add(readAlias(parser));
} else {
skip(parser);
}
@@ -180,12 +128,12 @@
return config;
}
- private static Family readFamily(XmlPullParser parser)
+ private static FontConfig.Family readFamily(XmlPullParser parser)
throws XmlPullParserException, IOException {
String name = parser.getAttributeValue(null, "name");
String lang = parser.getAttributeValue(null, "lang");
String variant = parser.getAttributeValue(null, "variant");
- List<Font> fonts = new ArrayList<Font>();
+ List<FontConfig.Font> fonts = new ArrayList<FontConfig.Font>();
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
@@ -195,18 +143,18 @@
skip(parser);
}
}
- return new Family(name, fonts, lang, variant);
+ return new FontConfig.Family(name, fonts, lang, variant);
}
/** Matches leading and trailing XML whitespace. */
private static final Pattern FILENAME_WHITESPACE_PATTERN =
Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$");
- private static Font readFont(XmlPullParser parser)
+ private static FontConfig.Font readFont(XmlPullParser parser)
throws XmlPullParserException, IOException {
String indexStr = parser.getAttributeValue(null, "index");
int index = indexStr == null ? 0 : Integer.parseInt(indexStr);
- List<Axis> axes = new ArrayList<Axis>();
+ List<FontConfig.Axis> axes = new ArrayList<FontConfig.Axis>();
String weightStr = parser.getAttributeValue(null, "weight");
int weight = weightStr == null ? 400 : Integer.parseInt(weightStr);
boolean isItalic = "italic".equals(parser.getAttributeValue(null, "style"));
@@ -225,7 +173,7 @@
}
String fullFilename = "/system/fonts/" +
FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
- return new Font(fullFilename, index, axes, weight, isItalic);
+ return new FontConfig.Font(fullFilename, index, axes, weight, isItalic);
}
/** The 'tag' attribute value is read as four character values between U+0020 and U+007E
@@ -239,7 +187,7 @@
private static final Pattern STYLE_VALUE_PATTERN =
Pattern.compile("-?(([0-9]+(\\.[0-9]+)?)|(\\.[0-9]+))");
- private static Axis readAxis(XmlPullParser parser)
+ private static FontConfig.Axis readAxis(XmlPullParser parser)
throws XmlPullParserException, IOException {
int tag = 0;
String tagStr = parser.getAttributeValue(null, "tag");
@@ -258,22 +206,22 @@
}
skip(parser); // axis tag is empty, ignore any contents and consume end tag
- return new Axis(tag, styleValue);
+ return new FontConfig.Axis(tag, styleValue);
}
- private static Alias readAlias(XmlPullParser parser)
+ private static FontConfig.Alias readAlias(XmlPullParser parser)
throws XmlPullParserException, IOException {
- Alias alias = new Alias();
- alias.name = parser.getAttributeValue(null, "name");
- alias.toName = parser.getAttributeValue(null, "to");
+ String name = parser.getAttributeValue(null, "name");
+ String toName = parser.getAttributeValue(null, "to");
String weightStr = parser.getAttributeValue(null, "weight");
+ int weight;
if (weightStr == null) {
- alias.weight = 400;
+ weight = 400;
} else {
- alias.weight = Integer.parseInt(weightStr);
+ weight = Integer.parseInt(weightStr);
}
skip(parser); // alias tag is empty, ignore any contents and consume end tag
- return alias;
+ return new FontConfig.Alias(name, toName, weight);
}
private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 2886f0d..4e863e3 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -16,18 +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;
@@ -62,7 +78,11 @@
static Typeface[] sDefaults;
private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache =
- new LongSparseArray<SparseArray<Typeface>>(3);
+ new LongSparseArray<>(3);
+ @GuardedBy("sLock")
+ private static FontsContract sFontsContract;
+ @GuardedBy("sLock")
+ private static Handler mHandler;
/**
* Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
@@ -72,6 +92,7 @@
static Typeface sDefaultTypeface;
static Map<String, Typeface> sSystemFontMap;
static FontFamily[] sFallbackFonts;
+ private static final Object sLock = new Object();
static final String FONTS_CONFIG = "fonts.xml";
@@ -109,6 +130,162 @@
}
/**
+ * @hide
+ * Used by Resources.
+ */
+ @NonNull
+ public static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
+ if (sFallbackFonts != null) {
+ synchronized (sDynamicTypefaceCache) {
+ final String key = createAssetUid(mgr, path);
+ Typeface typeface = sDynamicTypefaceCache.get(key);
+ if (typeface != null) return typeface;
+
+ FontFamily fontFamily = new FontFamily();
+ if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */)) {
+ FontFamily[] families = {fontFamily};
+ typeface = createFromFamiliesWithDefault(families);
+ sDynamicTypefaceCache.put(key, typeface);
+ return typeface;
+ }
+ }
+ }
+ throw new RuntimeException("Font resource not found " + path);
+ }
+
+ /**
+ * Create a typeface object given a font request. The font will be asynchronously fetched,
+ * therefore the result is delivered to the given callback. See {@link FontRequest}.
+ * Only one of the methods in callback will be invoked, depending on whether the request
+ * succeeds or fails. These calls will happen on the main thread.
+ * @param request A {@link FontRequest} object that identifies the provider and query for the
+ * request. May not be null.
+ * @param callback A callback that will be triggered when results are obtained. May not be null.
+ */
+ public static void create(@NonNull FontRequest request, @NonNull FontRequestCallback callback) {
+ synchronized (sLock) {
+ if (sFontsContract == null) {
+ sFontsContract = new FontsContract();
+ mHandler = new Handler();
+ }
+ final ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ public void onReceiveResult(int resultCode, Bundle resultData) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ receiveResult(request, callback, resultCode, resultData);
+ }
+ });
+ }
+ };
+ sFontsContract.getFont(request, receiver);
+ }
+ }
+
+ private static void receiveResult(FontRequest request, FontRequestCallback callback,
+ int resultCode, Bundle resultData) {
+ if (resultCode == FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND) {
+ callback.onTypefaceRequestFailed(
+ FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND);
+ return;
+ }
+ if (resultCode == FontsContract.RESULT_CODE_FONT_NOT_FOUND
+ || resultData == null) {
+ callback.onTypefaceRequestFailed(
+ FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
+ return;
+ }
+ List<FontResult> resultList =
+ resultData.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS);
+ if (resultList == null || resultList.isEmpty()) {
+ callback.onTypefaceRequestFailed(
+ FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
+ return;
+ }
+ FontFamily fontFamily = new FontFamily();
+ for (int i = 0; i < resultList.size(); ++i) {
+ FontResult result = resultList.get(i);
+ ParcelFileDescriptor fd = result.getFileDescriptor();
+ if (fd == null) {
+ callback.onTypefaceRequestFailed(
+ FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
+ return;
+ }
+ try (FileInputStream is = new FileInputStream(fd.getFileDescriptor())) {
+ FileChannel fileChannel = is.getChannel();
+ long fontSize = fileChannel.size();
+ ByteBuffer fontBuffer = fileChannel.map(
+ FileChannel.MapMode.READ_ONLY, 0, fontSize);
+ int style = result.getStyle();
+ int weight = (style & BOLD) != 0 ? 700 : 400;
+ // TODO: this method should be
+ // create(fd, ttcIndex, fontVariationSettings, style).
+ if (!fontFamily.addFontWeightStyle(fontBuffer, result.getTtcIndex(),
+ null, weight, (style & ITALIC) != 0)) {
+ Log.e(TAG, "Error creating font " + request.getQuery());
+ callback.onTypefaceRequestFailed(
+ FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
+ return;
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error reading font " + request.getQuery(), e);
+ callback.onTypefaceRequestFailed(
+ FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
+ return;
+ } finally {
+ IoUtils.closeQuietly(fd);
+ }
+ }
+ 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
@@ -195,7 +372,7 @@
if (typeface != null) return typeface;
FontFamily fontFamily = new FontFamily();
- if (fontFamily.addFontFromAsset(mgr, path)) {
+ if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */)) {
FontFamily[] families = { fontFamily };
typeface = createFromFamiliesWithDefault(families);
sDynamicTypefaceCache.put(key, typeface);
@@ -294,25 +471,25 @@
mStyle = nativeGetStyle(ni);
}
- private static FontFamily makeFamilyFromParsed(FontListParser.Family family,
+ private static FontFamily makeFamilyFromParsed(FontConfig.Family family,
Map<String, ByteBuffer> bufferForPath) {
- FontFamily fontFamily = new FontFamily(family.lang, family.variant);
- for (FontListParser.Font font : family.fonts) {
- ByteBuffer fontBuffer = bufferForPath.get(font.fontName);
+ FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
+ for (FontConfig.Font font : family.getFonts()) {
+ ByteBuffer fontBuffer = bufferForPath.get(font.getFontName());
if (fontBuffer == null) {
- try (FileInputStream file = new FileInputStream(font.fontName)) {
+ try (FileInputStream file = new FileInputStream(font.getFontName())) {
FileChannel fileChannel = file.getChannel();
long fontSize = fileChannel.size();
fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
- bufferForPath.put(font.fontName, fontBuffer);
+ bufferForPath.put(font.getFontName(), fontBuffer);
} catch (IOException e) {
- Log.e(TAG, "Error mapping font file " + font.fontName);
+ Log.e(TAG, "Error mapping font file " + font.getFontName());
continue;
}
}
- if (!fontFamily.addFontWeightStyle(fontBuffer, font.ttcIndex, font.axes,
- font.weight, font.isItalic)) {
- Log.e(TAG, "Error creating font " + font.fontName + "#" + font.ttcIndex);
+ if (!fontFamily.addFontWeightStyle(fontBuffer, font.getTtcIndex(), font.getAxes(),
+ font.getWeight(), font.isItalic())) {
+ Log.e(TAG, "Error creating font " + font.getFontName() + "#" + font.getTtcIndex());
}
}
return fontFamily;
@@ -329,16 +506,16 @@
File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG);
try {
FileInputStream fontsIn = new FileInputStream(configFilename);
- FontListParser.Config fontConfig = FontListParser.parse(fontsIn);
+ FontConfig fontConfig = FontListParser.parse(fontsIn);
Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>();
List<FontFamily> familyList = new ArrayList<FontFamily>();
// Note that the default typeface is always present in the fallback list;
// this is an enhancement from pre-Minikin behavior.
- for (int i = 0; i < fontConfig.families.size(); i++) {
- FontListParser.Family f = fontConfig.families.get(i);
- if (i == 0 || f.name == null) {
+ for (int i = 0; i < fontConfig.getFamilies().size(); i++) {
+ FontConfig.Family f = fontConfig.getFamilies().get(i);
+ if (i == 0 || f.getName() == null) {
familyList.add(makeFamilyFromParsed(f, bufferForPath));
}
}
@@ -346,10 +523,10 @@
setDefault(Typeface.createFromFamilies(sFallbackFonts));
Map<String, Typeface> systemFonts = new HashMap<String, Typeface>();
- for (int i = 0; i < fontConfig.families.size(); i++) {
+ for (int i = 0; i < fontConfig.getFamilies().size(); i++) {
Typeface typeface;
- FontListParser.Family f = fontConfig.families.get(i);
- if (f.name != null) {
+ FontConfig.Family f = fontConfig.getFamilies().get(i);
+ if (f.getName() != null) {
if (i == 0) {
// The first entry is the default typeface; no sense in
// duplicating the corresponding FontFamily.
@@ -359,17 +536,17 @@
FontFamily[] families = { fontFamily };
typeface = Typeface.createFromFamiliesWithDefault(families);
}
- systemFonts.put(f.name, typeface);
+ systemFonts.put(f.getName(), typeface);
}
}
- for (FontListParser.Alias alias : fontConfig.aliases) {
- Typeface base = systemFonts.get(alias.toName);
+ for (FontConfig.Alias alias : fontConfig.getAliases()) {
+ Typeface base = systemFonts.get(alias.getToName());
Typeface newFace = base;
- int weight = alias.weight;
+ int weight = alias.getWeight();
if (weight != 400) {
newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
}
- systemFonts.put(alias.name, newFace);
+ systemFonts.put(alias.getName(), newFace);
}
sSystemFontMap = systemFonts;
diff --git a/graphics/java/android/graphics/fonts/FontRequest.java b/graphics/java/android/graphics/fonts/FontRequest.java
new file mode 100644
index 0000000..e50df6f
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/FontRequest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Information about a font request that may be sent to a Font Provider.
+ */
+public final class FontRequest implements Parcelable {
+ private final String mProviderAuthority;
+ private final String mQuery;
+
+ /**
+ * @param providerAuthority The authority of the Font Provider to be used for the request.
+ * @param query The query to be sent over to the provider. Refer to your font provider's
+ * documentation on the format of this string.
+ */
+ public FontRequest(@NonNull String providerAuthority, @NonNull String query) {
+ mProviderAuthority = Preconditions.checkNotNull(providerAuthority);
+ mQuery = Preconditions.checkNotNull(query);
+ }
+
+ /**
+ * Returns the selected font provider's authority. This tells the system what font provider
+ * it should request the font from.
+ */
+ public String getProviderAuthority() {
+ return mProviderAuthority;
+ }
+
+ /**
+ * Returns the query string. Refer to your font provider's documentation on the format of this
+ * string.
+ */
+ public String getQuery() {
+ return mQuery;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mProviderAuthority);
+ dest.writeString(mQuery);
+ }
+
+ private FontRequest(Parcel in) {
+ mProviderAuthority = in.readString();
+ mQuery = in.readString();
+ }
+
+ public static final Parcelable.Creator<FontRequest> CREATOR =
+ new Parcelable.Creator<FontRequest>() {
+ @Override
+ public FontRequest createFromParcel(Parcel in) {
+ return new FontRequest(in);
+ }
+
+ @Override
+ public FontRequest[] newArray(int size) {
+ return new FontRequest[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "FontRequest {"
+ + "mProviderAuthority: " + mProviderAuthority
+ + ", mQuery: " + mQuery
+ + "}";
+ }
+}
diff --git a/graphics/java/android/graphics/fonts/FontResult.java b/graphics/java/android/graphics/fonts/FontResult.java
new file mode 100644
index 0000000..3ef99fd
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/FontResult.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Paint;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * Results returned from a Font Provider to the system.
+ * @hide
+ */
+public final class FontResult implements Parcelable {
+ private final ParcelFileDescriptor mFileDescriptor;
+ private final int mTtcIndex;
+ private final String mFontVariationSettings;
+ private final int mStyle;
+
+ /**
+ * Creates a FontResult with all the information needed about a provided font.
+ * @param fileDescriptor A ParcelFileDescriptor pointing to the font file. This shoult point to
+ * a real file or shared memory, as the client will mmap the given file
+ * descriptor. Pipes, sockets and other non-mmap-able file descriptors
+ * will fail to load in the client application.
+ * @param ttcIndex If providing a TTC_INDEX file, the index to point to. Otherwise, 0.
+ * @param fontVariationSettings If providing a variation font, the settings for it. May be null.
+ * @param style One of {@link android.graphics.Typeface#NORMAL},
+ * {@link android.graphics.Typeface#BOLD}, {@link android.graphics.Typeface#ITALIC}
+ * or {@link android.graphics.Typeface#BOLD_ITALIC}
+ */
+ public FontResult(@NonNull ParcelFileDescriptor fileDescriptor, int ttcIndex,
+ @Nullable String fontVariationSettings, int style) {
+ mFileDescriptor = Preconditions.checkNotNull(fileDescriptor);
+ mTtcIndex = ttcIndex;
+ mFontVariationSettings = fontVariationSettings;
+ mStyle = style;
+ }
+
+ public ParcelFileDescriptor getFileDescriptor() {
+ return mFileDescriptor;
+ }
+
+ public int getTtcIndex() {
+ return mTtcIndex;
+ }
+
+ public String getFontVariationSettings() {
+ return mFontVariationSettings;
+ }
+
+ public int getStyle() {
+ return mStyle;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mFileDescriptor, flags);
+ dest.writeInt(mTtcIndex);
+ dest.writeString(mFontVariationSettings);
+ dest.writeInt(mStyle);
+ }
+
+ private FontResult(Parcel in) {
+ mFileDescriptor = in.readParcelable(null);
+ mTtcIndex = in.readInt();
+ mFontVariationSettings = in.readString();
+ mStyle = in.readInt();
+ }
+
+ public static final Parcelable.Creator<FontResult> CREATOR =
+ new Parcelable.Creator<FontResult>() {
+ @Override
+ public FontResult createFromParcel(Parcel in) {
+ return new FontResult(in);
+ }
+
+ @Override
+ public FontResult[] newArray(int size) {
+ return new FontResult[size];
+ }
+ };
+}
diff --git a/graphics/java/android/graphics/fonts/FontSpec.aidl b/graphics/java/android/graphics/fonts/FontSpec.aidl
new file mode 100644
index 0000000..dddea25
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/FontSpec.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.graphics.fonts;
+
+parcelable FontSpec;
\ No newline at end of file
diff --git a/graphics/tests/graphicstests/src/android/graphics/VariationParserTest.java b/graphics/tests/graphicstests/src/android/graphics/VariationParserTest.java
index d046c11..2b4e6c2 100644
--- a/graphics/tests/graphicstests/src/android/graphics/VariationParserTest.java
+++ b/graphics/tests/graphicstests/src/android/graphics/VariationParserTest.java
@@ -17,50 +17,53 @@
package android.graphics;
import android.test.suitebuilder.annotation.SmallTest;
+import android.text.FontConfig;
import junit.framework.TestCase;
+import java.util.List;
+
public class VariationParserTest extends TestCase {
@SmallTest
public void testParseFontVariationSetting() {
int tag = FontListParser.makeTag('w', 'd', 't', 'h');
- FontListParser.Axis[] axis = FontListParser.parseFontVariationSettings("'wdth' 1");
- assertEquals(tag, axis[0].tag);
- assertEquals(1.0f, axis[0].styleValue);
+ FontConfig.Axis[] axis = FontListParser.parseFontVariationSettings("'wdth' 1");
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
axis = FontListParser.parseFontVariationSettings("\"wdth\" 100");
- assertEquals(tag, axis[0].tag);
- assertEquals(100.0f, axis[0].styleValue);
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(100.0f, axis[0].getStyleValue());
axis = FontListParser.parseFontVariationSettings(" 'wdth' 100");
- assertEquals(tag, axis[0].tag);
- assertEquals(100.0f, axis[0].styleValue);
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(100.0f, axis[0].getStyleValue());
axis = FontListParser.parseFontVariationSettings("\t'wdth' 0.5");
- assertEquals(tag, axis[0].tag);
- assertEquals(0.5f, axis[0].styleValue);
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(0.5f, axis[0].getStyleValue());
tag = FontListParser.makeTag('A', 'X', ' ', ' ');
axis = FontListParser.parseFontVariationSettings("'AX ' 1");
- assertEquals(tag, axis[0].tag);
- assertEquals(1.0f, axis[0].styleValue);
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
axis = FontListParser.parseFontVariationSettings("'AX '\t1");
- assertEquals(tag, axis[0].tag);
- assertEquals(1.0f, axis[0].styleValue);
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
axis = FontListParser.parseFontVariationSettings("'AX '\n1");
- assertEquals(tag, axis[0].tag);
- assertEquals(1.0f, axis[0].styleValue);
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
axis = FontListParser.parseFontVariationSettings("'AX '\r1");
- assertEquals(tag, axis[0].tag);
- assertEquals(1.0f, axis[0].styleValue);
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
axis = FontListParser.parseFontVariationSettings("'AX '\r\t\n 1");
- assertEquals(tag, axis[0].tag);
- assertEquals(1.0f, axis[0].styleValue);
+ assertEquals(tag, axis[0].getTag());
+ assertEquals(1.0f, axis[0].getStyleValue());
// Test for invalid input
axis = FontListParser.parseFontVariationSettings("");
@@ -87,26 +90,26 @@
@SmallTest
public void testParseFontVariationStyleSettings() {
- FontListParser.Axis[] axis =
+ FontConfig.Axis[] axis =
FontListParser.parseFontVariationSettings("'wdth' 10,'AX '\r1");
int tag1 = FontListParser.makeTag('w', 'd', 't', 'h');
int tag2 = FontListParser.makeTag('A', 'X', ' ', ' ');
- assertEquals(tag1, axis[0].tag);
- assertEquals(10.0f, axis[0].styleValue);
- assertEquals(tag2, axis[1].tag);
- assertEquals(1.0f, axis[1].styleValue);
+ assertEquals(tag1, axis[0].getTag());
+ assertEquals(10.0f, axis[0].getStyleValue());
+ assertEquals(tag2, axis[1].getTag());
+ assertEquals(1.0f, axis[1].getStyleValue());
// Test only spacers are allowed before tag
axis = FontListParser.parseFontVariationSettings(" 'wdth' 10,ab'wdth' 1");
tag1 = FontListParser.makeTag('w', 'd', 't', 'h');
- assertEquals(tag1, axis[0].tag);
- assertEquals(10.0f, axis[0].styleValue);
+ assertEquals(tag1, axis[0].getTag());
+ assertEquals(10.0f, axis[0].getStyleValue());
assertEquals(1, axis.length);
}
@SmallTest
public void testInvalidTagCharacters() {
- FontListParser.Axis[] axis =
+ FontConfig.Axis[] axis =
FontListParser.parseFontVariationSettings("'\u0000\u0000\u0000\u0000' 10");
assertEquals(0, axis.length);
axis = FontListParser.parseFontVariationSettings("'\u3042\u3044\u3046\u3048' 10");
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index a53a55a..1d8b021 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -180,16 +180,18 @@
}
}
- if (!backdrop.isEmpty()) {
- // content node translation to catch up with backdrop
- float dx = contentDrawBounds.left - backdrop.left;
- float dy = contentDrawBounds.top - backdrop.top;
+ if (!nodes[1]->nothingToDraw()) {
+ if (!backdrop.isEmpty()) {
+ // content node translation to catch up with backdrop
+ float dx = contentDrawBounds.left - backdrop.left;
+ float dy = contentDrawBounds.top - backdrop.top;
- Rect contentLocalClip = backdrop;
- contentLocalClip.translate(dx, dy);
- deferRenderNode(-dx, -dy, contentLocalClip, *nodes[1]);
- } else {
- deferRenderNode(*nodes[1]);
+ Rect contentLocalClip = backdrop;
+ contentLocalClip.translate(dx, dy);
+ deferRenderNode(-dx, -dy, contentLocalClip, *nodes[1]);
+ } else {
+ deferRenderNode(*nodes[1]);
+ }
}
// remaining overlay nodes, simply defer
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 275ce16..79daa3f 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -193,10 +193,9 @@
}
SkRect TestUtils::getClipBounds(const SkCanvas* canvas) {
- SkClipStack::BoundsType boundType;
- SkRect clipBounds;
- canvas->getClipStack()->getBounds(&clipBounds, &boundType);
- return clipBounds;
+ SkIRect bounds;
+ (void)canvas->getClipDeviceBounds(&bounds);
+ return SkRect::Make(bounds);
}
SkRect TestUtils::getLocalClipBounds(const SkCanvas* canvas) {
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index 6f3ed9c..5a2791c 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -311,13 +311,32 @@
TestUtils::syncHierarchyPropertiesAndDisplayList(node);
}
- FrameBuilder frameBuilder(SkRect::MakeWH(800, 600), 800, 600,
- sLightGeometry, Caches::getInstance());
- frameBuilder.deferRenderNodeScene(nodes, contentDrawBounds);
+ {
+ FrameBuilder frameBuilder(SkRect::MakeWH(800, 600), 800, 600,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNodeScene(nodes, contentDrawBounds);
- DeferRenderNodeSceneTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(4, renderer.getIndex());
+ DeferRenderNodeSceneTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(4, renderer.getIndex());
+ }
+
+ for (auto& node : nodes) {
+ EXPECT_FALSE(node->nothingToDraw());
+ node->setStagingDisplayList(nullptr, nullptr);
+ node->destroyHardwareResources(nullptr);
+ EXPECT_TRUE(node->nothingToDraw());
+ }
+
+ {
+ // Validate no crashes if any nodes are missing DisplayLists
+ FrameBuilder frameBuilder(SkRect::MakeWH(800, 600), 800, 600,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNodeScene(nodes, contentDrawBounds);
+
+ FailRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ }
}
RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, empty_noFbo0) {
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index f5ff058..2f1eae3 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -865,10 +865,8 @@
void onDrawPaint(const SkPaint&) {
switch (mDrawCounter++) {
case 0:
- // While this mirrors FrameBuilder::colorOp_unbounded, this value is different
- // because there is no root (root is clipped in SkiaPipeline::renderFrame).
- // SkiaPipeline.clipped and clip_replace verify the root clip.
- EXPECT_TRUE(TestUtils::getClipBounds(this).isEmpty());
+ EXPECT_EQ(SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT),
+ TestUtils::getClipBounds(this));
break;
case 1:
EXPECT_EQ(SkRect::MakeWH(10, 10), TestUtils::getClipBounds(this));
diff --git a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
index 92d9d3d..95c6ed6 100644
--- a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
+++ b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp
@@ -87,14 +87,7 @@
testProperty([](RenderProperties& properties) {
properties.mutableRevealClip().set(true, 50, 50, 25);
}, [](const SkCanvas& canvas) {
- SkClipStack::Iter it(*canvas.getClipStack(), SkClipStack::Iter::kBottom_IterStart);
- const SkClipStack::Element *top = it.next();
- ASSERT_NE(nullptr, top);
- SkPath clip;
- top->asPath(&clip);
- SkRect rect;
- EXPECT_TRUE(clip.isOval(&rect));
- EXPECT_EQ(SkRect::MakeLTRB(25, 25, 75, 75), rect);
+ EXPECT_EQ(SkRect::MakeLTRB(25, 25, 75, 75), TestUtils::getClipBounds(&canvas));
});
}
@@ -103,14 +96,7 @@
properties.mutableOutline().setShouldClip(true);
properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f);
}, [](const SkCanvas& canvas) {
- SkClipStack::Iter it(*canvas.getClipStack(), SkClipStack::Iter::kBottom_IterStart);
- const SkClipStack::Element *top = it.next();
- ASSERT_NE(nullptr, top);
- SkPath clip;
- top->asPath(&clip);
- SkRRect rrect;
- EXPECT_TRUE(clip.isRRect(&rrect));
- EXPECT_EQ(SkRRect::MakeRectXY(SkRect::MakeLTRB(10, 20, 30, 40), 5.0f, 5.0f), rrect);
+ EXPECT_EQ(SkRect::MakeLTRB(10, 20, 30, 40), TestUtils::getClipBounds(&canvas));
});
}
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index ce23176..aac9727 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -660,8 +660,14 @@
/**
* Gets the carrier frequency of the tracked signal.
*
- * <p>For example it can be the GPS L1 = 1.57542e9 Hz, or L2, L5, varying GLO channels, etc. If
- * the field is not set, it is the primary common use frequency, e.g. L1 for GPS.
+ * <p>For example it can be the GPS central frequency for L1 = 1575.45 MHz, or L2 = 1227.60 MHz,
+ * L5 = 1176.45 MHz, varying GLO channels, etc. If the field is not set, it is the primary
+ * common use central frequency, e.g. L1 = 1575.45 MHz for GPS.
+ *
+ * For an L1, L5 receiver tracking a satellite on L1 and L5 at the same time, two raw
+ * measurement objects will be reported for this same satellite, in one of the measurement
+ * objects, all the values related to L1 will be filled, and in the other all of the values
+ * related to L5 will be filled.
*
* <p>The value is only available if {@link #hasCarrierFrequencyHz()} is {@code true}.
*/
@@ -882,7 +888,7 @@
}
/**
- * Returns {@code true} if {@link #getAutomaticGainControlLevelInDb()} is available,
+ * Returns {@code true} if {@link #getAutomaticGainControlLevelInDb()} is available,
* {@code false} otherwise.
*/
public boolean hasAutomaticGainControlLevelInDb() {
@@ -892,11 +898,12 @@
/**
* Gets the Automatic Gain Control level in dB.
*
- * <p> AGC acts as a variable gain amplifier adjusting the power of the incoming signal to
- * minimize the quantization losses. The AGC level may be used to indicate potential
- * interference. When AGC is at a nominal level, this value must be set as 0. Higher gain
- * (and/or lower input power) shall be output as a positive number. Hence in cases of strong
- * jamming, in the band of this signal, this value will go more negative.
+ * <p> AGC acts as a variable gain amplifier adjusting the power of the incoming signal. The AGC
+ * level may be used to indicate potential interference. When AGC is at a nominal level, this
+ * value must be set as 0. Higher gain (and/or lower input power) shall be output as a positive
+ * number. Hence in cases of strong jamming, in the band of this signal, this value will go more
+ * negative.
+ *
* <p>Note: Different hardware designs (e.g. antenna, pre-amplification, or other RF HW
* components) may also affect the typical output of of this value on any given hardware design
* in an open sky test - the important aspect of this output is that changes in this value are
diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java
index 78dbc71..e90a174 100644
--- a/location/java/android/location/GnssStatus.java
+++ b/location/java/android/location/GnssStatus.java
@@ -231,8 +231,13 @@
/**
* Gets the carrier frequency of the signal tracked.
*
- * For example it can be the GPS L1 = 1.57542e9 Hz, or L2, L5, varying GLO channels, etc. If
- * the field is not set, it is the primary common use frequency, e.g. L1 for GPS.
+ * <p>For example it can be the GPS central frequency for L1 = 1575.45 MHz, or L2 = 1227.60 MHz,
+ * L5 = 1176.45 MHz, varying GLO channels, etc. If the field is not set, it is the primary
+ * common use central frequency, e.g. L1 = 1575.45 MHz for GPS.
+ *
+ * For an L1, L5 receiver tracking a satellite on L1 and L5 at the same time, two measurements
+ * will be reported for this same satellite, in one all the values related to L1 will be filled,
+ * and in the other all of the values related to L5 will be filled.
*
* <p>The value is only available if {@link #hasCarrierFrequency(int satIndex)} is {@code true}.
*/
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 432e77c..a76a328 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -192,5 +192,7 @@
oneway void releasePlayer(in int piid);
+ void disableRingtoneSync();
+
// WARNING: read warning at top of file, it is recommended to add new methods at the end
}
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 7614999..8a1027b 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -33,8 +33,11 @@
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.net.Uri;
import android.os.Environment;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.MediaStore;
@@ -850,6 +853,18 @@
public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) {
final ContentResolver resolver = context.getContentResolver();
+ if (Settings.Secure.getString(resolver, Settings.Secure.SYNC_PARENT_SOUNDS).equals("1")) {
+ // Sync is enabled, so we need to disable it
+ IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+ IAudioService audioService = IAudioService.Stub.asInterface(b);
+ try {
+ audioService.disableRingtoneSync();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to disable ringtone sync.");
+ return;
+ }
+ }
+
String setting = getSettingForType(type);
if (setting == null) return;
if(!isInternalRingtoneUri(ringtoneUri)) {
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 20706fd..3ee80af 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -543,6 +543,17 @@
*/
public static final String TYPE_S_DMB = "TYPE_S_DMB";
+ /**
+ * The channel type for preview videos.
+ *
+ * <P>Unlike other broadcast TV channel types, the programs in the preview channel usually
+ * are promotional videos. The UI may treat the preview channels differently from the other
+ * broadcast channels.
+ *
+ * @see #COLUMN_TYPE
+ */
+ public static final String TYPE_PREVIEW = "TYPE_PREVIEW";
+
/** A generic service type. */
public static final String SERVICE_TYPE_OTHER = "SERVICE_TYPE_OTHER";
@@ -1001,6 +1012,20 @@
*/
public static final String COLUMN_VERSION_NUMBER = "version_number";
+ /**
+ * The flag indicating whether this TV channel is transient or not.
+ *
+ * <p>A value of 1 indicates that the channel will be automatically removed by the system on
+ * reboot, and a value of 0 indicates that the channel is persistent across reboot. If not
+ * specified, this value is set to 0 (not transient) by default.
+ *
+ * <p>Type: INTEGER (boolean)
+ * @see Programs#COLUMN_TRANSIENT
+ * @hide
+ */
+ @SystemApi
+ public static final String COLUMN_TRANSIENT = "transient";
+
private Channels() {}
/**
@@ -1165,6 +1190,8 @@
* previous program in the same channel. In practice, start time will usually be the end
* time of the previous program.
*
+ * <p>Can be empty if this program belongs to a {@link Channels#TYPE_PREVIEW} channel.
+ *
* <p>Type: INTEGER (long)
*/
public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
@@ -1176,6 +1203,8 @@
* next program in the same channel. In practice, end time will usually be the start time of
* the next program.
*
+ * <p>Can be empty if this program belongs to a {@link Channels#TYPE_PREVIEW} channel.
+ *
* <p>Type: INTEGER (long)
*/
public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
@@ -1410,6 +1439,102 @@
*/
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 dfddaa5..1eae8db 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -317,6 +317,13 @@
*/
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/native/android/Android.mk b/native/android/Android.mk
index da4e4ba..355f52e 100644
--- a/native/android/Android.mk
+++ b/native/android/Android.mk
@@ -9,6 +9,7 @@
asset_manager.cpp \
choreographer.cpp \
configuration.cpp \
+ hardware_buffer.cpp \
input.cpp \
looper.cpp \
native_activity.cpp \
diff --git a/native/android/hardware_buffer.cpp b/native/android/hardware_buffer.cpp
new file mode 100644
index 0000000..6a10cb5
--- /dev/null
+++ b/native/android/hardware_buffer.cpp
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AHardwareBuffer"
+
+#include <android/hardware_buffer_jni.h>
+
+#include <errno.h>
+#include <sys/socket.h>
+
+#include <memory>
+
+#include <android_runtime/android_hardware_HardwareBuffer.h>
+#include <binder/Binder.h>
+#include <binder/Parcel.h>
+#include <cutils/native_handle.h>
+#include <binder/IServiceManager.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/IGraphicBufferAlloc.h>
+#include <ui/GraphicBuffer.h>
+#include <utils/Flattenable.h>
+#include <utils/Log.h>
+
+static constexpr int kDataBufferSize = 64 * sizeof(int); // 64 ints
+
+using namespace android;
+
+static inline const GraphicBuffer* AHardwareBuffer_to_GraphicBuffer(
+ const AHardwareBuffer* buffer) {
+ return reinterpret_cast<const GraphicBuffer*>(buffer);
+}
+
+static inline GraphicBuffer* AHardwareBuffer_to_GraphicBuffer(
+ AHardwareBuffer* buffer) {
+ return reinterpret_cast<GraphicBuffer*>(buffer);
+}
+
+static inline AHardwareBuffer* GraphicBuffer_to_AHardwareBuffer(
+ GraphicBuffer* buffer) {
+ return reinterpret_cast<AHardwareBuffer*>(buffer);
+}
+
+// ----------------------------------------------------------------------------
+// Public functions
+// ----------------------------------------------------------------------------
+
+int AHardwareBuffer_allocate(const AHardwareBuffer_Desc* desc,
+ AHardwareBuffer** outBuffer) {
+ if (!outBuffer || !desc) return BAD_VALUE;
+
+ // The holder is used to destroy the buffer if an error occurs.
+ sp<IServiceManager> sm = defaultServiceManager();
+ if (sm == nullptr) {
+ ALOGE("Unable to connect to ServiceManager");
+ return PERMISSION_DENIED;
+ }
+
+ // Get the SurfaceFlingerService.
+ sp<ISurfaceComposer> composer = interface_cast<ISurfaceComposer>(
+ sm->getService(String16("SurfaceFlinger")));
+ if (composer == nullptr) {
+ ALOGE("Unable to connect to surface composer");
+ return PERMISSION_DENIED;
+ }
+ // Get an IGraphicBufferAlloc to create the buffer.
+ sp<IGraphicBufferAlloc> allocator = composer->createGraphicBufferAlloc();
+ if (allocator == nullptr) {
+ ALOGE("Unable to obtain a buffer allocator");
+ return PERMISSION_DENIED;
+ }
+
+ int format = android_hardware_HardwareBuffer_convertToPixelFormat(
+ desc->format);
+ if (format == 0) {
+ ALOGE("Invalid pixel format");
+ return BAD_VALUE;
+ }
+
+ status_t err;
+ uint32_t usage = android_hardware_HardwareBuffer_convertToGrallocUsageBits(
+ desc->usage0, desc->usage1);
+ sp<GraphicBuffer> gbuffer = allocator->createGraphicBuffer(desc->width,
+ desc->height, format, desc->layers, usage, &err);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ *outBuffer = GraphicBuffer_to_AHardwareBuffer(gbuffer.get());
+ // Ensure the buffer doesn't get destroyed with the sp<> goes away.
+ AHardwareBuffer_acquire(*outBuffer);
+ return NO_ERROR;
+}
+
+void AHardwareBuffer_acquire(AHardwareBuffer* buffer) {
+ AHardwareBuffer_to_GraphicBuffer(buffer)->incStrong(
+ (void*)AHardwareBuffer_acquire);
+}
+
+void AHardwareBuffer_release(AHardwareBuffer* buffer) {
+ AHardwareBuffer_to_GraphicBuffer(buffer)->decStrong(
+ (void*)AHardwareBuffer_acquire);
+}
+
+void AHardwareBuffer_describe(const AHardwareBuffer* buffer,
+ AHardwareBuffer_Desc* outDesc) {
+ if (!buffer || !outDesc) return;
+
+ const GraphicBuffer* gbuffer = AHardwareBuffer_to_GraphicBuffer(buffer);
+
+ outDesc->width = gbuffer->getWidth();
+ outDesc->height = gbuffer->getHeight();
+ outDesc->layers = gbuffer->getLayerCount();
+ outDesc->usage0 =
+ android_hardware_HardwareBuffer_convertFromGrallocUsageBits(
+ gbuffer->getUsage());
+ outDesc->usage1 = 0;
+ outDesc->format = android_hardware_HardwareBuffer_convertFromPixelFormat(
+ static_cast<uint32_t>(gbuffer->getPixelFormat()));
+}
+
+int AHardwareBuffer_lock(AHardwareBuffer* buffer, uint64_t usage0,
+ int32_t fence, const ARect* rect, void** outVirtualAddress) {
+ if (!buffer) return BAD_VALUE;
+
+ if (usage0 & ~(AHARDWAREBUFFER_USAGE0_CPU_READ_OFTEN |
+ AHARDWAREBUFFER_USAGE0_CPU_WRITE_OFTEN)) {
+ ALOGE("Invalid usage flags passed to AHardwareBuffer_lock; only "
+ " AHARDWAREBUFFER_USAGE0_CPU_* flags are allowed");
+ return BAD_VALUE;
+ }
+
+ uint32_t usage = android_hardware_HardwareBuffer_convertToGrallocUsageBits(
+ usage0, 0);
+ GraphicBuffer* gBuffer = AHardwareBuffer_to_GraphicBuffer(buffer);
+ if (!rect) {
+ return gBuffer->lockAsync(usage, outVirtualAddress, fence);
+ } else {
+ Rect bounds(rect->left, rect->top, rect->right, rect->bottom);
+ return gBuffer->lockAsync(usage, bounds, outVirtualAddress, fence);
+ }
+}
+
+int AHardwareBuffer_unlock(AHardwareBuffer* buffer, int32_t* fence) {
+ if (!buffer) return BAD_VALUE;
+
+ GraphicBuffer* gBuffer = AHardwareBuffer_to_GraphicBuffer(buffer);
+ return gBuffer->unlockAsync(fence);
+}
+
+int AHardwareBuffer_sendHandleToUnixSocket(const AHardwareBuffer* buffer,
+ int socketFd) {
+ if (!buffer) return BAD_VALUE;
+ const GraphicBuffer* gBuffer = AHardwareBuffer_to_GraphicBuffer(buffer);
+
+ size_t flattenedSize = gBuffer->getFlattenedSize();
+ size_t fdCount = gBuffer->getFdCount();
+
+ std::unique_ptr<uint8_t[]> data(new uint8_t[flattenedSize]);
+ std::unique_ptr<int[]> fds(new int[fdCount]);
+
+ // Make copies of needed items since flatten modifies them, and we don't
+ // want to send anything if there's an error during flatten.
+ size_t flattenedSizeCopy = flattenedSize;
+ size_t fdCountCopy = fdCount;
+ void* dataStart = data.get();
+ int* fdsStart = fds.get();
+ status_t err = gBuffer->flatten(dataStart, flattenedSizeCopy, fdsStart,
+ fdCountCopy);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ struct iovec iov[1];
+ iov[0].iov_base = data.get();
+ iov[0].iov_len = flattenedSize;
+
+ char buf[CMSG_SPACE(kDataBufferSize)];
+ struct msghdr msg = {
+ .msg_control = buf,
+ .msg_controllen = sizeof(buf),
+ .msg_iov = &iov[0],
+ .msg_iovlen = 1,
+ };
+
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int) * fdCount);
+ int* fdData = reinterpret_cast<int*>(CMSG_DATA(cmsg));
+ memcpy(fdData, fds.get(), sizeof(int) * fdCount);
+ msg.msg_controllen = cmsg->cmsg_len;
+
+ int result = sendmsg(socketFd, &msg, 0);
+ if (result <= 0) {
+ ALOGE("Error writing AHardwareBuffer to socket: error %#x (%s)",
+ result, strerror(errno));
+ return result;
+ }
+ return NO_ERROR;
+}
+
+int AHardwareBuffer_recvHandleFromUnixSocket(int socketFd,
+ AHardwareBuffer** outBuffer) {
+ if (!outBuffer) return BAD_VALUE;
+
+ char dataBuf[CMSG_SPACE(kDataBufferSize)];
+ char fdBuf[CMSG_SPACE(kDataBufferSize)];
+ struct iovec iov[1];
+ iov[0].iov_base = dataBuf;
+ iov[0].iov_len = sizeof(dataBuf);
+
+ struct msghdr msg = {
+ .msg_control = fdBuf,
+ .msg_controllen = sizeof(fdBuf),
+ .msg_iov = &iov[0],
+ .msg_iovlen = 1,
+ };
+
+ int result = recvmsg(socketFd, &msg, 0);
+ if (result <= 0) {
+ ALOGE("Error reading AHardwareBuffer from socket: error %#x (%s)",
+ result, strerror(errno));
+ return result;
+ }
+
+ if (msg.msg_iovlen != 1) {
+ ALOGE("Error reading AHardwareBuffer from socket: bad data length");
+ return INVALID_OPERATION;
+ }
+
+ if (msg.msg_controllen % sizeof(int) != 0) {
+ ALOGE("Error reading AHardwareBuffer from socket: bad fd length");
+ return INVALID_OPERATION;
+ }
+
+ size_t dataLen = msg.msg_iov[0].iov_len;
+ const void* data = static_cast<const void*>(msg.msg_iov[0].iov_base);
+ if (!data) {
+ ALOGE("Error reading AHardwareBuffer from socket: no buffer data");
+ return INVALID_OPERATION;
+ }
+
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+ if (!cmsg) {
+ ALOGE("Error reading AHardwareBuffer from socket: no fd header");
+ return INVALID_OPERATION;
+ }
+
+ size_t fdCount = msg.msg_controllen >> 2;
+ const int* fdData = reinterpret_cast<const int*>(CMSG_DATA(cmsg));
+ if (!fdData) {
+ ALOGE("Error reading AHardwareBuffer from socket: no fd data");
+ return INVALID_OPERATION;
+ }
+
+ GraphicBuffer* gBuffer = new GraphicBuffer();
+ status_t err = gBuffer->unflatten(data, dataLen, fdData, fdCount);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ *outBuffer = GraphicBuffer_to_AHardwareBuffer(gBuffer);
+ // Ensure the buffer has a positive ref-count.
+ AHardwareBuffer_acquire(*outBuffer);
+
+ return NO_ERROR;
+}
+
+const struct native_handle* AHardwareBuffer_getNativeHandle(
+ const AHardwareBuffer* buffer) {
+ if (!buffer) return nullptr;
+ const GraphicBuffer* gbuffer = AHardwareBuffer_to_GraphicBuffer(buffer);
+ return gbuffer->handle;
+}
+
+// ----------------------------------------------------------------------------
+// JNI functions
+// ----------------------------------------------------------------------------
+
+AHardwareBuffer* AHardwareBuffer_fromHardwareBuffer(JNIEnv* env,
+ jobject hardwareBufferObj) {
+ return android_hardware_HardwareBuffer_getNativeHardwareBuffer(env,
+ hardwareBufferObj);
+}
+
+jobject AHardwareBuffer_toHardwareBuffer(JNIEnv* env,
+ AHardwareBuffer* hardwareBuffer) {
+ return android_hardware_HardwareBuffer_createFromAHardwareBuffer(env,
+ hardwareBuffer);
+}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 5758a3c..f9e8fda 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -139,6 +139,17 @@
ANativeActivity_setWindowFlags;
ANativeActivity_setWindowFormat;
ANativeActivity_showSoftInput;
+ AHardwareBuffer_acquire; # introduced=26
+ AHardwareBuffer_allocate; # introduced=26
+ AHardwareBuffer_describe; # introduced=26
+ AHardwareBuffer_fromHardwareBuffer; # introduced=26
+ AHardwareBuffer_getNativeHandle; # introduced=26
+ AHardwareBuffer_lock; # introduced=26
+ AHardwareBuffer_recvHandleFromUnixSocket; # introduced=26
+ AHardwareBuffer_release; # introduced=26
+ AHardwareBuffer_sendHandleToUnixSocket; # introduced=26
+ AHardwareBuffer_toHardwareBuffer; # introduced=26
+ AHardwareBuffer_unlock; # introduced=26
ANativeWindow_acquire;
ANativeWindow_fromSurface;
ANativeWindow_fromSurfaceTexture; # introduced-arm=13 introduced-mips=13 introduced-x86=13
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 28d9e5c..e2080b0 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -37,5 +37,7 @@
<activity android:name="com.android.carrierdefaultapp.CaptivePortalLaunchActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:excludeFromRecents="true"/>
+ <service android:name="com.android.carrierdefaultapp.ProvisionObserver"
+ android:permission="android.permission.BIND_JOB_SERVICE"/>
</application>
</manifest>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java
index bc0fa02..3fd89d9 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java
@@ -28,6 +28,10 @@
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceive intent: " + intent.getAction());
+ if (ProvisionObserver.isDeferredForProvision(context, intent)) {
+ Log.d(TAG, "skip carrier actions during provisioning");
+ return;
+ }
List<Integer> actionList = CustomConfigLoader.loadCarrierActionList(context, intent);
for (int actionIdx : actionList) {
Log.d(TAG, "apply carrier action idx: " + actionIdx);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
new file mode 100644
index 0000000..3e34f0a
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.carrierdefaultapp;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.telephony.TelephonyIntents;
+
+/**
+ * Service to run {@link android.app.job.JobScheduler} job.
+ * Service to monitor when there is a change to conent URI
+ * {@link android.provider.Settings.Global#DEVICE_PROVISIONED DEVICE_PROVISIONED}
+ */
+public class ProvisionObserver extends JobService {
+
+ private static final String TAG = ProvisionObserver.class.getSimpleName();
+ public static final int PROVISION_OBSERVER_REEVALUATION_JOB_ID = 1;
+ // minimum & maximum update delay TBD
+ private static final int CONTENT_UPDATE_DELAY_MS = 100;
+ private static final int CONTENT_MAX_DELAY_MS = 200;
+
+ @Override
+ public boolean onStartJob(JobParameters jobParameters) {
+ switch (jobParameters.getJobId()) {
+ case PROVISION_OBSERVER_REEVALUATION_JOB_ID:
+ if (isProvisioned(this)) {
+ Log.d(TAG, "device provisioned, force network re-evaluation");
+ final ConnectivityManager connMgr = ConnectivityManager.from(this);
+ Network[] info = connMgr.getAllNetworks();
+ for (Network nw : info) {
+ final NetworkCapabilities nc = connMgr.getNetworkCapabilities(nw);
+ if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+ // force connectivity re-evaluation to assume skipped carrier actions.
+ // one of the following calls will match the last evaluation.
+ connMgr.reportNetworkConnectivity(nw, true);
+ connMgr.reportNetworkConnectivity(nw, false);
+ break;
+ }
+ }
+ }
+ default:
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters jobParameters) {
+ return false;
+ }
+
+ // Returns true if the device is not provisioned yet (in setup wizard), false otherwise
+ private static boolean isProvisioned(Context context) {
+ return Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) == 1;
+ }
+
+ /**
+ * Static utility function to schedule a job to execute upon the change of content URI
+ * {@link android.provider.Settings.Global#DEVICE_PROVISIONED DEVICE_PROVISIONED}.
+ * @param context The context used to retrieve the {@link ComponentName} and system services
+ * @return true carrier actions are deferred due to phone provisioning process, false otherwise
+ */
+ public static boolean isDeferredForProvision(Context context, Intent intent) {
+ if (isProvisioned(context)) {
+ return false;
+ }
+ int jobId;
+ switch(intent.getAction()) {
+ case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED:
+ jobId = PROVISION_OBSERVER_REEVALUATION_JOB_ID;
+ break;
+ default:
+ return false;
+ }
+ final JobScheduler jobScheduler = (JobScheduler) context.getSystemService(
+ Context.JOB_SCHEDULER_SERVICE);
+ final JobInfo job = new JobInfo.Builder(jobId,
+ new ComponentName(context, ProvisionObserver.class))
+ .addTriggerContentUri(new JobInfo.TriggerContentUri(
+ Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), 0))
+ .setTriggerContentUpdateDelay(CONTENT_UPDATE_DELAY_MS)
+ .setTriggerContentMaxDelay(CONTENT_MAX_DELAY_MS)
+ .build();
+ jobScheduler.schedule(job);
+ return true;
+ }
+}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 0a7bdbf..80d4a26 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -26,7 +26,6 @@
import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE;
import static android.os.BatteryManager.EXTRA_PLUGGED;
import static android.os.BatteryManager.EXTRA_STATUS;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
import android.app.ActivityManager;
import android.app.AlarmManager;
@@ -106,12 +105,6 @@
private static final String ACTION_FACE_UNLOCK_STOPPED
= "com.android.facelock.FACE_UNLOCK_STOPPED";
- private static final String ACTION_STRONG_AUTH_TIMEOUT =
- "com.android.systemui.ACTION_STRONG_AUTH_TIMEOUT";
- private static final String USER_ID = "com.android.systemui.USER_ID";
-
- private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
-
// Callback messages
private static final int MSG_TIME_UPDATE = 301;
private static final int MSG_BATTERY_UPDATE = 302;
@@ -203,7 +196,6 @@
private boolean mDeviceInteractive;
private boolean mScreenOn;
private SubscriptionManager mSubscriptionManager;
- private AlarmManager mAlarmManager;
private List<SubscriptionInfo> mSubscriptionInfo;
private TrustManager mTrustManager;
private UserManager mUserManager;
@@ -588,26 +580,12 @@
}
public void reportSuccessfulStrongAuthUnlockAttempt() {
- scheduleStrongAuthTimeout();
if (mFpm != null) {
byte[] token = null; /* TODO: pass real auth token once fp HAL supports it */
mFpm.resetTimeout(token);
}
}
- private void scheduleStrongAuthTimeout() {
- final DevicePolicyManager dpm =
- (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- long when = SystemClock.elapsedRealtime() + dpm.getRequiredStrongAuthTimeout(null,
- sCurrentUser);
- Intent intent = new Intent(ACTION_STRONG_AUTH_TIMEOUT);
- intent.putExtra(USER_ID, sCurrentUser);
- PendingIntent sender = PendingIntent.getBroadcast(mContext,
- sCurrentUser, intent, PendingIntent.FLAG_CANCEL_CURRENT);
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when, sender);
- notifyStrongAuthStateChanged(sCurrentUser);
- }
-
private void notifyStrongAuthStateChanged(int userId) {
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -723,17 +701,6 @@
}
};
- private final BroadcastReceiver mStrongAuthTimeoutReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (ACTION_STRONG_AUTH_TIMEOUT.equals(intent.getAction())) {
- int userId = intent.getIntExtra(USER_ID, -1);
- mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, userId);
- notifyStrongAuthStateChanged(userId);
- }
- }
- };
-
private final FingerprintManager.LockoutResetCallback mLockoutResetCallback
= new FingerprintManager.LockoutResetCallback() {
@Override
@@ -1034,7 +1001,6 @@
private KeyguardUpdateMonitor(Context context) {
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
- mAlarmManager = context.getSystemService(AlarmManager.class);
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
mStrongAuthTracker = new StrongAuthTracker(context);
@@ -1094,10 +1060,6 @@
e.rethrowAsRuntimeException();
}
- IntentFilter strongAuthTimeoutFilter = new IntentFilter();
- strongAuthTimeoutFilter.addAction(ACTION_STRONG_AUTH_TIMEOUT);
- context.registerReceiver(mStrongAuthTimeoutReceiver, strongAuthTimeoutFilter,
- PERMISSION_SELF, null /* handler */);
mTrustManager = (TrustManager) context.getSystemService(Context.TRUST_SERVICE);
mTrustManager.registerTrustListener(this);
mLockPatternUtils = new LockPatternUtils(context);
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 2b1582d..24a3aa9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1429,6 +1429,23 @@
}
};
+ public static final AppFilter FILTER_GAMES = new AppFilter() {
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(ApplicationsState.AppEntry info) {
+ // TODO: Update for the new game category.
+ boolean isGame;
+ synchronized (info.info) {
+ isGame = ((info.info.flags & ApplicationInfo.FLAG_IS_GAME) != 0)
+ || info.info.category == ApplicationInfo.CATEGORY_GAME;
+ }
+ return isGame;
+ }
+ };
+
public static class VolumeFilter implements AppFilter {
private final String mVolumeUuid;
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
index d34dd89..d4623d6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
@@ -22,13 +22,22 @@
public class InterestingConfigChanges {
private final Configuration mLastConfiguration = new Configuration();
+ private final int mFlags;
private int mLastDensity;
+ public InterestingConfigChanges() {
+ this(0);
+ }
+
+ public InterestingConfigChanges(int extraFlags) {
+ mFlags = extraFlags | ActivityInfo.CONFIG_LOCALE
+ | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_SCREEN_LAYOUT;
+ }
+
public boolean applyNewConfig(Resources res) {
int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
- if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE
- |ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) {
+ if (densityChanged || (configChanges & (mFlags)) != 0) {
mLastDensity = res.getDisplayMetrics().densityDpi;
return true;
}
diff --git a/packages/SettingsLib/tests/integ/Android.mk b/packages/SettingsLib/tests/integ/Android.mk
index 98bce0c..bd910dd 100644
--- a/packages/SettingsLib/tests/integ/Android.mk
+++ b/packages/SettingsLib/tests/integ/Android.mk
@@ -28,7 +28,8 @@
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 0000000..4f2347d
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.pm.ApplicationInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ApplicationsStateTest {
+ private ApplicationsState.AppFilter mFilter;
+ private ApplicationsState.AppEntry mEntry;
+
+ @Before
+ public void setUp() {
+ mFilter = ApplicationsState.FILTER_GAMES;
+ mEntry = mock(ApplicationsState.AppEntry.class);
+ mEntry.info = mock(ApplicationInfo.class);
+ }
+
+ @Test
+ public void testGamesFilterAcceptsGameDeprecated() {
+ mEntry.info.flags = ApplicationInfo.FLAG_IS_GAME;
+
+ assertThat(mFilter.filterApp(mEntry)).isTrue();
+ }
+
+ @Test
+ public void testGameFilterAcceptsCategorizedGame() {
+ mEntry.info.category = ApplicationInfo.CATEGORY_GAME;
+
+ assertThat(mFilter.filterApp(mEntry)).isTrue();
+ }
+
+ @Test
+ public void testGameFilterAcceptsCategorizedGameAndDeprecatedIsGame() {
+ mEntry.info.flags = ApplicationInfo.FLAG_IS_GAME;
+ mEntry.info.category = ApplicationInfo.CATEGORY_GAME;
+
+ assertThat(mFilter.filterApp(mEntry)).isTrue();
+ }
+
+ @Test
+ public void testGamesFilterRejectsNotGame() {
+ mEntry.info.category = ApplicationInfo.CATEGORY_UNDEFINED;
+
+ assertThat(mFilter.filterApp(mEntry)).isFalse();
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
new file mode 100644
index 0000000..19ce3d0
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -0,0 +1,1655 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.settings;
+
+import android.annotation.NonNull;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.providers.settings.GlobalSettingsProto;
+import android.providers.settings.SecureSettingsProto;
+import android.providers.settings.SettingProto;
+import android.providers.settings.SettingsServiceDumpProto;
+import android.providers.settings.SystemSettingsProto;
+import android.providers.settings.UserSettingsProto;
+import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
+
+/** @hide */
+class SettingsProtoDumpUtil {
+ private SettingsProtoDumpUtil() {}
+
+ static void dumpProtoLocked(SettingsProvider.SettingsRegistry settingsRegistry,
+ ProtoOutputStream proto) {
+ // Global settings
+ SettingsState globalSettings = settingsRegistry.getSettingsLocked(
+ SettingsProvider.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
+ long globalSettingsToken = proto.start(SettingsServiceDumpProto.GLOBAL_SETTINGS);
+ dumpProtoGlobalSettingsLocked(globalSettings, proto);
+ proto.end(globalSettingsToken);
+
+ // Per-user settings
+ SparseBooleanArray users = settingsRegistry.getKnownUsersLocked();
+ final int userCount = users.size();
+ for (int i = 0; i < userCount; i++) {
+ long userSettingsToken = proto.start(SettingsServiceDumpProto.USER_SETTINGS);
+ dumpProtoUserSettingsLocked(
+ settingsRegistry, UserHandle.of(users.keyAt(i)), proto);
+ proto.end(userSettingsToken);
+ }
+ }
+
+ /**
+ * Dump all settings of a user as a proto buf.
+ *
+ * @param settingsRegistry
+ * @param user The user the settings should be dumped for
+ * @param proto The proto buf stream to dump to
+ */
+ private static void dumpProtoUserSettingsLocked(
+ SettingsProvider.SettingsRegistry settingsRegistry,
+ @NonNull UserHandle user,
+ @NonNull ProtoOutputStream proto) {
+ proto.write(UserSettingsProto.USER_ID, user.getIdentifier());
+
+ SettingsState secureSettings = settingsRegistry.getSettingsLocked(
+ SettingsProvider.SETTINGS_TYPE_SECURE, user.getIdentifier());
+ long secureSettingsToken = proto.start(UserSettingsProto.SECURE_SETTINGS);
+ dumpProtoSecureSettingsLocked(secureSettings, proto);
+ proto.end(secureSettingsToken);
+
+ SettingsState systemSettings = settingsRegistry.getSettingsLocked(
+ SettingsProvider.SETTINGS_TYPE_SYSTEM, user.getIdentifier());
+ long systemSettingsToken = proto.start(UserSettingsProto.SYSTEM_SETTINGS);
+ dumpProtoSystemSettingsLocked(systemSettings, proto);
+ proto.end(systemSettingsToken);
+ }
+
+ private static void dumpProtoGlobalSettingsLocked(
+ @NonNull SettingsState s, @NonNull ProtoOutputStream p) {
+ dumpSetting(s, p,
+ Settings.Global.ADD_USERS_WHEN_LOCKED,
+ GlobalSettingsProto.ADD_USERS_WHEN_LOCKED);
+ dumpSetting(s, p,
+ Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED,
+ GlobalSettingsProto.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.AIRPLANE_MODE_ON,
+ GlobalSettingsProto.AIRPLANE_MODE_ON);
+ dumpSetting(s, p,
+ Settings.Global.THEATER_MODE_ON,
+ GlobalSettingsProto.THEATER_MODE_ON);
+ dumpSetting(s, p,
+ Settings.Global.RADIO_BLUETOOTH,
+ GlobalSettingsProto.RADIO_BLUETOOTH);
+ dumpSetting(s, p,
+ Settings.Global.RADIO_WIFI,
+ GlobalSettingsProto.RADIO_WIFI);
+ dumpSetting(s, p,
+ Settings.Global.RADIO_WIMAX,
+ GlobalSettingsProto.RADIO_WIMAX);
+ dumpSetting(s, p,
+ Settings.Global.RADIO_CELL,
+ GlobalSettingsProto.RADIO_CELL);
+ dumpSetting(s, p,
+ Settings.Global.RADIO_NFC,
+ GlobalSettingsProto.RADIO_NFC);
+ dumpSetting(s, p,
+ Settings.Global.AIRPLANE_MODE_RADIOS,
+ GlobalSettingsProto.AIRPLANE_MODE_RADIOS);
+ dumpSetting(s, p,
+ Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
+ GlobalSettingsProto.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_DISABLED_PROFILES,
+ GlobalSettingsProto.BLUETOOTH_DISABLED_PROFILES);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_INTEROPERABILITY_LIST,
+ GlobalSettingsProto.BLUETOOTH_INTEROPERABILITY_LIST);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_SLEEP_POLICY,
+ GlobalSettingsProto.WIFI_SLEEP_POLICY);
+ dumpSetting(s, p,
+ Settings.Global.AUTO_TIME,
+ GlobalSettingsProto.AUTO_TIME);
+ dumpSetting(s, p,
+ Settings.Global.AUTO_TIME_ZONE,
+ GlobalSettingsProto.AUTO_TIME_ZONE);
+ dumpSetting(s, p,
+ Settings.Global.CAR_DOCK_SOUND,
+ GlobalSettingsProto.CAR_DOCK_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.CAR_UNDOCK_SOUND,
+ GlobalSettingsProto.CAR_UNDOCK_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.DESK_DOCK_SOUND,
+ GlobalSettingsProto.DESK_DOCK_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.DESK_UNDOCK_SOUND,
+ GlobalSettingsProto.DESK_UNDOCK_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.DOCK_SOUNDS_ENABLED,
+ GlobalSettingsProto.DOCK_SOUNDS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY,
+ GlobalSettingsProto.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY);
+ dumpSetting(s, p,
+ Settings.Global.LOCK_SOUND,
+ GlobalSettingsProto.LOCK_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.UNLOCK_SOUND,
+ GlobalSettingsProto.UNLOCK_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.TRUSTED_SOUND,
+ GlobalSettingsProto.TRUSTED_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.LOW_BATTERY_SOUND,
+ GlobalSettingsProto.LOW_BATTERY_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.POWER_SOUNDS_ENABLED,
+ GlobalSettingsProto.POWER_SOUNDS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.WIRELESS_CHARGING_STARTED_SOUND,
+ GlobalSettingsProto.WIRELESS_CHARGING_STARTED_SOUND);
+ dumpSetting(s, p,
+ Settings.Global.CHARGING_SOUNDS_ENABLED,
+ GlobalSettingsProto.CHARGING_SOUNDS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
+ GlobalSettingsProto.STAY_ON_WHILE_PLUGGED_IN);
+ dumpSetting(s, p,
+ Settings.Global.BUGREPORT_IN_POWER_MENU,
+ GlobalSettingsProto.BUGREPORT_IN_POWER_MENU);
+ dumpSetting(s, p,
+ Settings.Global.ADB_ENABLED,
+ GlobalSettingsProto.ADB_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.DEBUG_VIEW_ATTRIBUTES,
+ GlobalSettingsProto.DEBUG_VIEW_ATTRIBUTES);
+ dumpSetting(s, p,
+ Settings.Global.ASSISTED_GPS_ENABLED,
+ GlobalSettingsProto.ASSISTED_GPS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_ON,
+ GlobalSettingsProto.BLUETOOTH_ON);
+ dumpSetting(s, p,
+ Settings.Global.CDMA_CELL_BROADCAST_SMS,
+ GlobalSettingsProto.CDMA_CELL_BROADCAST_SMS);
+ dumpSetting(s, p,
+ Settings.Global.CDMA_ROAMING_MODE,
+ GlobalSettingsProto.CDMA_ROAMING_MODE);
+ dumpSetting(s, p,
+ Settings.Global.CDMA_SUBSCRIPTION_MODE,
+ GlobalSettingsProto.CDMA_SUBSCRIPTION_MODE);
+ dumpSetting(s, p,
+ Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
+ GlobalSettingsProto.DATA_ACTIVITY_TIMEOUT_MOBILE);
+ dumpSetting(s, p,
+ Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
+ GlobalSettingsProto.DATA_ACTIVITY_TIMEOUT_WIFI);
+ dumpSetting(s, p,
+ Settings.Global.DATA_ROAMING,
+ GlobalSettingsProto.DATA_ROAMING);
+ dumpSetting(s, p,
+ Settings.Global.MDC_INITIAL_MAX_RETRY,
+ GlobalSettingsProto.MDC_INITIAL_MAX_RETRY);
+ dumpSetting(s, p,
+ Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
+ GlobalSettingsProto.FORCE_ALLOW_ON_EXTERNAL);
+ dumpSetting(s, p,
+ Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
+ GlobalSettingsProto.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES);
+ dumpSetting(s, p,
+ Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT,
+ GlobalSettingsProto.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT);
+ dumpSetting(s, p,
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
+ GlobalSettingsProto.DEVELOPMENT_SETTINGS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.DEVICE_PROVISIONED,
+ GlobalSettingsProto.DEVICE_PROVISIONED);
+ dumpSetting(s, p,
+ Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED,
+ GlobalSettingsProto.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.DISPLAY_SIZE_FORCED,
+ GlobalSettingsProto.DISPLAY_SIZE_FORCED);
+ dumpSetting(s, p,
+ Settings.Global.DISPLAY_SCALING_FORCE,
+ GlobalSettingsProto.DISPLAY_SCALING_FORCE);
+ dumpSetting(s, p,
+ Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE,
+ GlobalSettingsProto.DOWNLOAD_MAX_BYTES_OVER_MOBILE);
+ dumpSetting(s, p,
+ Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE,
+ GlobalSettingsProto.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE);
+ dumpSetting(s, p,
+ Settings.Global.HDMI_CONTROL_ENABLED,
+ GlobalSettingsProto.HDMI_CONTROL_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.HDMI_SYSTEM_AUDIO_ENABLED,
+ GlobalSettingsProto.HDMI_SYSTEM_AUDIO_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
+ GlobalSettingsProto.HDMI_CONTROL_AUTO_WAKEUP_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
+ GlobalSettingsProto.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.MHL_INPUT_SWITCHING_ENABLED,
+ GlobalSettingsProto.MHL_INPUT_SWITCHING_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.MHL_POWER_CHARGE_ENABLED,
+ GlobalSettingsProto.MHL_POWER_CHARGE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.MOBILE_DATA,
+ GlobalSettingsProto.MOBILE_DATA);
+ dumpSetting(s, p,
+ Settings.Global.MOBILE_DATA_ALWAYS_ON,
+ GlobalSettingsProto.MOBILE_DATA_ALWAYS_ON);
+ dumpSetting(s, p,
+ Settings.Global.CONNECTIVITY_METRICS_BUFFER_SIZE,
+ GlobalSettingsProto.CONNECTIVITY_METRICS_BUFFER_SIZE);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_ENABLED,
+ GlobalSettingsProto.NETSTATS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_POLL_INTERVAL,
+ GlobalSettingsProto.NETSTATS_POLL_INTERVAL);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_TIME_CACHE_MAX_AGE,
+ GlobalSettingsProto.NETSTATS_TIME_CACHE_MAX_AGE);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES,
+ GlobalSettingsProto.NETSTATS_GLOBAL_ALERT_BYTES);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_SAMPLE_ENABLED,
+ GlobalSettingsProto.NETSTATS_SAMPLE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_DEV_BUCKET_DURATION,
+ GlobalSettingsProto.NETSTATS_DEV_BUCKET_DURATION);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_DEV_PERSIST_BYTES,
+ GlobalSettingsProto.NETSTATS_DEV_PERSIST_BYTES);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_DEV_ROTATE_AGE,
+ GlobalSettingsProto.NETSTATS_DEV_ROTATE_AGE);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_DEV_DELETE_AGE,
+ GlobalSettingsProto.NETSTATS_DEV_DELETE_AGE);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_UID_BUCKET_DURATION,
+ GlobalSettingsProto.NETSTATS_UID_BUCKET_DURATION);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_UID_PERSIST_BYTES,
+ GlobalSettingsProto.NETSTATS_UID_PERSIST_BYTES);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_UID_ROTATE_AGE,
+ GlobalSettingsProto.NETSTATS_UID_ROTATE_AGE);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_UID_DELETE_AGE,
+ GlobalSettingsProto.NETSTATS_UID_DELETE_AGE);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_UID_TAG_BUCKET_DURATION,
+ GlobalSettingsProto.NETSTATS_UID_TAG_BUCKET_DURATION);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES,
+ GlobalSettingsProto.NETSTATS_UID_TAG_PERSIST_BYTES);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE,
+ GlobalSettingsProto.NETSTATS_UID_TAG_ROTATE_AGE);
+ dumpSetting(s, p,
+ Settings.Global.NETSTATS_UID_TAG_DELETE_AGE,
+ GlobalSettingsProto.NETSTATS_UID_TAG_DELETE_AGE);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_PREFERENCE,
+ GlobalSettingsProto.NETWORK_PREFERENCE);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_SCORER_APP,
+ GlobalSettingsProto.NETWORK_SCORER_APP);
+ dumpSetting(s, p,
+ Settings.Global.NITZ_UPDATE_DIFF,
+ GlobalSettingsProto.NITZ_UPDATE_DIFF);
+ dumpSetting(s, p,
+ Settings.Global.NITZ_UPDATE_SPACING,
+ GlobalSettingsProto.NITZ_UPDATE_SPACING);
+ dumpSetting(s, p,
+ Settings.Global.NTP_SERVER,
+ GlobalSettingsProto.NTP_SERVER);
+ dumpSetting(s, p,
+ Settings.Global.NTP_TIMEOUT,
+ GlobalSettingsProto.NTP_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.Global.STORAGE_BENCHMARK_INTERVAL,
+ GlobalSettingsProto.STORAGE_BENCHMARK_INTERVAL);
+ dumpSetting(s, p,
+ Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
+ GlobalSettingsProto.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS);
+ dumpSetting(s, p,
+ Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
+ GlobalSettingsProto.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT);
+ dumpSetting(s, p,
+ Settings.Global.DNS_RESOLVER_MIN_SAMPLES,
+ GlobalSettingsProto.DNS_RESOLVER_MIN_SAMPLES);
+ dumpSetting(s, p,
+ Settings.Global.DNS_RESOLVER_MAX_SAMPLES,
+ GlobalSettingsProto.DNS_RESOLVER_MAX_SAMPLES);
+ dumpSetting(s, p,
+ Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE,
+ GlobalSettingsProto.OTA_DISABLE_AUTOMATIC_UPDATE);
+ dumpSetting(s, p,
+ Settings.Global.PACKAGE_VERIFIER_ENABLE,
+ GlobalSettingsProto.PACKAGE_VERIFIER_ENABLE);
+ dumpSetting(s, p,
+ Settings.Global.PACKAGE_VERIFIER_TIMEOUT,
+ GlobalSettingsProto.PACKAGE_VERIFIER_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.Global.PACKAGE_VERIFIER_DEFAULT_RESPONSE,
+ GlobalSettingsProto.PACKAGE_VERIFIER_DEFAULT_RESPONSE);
+ dumpSetting(s, p,
+ Settings.Global.PACKAGE_VERIFIER_SETTING_VISIBLE,
+ GlobalSettingsProto.PACKAGE_VERIFIER_SETTING_VISIBLE);
+ dumpSetting(s, p,
+ Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB,
+ GlobalSettingsProto.PACKAGE_VERIFIER_INCLUDE_ADB);
+ dumpSetting(s, p,
+ Settings.Global.FSTRIM_MANDATORY_INTERVAL,
+ GlobalSettingsProto.FSTRIM_MANDATORY_INTERVAL);
+ dumpSetting(s, p,
+ Settings.Global.PDP_WATCHDOG_POLL_INTERVAL_MS,
+ GlobalSettingsProto.PDP_WATCHDOG_POLL_INTERVAL_MS);
+ dumpSetting(s, p,
+ Settings.Global.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS,
+ GlobalSettingsProto.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS);
+ dumpSetting(s, p,
+ Settings.Global.PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS,
+ GlobalSettingsProto.PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS);
+ dumpSetting(s, p,
+ Settings.Global.PDP_WATCHDOG_TRIGGER_PACKET_COUNT,
+ GlobalSettingsProto.PDP_WATCHDOG_TRIGGER_PACKET_COUNT);
+ dumpSetting(s, p,
+ Settings.Global.PDP_WATCHDOG_ERROR_POLL_COUNT,
+ GlobalSettingsProto.PDP_WATCHDOG_ERROR_POLL_COUNT);
+ dumpSetting(s, p,
+ Settings.Global.PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT,
+ GlobalSettingsProto.PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT);
+ dumpSetting(s, p,
+ Settings.Global.SAMPLING_PROFILER_MS,
+ GlobalSettingsProto.SAMPLING_PROFILER_MS);
+ dumpSetting(s, p,
+ Settings.Global.SETUP_PREPAID_DATA_SERVICE_URL,
+ GlobalSettingsProto.SETUP_PREPAID_DATA_SERVICE_URL);
+ dumpSetting(s, p,
+ Settings.Global.SETUP_PREPAID_DETECTION_TARGET_URL,
+ GlobalSettingsProto.SETUP_PREPAID_DETECTION_TARGET_URL);
+ dumpSetting(s, p,
+ Settings.Global.SETUP_PREPAID_DETECTION_REDIR_HOST,
+ GlobalSettingsProto.SETUP_PREPAID_DETECTION_REDIR_HOST);
+ dumpSetting(s, p,
+ Settings.Global.SMS_OUTGOING_CHECK_INTERVAL_MS,
+ GlobalSettingsProto.SMS_OUTGOING_CHECK_INTERVAL_MS);
+ dumpSetting(s, p,
+ Settings.Global.SMS_OUTGOING_CHECK_MAX_COUNT,
+ GlobalSettingsProto.SMS_OUTGOING_CHECK_MAX_COUNT);
+ dumpSetting(s, p,
+ Settings.Global.SMS_SHORT_CODE_CONFIRMATION,
+ GlobalSettingsProto.SMS_SHORT_CODE_CONFIRMATION);
+ dumpSetting(s, p,
+ Settings.Global.SMS_SHORT_CODE_RULE,
+ GlobalSettingsProto.SMS_SHORT_CODE_RULE);
+ dumpSetting(s, p,
+ Settings.Global.TCP_DEFAULT_INIT_RWND,
+ GlobalSettingsProto.TCP_DEFAULT_INIT_RWND);
+ dumpSetting(s, p,
+ Settings.Global.TETHER_SUPPORTED,
+ GlobalSettingsProto.TETHER_SUPPORTED);
+ dumpSetting(s, p,
+ Settings.Global.TETHER_DUN_REQUIRED,
+ GlobalSettingsProto.TETHER_DUN_REQUIRED);
+ dumpSetting(s, p,
+ Settings.Global.TETHER_DUN_APN,
+ GlobalSettingsProto.TETHER_DUN_APN);
+ dumpSetting(s, p,
+ Settings.Global.CARRIER_APP_WHITELIST,
+ GlobalSettingsProto.CARRIER_APP_WHITELIST);
+ dumpSetting(s, p,
+ Settings.Global.USB_MASS_STORAGE_ENABLED,
+ GlobalSettingsProto.USB_MASS_STORAGE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.USE_GOOGLE_MAIL,
+ GlobalSettingsProto.USE_GOOGLE_MAIL);
+ dumpSetting(s, p,
+ Settings.Global.WEBVIEW_DATA_REDUCTION_PROXY_KEY,
+ GlobalSettingsProto.WEBVIEW_DATA_REDUCTION_PROXY_KEY);
+ dumpSetting(s, p,
+ Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED,
+ GlobalSettingsProto.WEBVIEW_FALLBACK_LOGIC_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.WEBVIEW_PROVIDER,
+ GlobalSettingsProto.WEBVIEW_PROVIDER);
+ dumpSetting(s, p,
+ Settings.Global.WEBVIEW_MULTIPROCESS,
+ GlobalSettingsProto.WEBVIEW_MULTIPROCESS);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT,
+ GlobalSettingsProto.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS,
+ GlobalSettingsProto.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_AVOID_BAD_WIFI,
+ GlobalSettingsProto.NETWORK_AVOID_BAD_WIFI);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_DISPLAY_ON,
+ GlobalSettingsProto.WIFI_DISPLAY_ON);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON,
+ GlobalSettingsProto.WIFI_DISPLAY_CERTIFICATION_ON);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_DISPLAY_WPS_CONFIG,
+ GlobalSettingsProto.WIFI_DISPLAY_WPS_CONFIG);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+ GlobalSettingsProto.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON);
+ dumpSetting(s, p,
+ Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+ GlobalSettingsProto.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
+ GlobalSettingsProto.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_COUNTRY_CODE,
+ GlobalSettingsProto.WIFI_COUNTRY_CODE);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS,
+ GlobalSettingsProto.WIFI_FRAMEWORK_SCAN_INTERVAL_MS);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_IDLE_MS,
+ GlobalSettingsProto.WIFI_IDLE_MS);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_NUM_OPEN_NETWORKS_KEPT,
+ GlobalSettingsProto.WIFI_NUM_OPEN_NETWORKS_KEPT);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_ON,
+ GlobalSettingsProto.WIFI_ON);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
+ GlobalSettingsProto.WIFI_SCAN_ALWAYS_AVAILABLE);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_WAKEUP_ENABLED,
+ GlobalSettingsProto.WIFI_WAKEUP_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
+ GlobalSettingsProto.NETWORK_RECOMMENDATIONS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE,
+ GlobalSettingsProto.BLE_SCAN_ALWAYS_AVAILABLE);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_SAVED_STATE,
+ GlobalSettingsProto.WIFI_SAVED_STATE);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS,
+ GlobalSettingsProto.WIFI_SUPPLICANT_SCAN_INTERVAL_MS);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_ENHANCED_AUTO_JOIN,
+ GlobalSettingsProto.WIFI_ENHANCED_AUTO_JOIN);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_NETWORK_SHOW_RSSI,
+ GlobalSettingsProto.WIFI_NETWORK_SHOW_RSSI);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS,
+ GlobalSettingsProto.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_WATCHDOG_ON,
+ GlobalSettingsProto.WIFI_WATCHDOG_ON);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
+ GlobalSettingsProto.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED,
+ GlobalSettingsProto.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED,
+ GlobalSettingsProto.WIFI_VERBOSE_LOGGING_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT,
+ GlobalSettingsProto.WIFI_MAX_DHCP_RETRY_COUNT);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS,
+ GlobalSettingsProto.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
+ GlobalSettingsProto.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_FREQUENCY_BAND,
+ GlobalSettingsProto.WIFI_FREQUENCY_BAND);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_P2P_DEVICE_NAME,
+ GlobalSettingsProto.WIFI_P2P_DEVICE_NAME);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_REENABLE_DELAY_MS,
+ GlobalSettingsProto.WIFI_REENABLE_DELAY_MS);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS,
+ GlobalSettingsProto.WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS);
+ dumpSetting(s, p,
+ Settings.Global.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS,
+ GlobalSettingsProto.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS);
+ dumpSetting(s, p,
+ Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS,
+ GlobalSettingsProto.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS);
+ dumpSetting(s, p,
+ Settings.Global.PROVISIONING_APN_ALARM_DELAY_IN_MS,
+ GlobalSettingsProto.PROVISIONING_APN_ALARM_DELAY_IN_MS);
+ dumpSetting(s, p,
+ Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS,
+ GlobalSettingsProto.GPRS_REGISTER_CHECK_PERIOD_MS);
+ dumpSetting(s, p,
+ Settings.Global.WTF_IS_FATAL,
+ GlobalSettingsProto.WTF_IS_FATAL);
+ dumpSetting(s, p,
+ Settings.Global.MODE_RINGER,
+ GlobalSettingsProto.MODE_RINGER);
+ dumpSetting(s, p,
+ Settings.Global.OVERLAY_DISPLAY_DEVICES,
+ GlobalSettingsProto.OVERLAY_DISPLAY_DEVICES);
+ dumpSetting(s, p,
+ Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
+ GlobalSettingsProto.BATTERY_DISCHARGE_DURATION_THRESHOLD);
+ dumpSetting(s, p,
+ Settings.Global.BATTERY_DISCHARGE_THRESHOLD,
+ GlobalSettingsProto.BATTERY_DISCHARGE_THRESHOLD);
+ dumpSetting(s, p,
+ Settings.Global.SEND_ACTION_APP_ERROR,
+ GlobalSettingsProto.SEND_ACTION_APP_ERROR);
+ dumpSetting(s, p,
+ Settings.Global.DROPBOX_AGE_SECONDS,
+ GlobalSettingsProto.DROPBOX_AGE_SECONDS);
+ dumpSetting(s, p,
+ Settings.Global.DROPBOX_MAX_FILES,
+ GlobalSettingsProto.DROPBOX_MAX_FILES);
+ dumpSetting(s, p,
+ Settings.Global.DROPBOX_QUOTA_KB,
+ GlobalSettingsProto.DROPBOX_QUOTA_KB);
+ dumpSetting(s, p,
+ Settings.Global.DROPBOX_QUOTA_PERCENT,
+ GlobalSettingsProto.DROPBOX_QUOTA_PERCENT);
+ dumpSetting(s, p,
+ Settings.Global.DROPBOX_RESERVE_PERCENT,
+ GlobalSettingsProto.DROPBOX_RESERVE_PERCENT);
+ dumpSetting(s, p,
+ Settings.Global.DROPBOX_TAG_PREFIX,
+ GlobalSettingsProto.DROPBOX_TAG_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.ERROR_LOGCAT_PREFIX,
+ GlobalSettingsProto.ERROR_LOGCAT_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL,
+ GlobalSettingsProto.SYS_FREE_STORAGE_LOG_INTERVAL);
+ dumpSetting(s, p,
+ Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
+ GlobalSettingsProto.DISK_FREE_CHANGE_REPORTING_THRESHOLD);
+ dumpSetting(s, p,
+ Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE,
+ GlobalSettingsProto.SYS_STORAGE_THRESHOLD_PERCENTAGE);
+ dumpSetting(s, p,
+ Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES,
+ GlobalSettingsProto.SYS_STORAGE_THRESHOLD_MAX_BYTES);
+ dumpSetting(s, p,
+ Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
+ GlobalSettingsProto.SYS_STORAGE_FULL_THRESHOLD_BYTES);
+ dumpSetting(s, p,
+ Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
+ GlobalSettingsProto.SYNC_MAX_RETRY_DELAY_IN_SECONDS);
+ dumpSetting(s, p,
+ Settings.Global.CONNECTIVITY_CHANGE_DELAY,
+ GlobalSettingsProto.CONNECTIVITY_CHANGE_DELAY);
+ dumpSetting(s, p,
+ Settings.Global.CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS,
+ GlobalSettingsProto.CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS);
+ dumpSetting(s, p,
+ Settings.Global.PAC_CHANGE_DELAY,
+ GlobalSettingsProto.PAC_CHANGE_DELAY);
+ dumpSetting(s, p,
+ Settings.Global.CAPTIVE_PORTAL_MODE,
+ GlobalSettingsProto.CAPTIVE_PORTAL_MODE);
+ dumpSetting(s, p,
+ Settings.Global.CAPTIVE_PORTAL_SERVER,
+ GlobalSettingsProto.CAPTIVE_PORTAL_SERVER);
+ dumpSetting(s, p,
+ Settings.Global.CAPTIVE_PORTAL_HTTPS_URL,
+ GlobalSettingsProto.CAPTIVE_PORTAL_HTTPS_URL);
+ dumpSetting(s, p,
+ Settings.Global.CAPTIVE_PORTAL_HTTP_URL,
+ GlobalSettingsProto.CAPTIVE_PORTAL_HTTP_URL);
+ dumpSetting(s, p,
+ Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL,
+ GlobalSettingsProto.CAPTIVE_PORTAL_FALLBACK_URL);
+ dumpSetting(s, p,
+ Settings.Global.CAPTIVE_PORTAL_USE_HTTPS,
+ GlobalSettingsProto.CAPTIVE_PORTAL_USE_HTTPS);
+ dumpSetting(s, p,
+ Settings.Global.CAPTIVE_PORTAL_USER_AGENT,
+ GlobalSettingsProto.CAPTIVE_PORTAL_USER_AGENT);
+ dumpSetting(s, p,
+ Settings.Global.NSD_ON,
+ GlobalSettingsProto.NSD_ON);
+ dumpSetting(s, p,
+ Settings.Global.SET_INSTALL_LOCATION,
+ GlobalSettingsProto.SET_INSTALL_LOCATION);
+ dumpSetting(s, p,
+ Settings.Global.DEFAULT_INSTALL_LOCATION,
+ GlobalSettingsProto.DEFAULT_INSTALL_LOCATION);
+ dumpSetting(s, p,
+ Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY,
+ GlobalSettingsProto.INET_CONDITION_DEBOUNCE_UP_DELAY);
+ dumpSetting(s, p,
+ Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY,
+ GlobalSettingsProto.INET_CONDITION_DEBOUNCE_DOWN_DELAY);
+ dumpSetting(s, p,
+ Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT,
+ GlobalSettingsProto.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT);
+ dumpSetting(s, p,
+ Settings.Global.HTTP_PROXY,
+ GlobalSettingsProto.HTTP_PROXY);
+ dumpSetting(s, p,
+ Settings.Global.GLOBAL_HTTP_PROXY_HOST,
+ GlobalSettingsProto.GLOBAL_HTTP_PROXY_HOST);
+ dumpSetting(s, p,
+ Settings.Global.GLOBAL_HTTP_PROXY_PORT,
+ GlobalSettingsProto.GLOBAL_HTTP_PROXY_PORT);
+ dumpSetting(s, p,
+ Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
+ GlobalSettingsProto.GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
+ dumpSetting(s, p,
+ Settings.Global.GLOBAL_HTTP_PROXY_PAC,
+ GlobalSettingsProto.GLOBAL_HTTP_PROXY_PAC);
+ dumpSetting(s, p,
+ Settings.Global.SET_GLOBAL_HTTP_PROXY,
+ GlobalSettingsProto.SET_GLOBAL_HTTP_PROXY);
+ dumpSetting(s, p,
+ Settings.Global.DEFAULT_DNS_SERVER,
+ GlobalSettingsProto.DEFAULT_DNS_SERVER);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_HEADSET_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_HEADSET_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_MAP_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_MAP_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_MAP_CLIENT_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_MAP_CLIENT_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_SAP_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_SAP_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_PAN_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_PAN_PRIORITY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Global.DEVICE_IDLE_CONSTANTS,
+ GlobalSettingsProto.DEVICE_IDLE_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH,
+ GlobalSettingsProto.DEVICE_IDLE_CONSTANTS_WATCH);
+ dumpSetting(s, p,
+ Settings.Global.APP_IDLE_CONSTANTS,
+ GlobalSettingsProto.APP_IDLE_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.ALARM_MANAGER_CONSTANTS,
+ GlobalSettingsProto.ALARM_MANAGER_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.JOB_SCHEDULER_CONSTANTS,
+ GlobalSettingsProto.JOB_SCHEDULER_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.SHORTCUT_MANAGER_CONSTANTS,
+ GlobalSettingsProto.SHORTCUT_MANAGER_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.WINDOW_ANIMATION_SCALE,
+ GlobalSettingsProto.WINDOW_ANIMATION_SCALE);
+ dumpSetting(s, p,
+ Settings.Global.TRANSITION_ANIMATION_SCALE,
+ GlobalSettingsProto.TRANSITION_ANIMATION_SCALE);
+ dumpSetting(s, p,
+ Settings.Global.ANIMATOR_DURATION_SCALE,
+ GlobalSettingsProto.ANIMATOR_DURATION_SCALE);
+ dumpSetting(s, p,
+ Settings.Global.FANCY_IME_ANIMATIONS,
+ GlobalSettingsProto.FANCY_IME_ANIMATIONS);
+ dumpSetting(s, p,
+ Settings.Global.COMPATIBILITY_MODE,
+ GlobalSettingsProto.COMPATIBILITY_MODE);
+ dumpSetting(s, p,
+ Settings.Global.EMERGENCY_TONE,
+ GlobalSettingsProto.EMERGENCY_TONE);
+ dumpSetting(s, p,
+ Settings.Global.CALL_AUTO_RETRY,
+ GlobalSettingsProto.CALL_AUTO_RETRY);
+ dumpSetting(s, p,
+ Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
+ GlobalSettingsProto.EMERGENCY_AFFORDANCE_NEEDED);
+ dumpSetting(s, p,
+ Settings.Global.PREFERRED_NETWORK_MODE,
+ GlobalSettingsProto.PREFERRED_NETWORK_MODE);
+ dumpSetting(s, p,
+ Settings.Global.DEBUG_APP,
+ GlobalSettingsProto.DEBUG_APP);
+ dumpSetting(s, p,
+ Settings.Global.WAIT_FOR_DEBUGGER,
+ GlobalSettingsProto.WAIT_FOR_DEBUGGER);
+ dumpSetting(s, p,
+ Settings.Global.LOW_POWER_MODE,
+ GlobalSettingsProto.LOW_POWER_MODE);
+ dumpSetting(s, p,
+ Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL,
+ GlobalSettingsProto.LOW_POWER_MODE_TRIGGER_LEVEL);
+ dumpSetting(s, p,
+ Settings.Global.ALWAYS_FINISH_ACTIVITIES,
+ GlobalSettingsProto.ALWAYS_FINISH_ACTIVITIES);
+ dumpSetting(s, p,
+ Settings.Global.DOCK_AUDIO_MEDIA_ENABLED,
+ GlobalSettingsProto.DOCK_AUDIO_MEDIA_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.ENCODED_SURROUND_OUTPUT,
+ GlobalSettingsProto.ENCODED_SURROUND_OUTPUT);
+ dumpSetting(s, p,
+ Settings.Global.AUDIO_SAFE_VOLUME_STATE,
+ GlobalSettingsProto.AUDIO_SAFE_VOLUME_STATE);
+ dumpSetting(s, p,
+ Settings.Global.TZINFO_UPDATE_CONTENT_URL,
+ GlobalSettingsProto.TZINFO_UPDATE_CONTENT_URL);
+ dumpSetting(s, p,
+ Settings.Global.TZINFO_UPDATE_METADATA_URL,
+ GlobalSettingsProto.TZINFO_UPDATE_METADATA_URL);
+ dumpSetting(s, p,
+ Settings.Global.SELINUX_UPDATE_CONTENT_URL,
+ GlobalSettingsProto.SELINUX_UPDATE_CONTENT_URL);
+ dumpSetting(s, p,
+ Settings.Global.SELINUX_UPDATE_METADATA_URL,
+ GlobalSettingsProto.SELINUX_UPDATE_METADATA_URL);
+ dumpSetting(s, p,
+ Settings.Global.SMS_SHORT_CODES_UPDATE_CONTENT_URL,
+ GlobalSettingsProto.SMS_SHORT_CODES_UPDATE_CONTENT_URL);
+ dumpSetting(s, p,
+ Settings.Global.SMS_SHORT_CODES_UPDATE_METADATA_URL,
+ GlobalSettingsProto.SMS_SHORT_CODES_UPDATE_METADATA_URL);
+ dumpSetting(s, p,
+ Settings.Global.APN_DB_UPDATE_CONTENT_URL,
+ GlobalSettingsProto.APN_DB_UPDATE_CONTENT_URL);
+ dumpSetting(s, p,
+ Settings.Global.APN_DB_UPDATE_METADATA_URL,
+ GlobalSettingsProto.APN_DB_UPDATE_METADATA_URL);
+ dumpSetting(s, p,
+ Settings.Global.CERT_PIN_UPDATE_CONTENT_URL,
+ GlobalSettingsProto.CERT_PIN_UPDATE_CONTENT_URL);
+ dumpSetting(s, p,
+ Settings.Global.CERT_PIN_UPDATE_METADATA_URL,
+ GlobalSettingsProto.CERT_PIN_UPDATE_METADATA_URL);
+ dumpSetting(s, p,
+ Settings.Global.INTENT_FIREWALL_UPDATE_CONTENT_URL,
+ GlobalSettingsProto.INTENT_FIREWALL_UPDATE_CONTENT_URL);
+ dumpSetting(s, p,
+ Settings.Global.INTENT_FIREWALL_UPDATE_METADATA_URL,
+ GlobalSettingsProto.INTENT_FIREWALL_UPDATE_METADATA_URL);
+ dumpSetting(s, p,
+ Settings.Global.SELINUX_STATUS,
+ GlobalSettingsProto.SELINUX_STATUS);
+ dumpSetting(s, p,
+ Settings.Global.DEVELOPMENT_FORCE_RTL,
+ GlobalSettingsProto.DEVELOPMENT_FORCE_RTL);
+ dumpSetting(s, p,
+ Settings.Global.LOW_BATTERY_SOUND_TIMEOUT,
+ GlobalSettingsProto.LOW_BATTERY_SOUND_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS,
+ GlobalSettingsProto.WIFI_BOUNCE_DELAY_OVERRIDE_MS);
+ dumpSetting(s, p,
+ Settings.Global.POLICY_CONTROL,
+ GlobalSettingsProto.POLICY_CONTROL);
+ dumpSetting(s, p,
+ Settings.Global.ZEN_MODE,
+ GlobalSettingsProto.ZEN_MODE);
+ dumpSetting(s, p,
+ Settings.Global.ZEN_MODE_RINGER_LEVEL,
+ GlobalSettingsProto.ZEN_MODE_RINGER_LEVEL);
+ dumpSetting(s, p,
+ Settings.Global.ZEN_MODE_CONFIG_ETAG,
+ GlobalSettingsProto.ZEN_MODE_CONFIG_ETAG);
+ dumpSetting(s, p,
+ Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+ GlobalSettingsProto.HEADS_UP_NOTIFICATIONS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.DEVICE_NAME,
+ GlobalSettingsProto.DEVICE_NAME);
+ dumpSetting(s, p,
+ Settings.Global.NETWORK_SCORING_PROVISIONED,
+ GlobalSettingsProto.NETWORK_SCORING_PROVISIONED);
+ dumpSetting(s, p,
+ Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT,
+ GlobalSettingsProto.REQUIRE_PASSWORD_TO_DECRYPT);
+ dumpSetting(s, p,
+ Settings.Global.ENHANCED_4G_MODE_ENABLED,
+ GlobalSettingsProto.ENHANCED_4G_MODE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.VT_IMS_ENABLED,
+ GlobalSettingsProto.VT_IMS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.WFC_IMS_ENABLED,
+ GlobalSettingsProto.WFC_IMS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.WFC_IMS_MODE,
+ GlobalSettingsProto.WFC_IMS_MODE);
+ dumpSetting(s, p,
+ Settings.Global.WFC_IMS_ROAMING_MODE,
+ GlobalSettingsProto.WFC_IMS_ROAMING_MODE);
+ dumpSetting(s, p,
+ Settings.Global.WFC_IMS_ROAMING_ENABLED,
+ GlobalSettingsProto.WFC_IMS_ROAMING_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.LTE_SERVICE_FORCED,
+ GlobalSettingsProto.LTE_SERVICE_FORCED);
+ dumpSetting(s, p,
+ Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
+ GlobalSettingsProto.EPHEMERAL_COOKIE_MAX_SIZE_BYTES);
+ dumpSetting(s, p,
+ Settings.Global.ENABLE_EPHEMERAL_FEATURE,
+ GlobalSettingsProto.ENABLE_EPHEMERAL_FEATURE);
+ dumpSetting(s, p,
+ Settings.Global.UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS,
+ GlobalSettingsProto.UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS);
+ dumpSetting(s, p,
+ Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED,
+ GlobalSettingsProto.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED);
+ dumpSetting(s, p,
+ Settings.Global.BOOT_COUNT,
+ GlobalSettingsProto.BOOT_COUNT);
+ dumpSetting(s, p,
+ Settings.Global.SAFE_BOOT_DISALLOWED,
+ GlobalSettingsProto.SAFE_BOOT_DISALLOWED);
+ dumpSetting(s, p,
+ Settings.Global.DEVICE_DEMO_MODE,
+ GlobalSettingsProto.DEVICE_DEMO_MODE);
+ dumpSetting(s, p,
+ Settings.Global.RETAIL_DEMO_MODE_CONSTANTS,
+ GlobalSettingsProto.RETAIL_DEMO_MODE_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.DATABASE_DOWNGRADE_REASON,
+ GlobalSettingsProto.DATABASE_DOWNGRADE_REASON);
+ dumpSetting(s, p,
+ Settings.Global.CONTACTS_DATABASE_WAL_ENABLED,
+ GlobalSettingsProto.CONTACTS_DATABASE_WAL_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
+ GlobalSettingsProto.MULTI_SIM_VOICE_CALL_SUBSCRIPTION);
+ dumpSetting(s, p,
+ Settings.Global.MULTI_SIM_VOICE_PROMPT,
+ GlobalSettingsProto.MULTI_SIM_VOICE_PROMPT);
+ dumpSetting(s, p,
+ Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
+ GlobalSettingsProto.MULTI_SIM_DATA_CALL_SUBSCRIPTION);
+ dumpSetting(s, p,
+ Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
+ GlobalSettingsProto.MULTI_SIM_SMS_SUBSCRIPTION);
+ dumpSetting(s, p,
+ Settings.Global.MULTI_SIM_SMS_PROMPT,
+ GlobalSettingsProto.MULTI_SIM_SMS_PROMPT);
+ dumpSetting(s, p,
+ Settings.Global.NEW_CONTACT_AGGREGATOR,
+ GlobalSettingsProto.NEW_CONTACT_AGGREGATOR);
+ dumpSetting(s, p,
+ Settings.Global.CONTACT_METADATA_SYNC_ENABLED,
+ GlobalSettingsProto.CONTACT_METADATA_SYNC_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.ENABLE_CELLULAR_ON_BOOT,
+ GlobalSettingsProto.ENABLE_CELLULAR_ON_BOOT);
+ dumpSetting(s, p,
+ Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
+ GlobalSettingsProto.MAX_NOTIFICATION_ENQUEUE_RATE);
+ dumpSetting(s, p,
+ Settings.Global.CELL_ON,
+ GlobalSettingsProto.CELL_ON);
+ }
+
+ /** Dump a single {@link SettingsState.Setting} to a proto buf */
+ private static void dumpSetting(@NonNull SettingsState settings,
+ @NonNull ProtoOutputStream proto, String settingName, long fieldId) {
+ SettingsState.Setting setting = settings.getSettingLocked(settingName);
+ long settingsToken = proto.start(fieldId);
+ proto.write(SettingProto.ID, setting.getId());
+ proto.write(SettingProto.NAME, settingName);
+ if (setting.getPackageName() != null) {
+ proto.write(SettingProto.PKG, setting.getPackageName());
+ }
+ proto.write(SettingProto.VALUE, setting.getValue());
+ if (setting.getDefaultValue() != null) {
+ proto.write(SettingProto.DEFAULT_VALUE, setting.getDefaultValue());
+ proto.write(SettingProto.DEFAULT_FROM_SYSTEM, setting.isDefaultFromSystem());
+ }
+ proto.end(settingsToken);
+ }
+
+ static void dumpProtoSecureSettingsLocked(
+ @NonNull SettingsState s, @NonNull ProtoOutputStream p) {
+ dumpSetting(s, p,
+ Settings.Secure.ANDROID_ID,
+ SecureSettingsProto.ANDROID_ID);
+ dumpSetting(s, p,
+ Settings.Secure.DEFAULT_INPUT_METHOD,
+ SecureSettingsProto.DEFAULT_INPUT_METHOD);
+ dumpSetting(s, p,
+ Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
+ SecureSettingsProto.SELECTED_INPUT_METHOD_SUBTYPE);
+ dumpSetting(s, p,
+ Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY,
+ SecureSettingsProto.INPUT_METHODS_SUBTYPE_HISTORY);
+ dumpSetting(s, p,
+ Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY,
+ SecureSettingsProto.INPUT_METHOD_SELECTOR_VISIBILITY);
+ dumpSetting(s, p,
+ Settings.Secure.VOICE_INTERACTION_SERVICE,
+ SecureSettingsProto.VOICE_INTERACTION_SERVICE);
+ dumpSetting(s, p,
+ Settings.Secure.AUTO_FILL_SERVICE,
+ SecureSettingsProto.AUTO_FILL_SERVICE);
+ dumpSetting(s, p,
+ Settings.Secure.BLUETOOTH_HCI_LOG,
+ SecureSettingsProto.BLUETOOTH_HCI_LOG);
+ dumpSetting(s, p,
+ Settings.Secure.USER_SETUP_COMPLETE,
+ SecureSettingsProto.USER_SETUP_COMPLETE);
+ dumpSetting(s, p,
+ Settings.Secure.COMPLETED_CATEGORY_PREFIX,
+ SecureSettingsProto.COMPLETED_CATEGORY_PREFIX);
+ dumpSetting(s, p,
+ Settings.Secure.ENABLED_INPUT_METHODS,
+ SecureSettingsProto.ENABLED_INPUT_METHODS);
+ dumpSetting(s, p,
+ Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
+ SecureSettingsProto.DISABLED_SYSTEM_INPUT_METHODS);
+ dumpSetting(s, p,
+ Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+ SecureSettingsProto.SHOW_IME_WITH_HARD_KEYBOARD);
+ dumpSetting(s, p,
+ Settings.Secure.ALWAYS_ON_VPN_APP,
+ SecureSettingsProto.ALWAYS_ON_VPN_APP);
+ dumpSetting(s, p,
+ Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
+ SecureSettingsProto.ALWAYS_ON_VPN_LOCKDOWN);
+ dumpSetting(s, p,
+ Settings.Secure.INSTALL_NON_MARKET_APPS,
+ SecureSettingsProto.INSTALL_NON_MARKET_APPS);
+ dumpSetting(s, p,
+ Settings.Secure.LOCATION_MODE,
+ SecureSettingsProto.LOCATION_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.LOCATION_PREVIOUS_MODE,
+ SecureSettingsProto.LOCATION_PREVIOUS_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
+ SecureSettingsProto.LOCK_TO_APP_EXIT_LOCKED);
+ dumpSetting(s, p,
+ Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+ SecureSettingsProto.LOCK_SCREEN_LOCK_AFTER_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
+ SecureSettingsProto.LOCK_SCREEN_ALLOW_REMOTE_INPUT);
+ dumpSetting(s, p,
+ Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING,
+ SecureSettingsProto.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING);
+ dumpSetting(s, p,
+ Settings.Secure.TRUST_AGENTS_INITIALIZED,
+ SecureSettingsProto.TRUST_AGENTS_INITIALIZED);
+ dumpSetting(s, p,
+ Settings.Secure.PARENTAL_CONTROL_ENABLED,
+ SecureSettingsProto.PARENTAL_CONTROL_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.PARENTAL_CONTROL_LAST_UPDATE,
+ SecureSettingsProto.PARENTAL_CONTROL_LAST_UPDATE);
+ dumpSetting(s, p,
+ Settings.Secure.PARENTAL_CONTROL_REDIRECT_URL,
+ SecureSettingsProto.PARENTAL_CONTROL_REDIRECT_URL);
+ dumpSetting(s, p,
+ Settings.Secure.SETTINGS_CLASSNAME,
+ SecureSettingsProto.SETTINGS_CLASSNAME);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_ENABLED,
+ SecureSettingsProto.ACCESSIBILITY_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.TOUCH_EXPLORATION_ENABLED,
+ SecureSettingsProto.TOUCH_EXPLORATION_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ SecureSettingsProto.ENABLED_ACCESSIBILITY_SERVICES);
+ dumpSetting(s, p,
+ Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+ SecureSettingsProto.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD,
+ SecureSettingsProto.ACCESSIBILITY_SPEAK_PASSWORD);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
+ SecureSettingsProto.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION,
+ SecureSettingsProto.ACCESSIBILITY_SCRIPT_INJECTION);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SCREEN_READER_URL,
+ SecureSettingsProto.ACCESSIBILITY_SCREEN_READER_URL);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS,
+ SecureSettingsProto.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+ SecureSettingsProto.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+ SecureSettingsProto.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ SecureSettingsProto.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_LOCALE,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_LOCALE);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_PRESET);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_EDGE_TYPE);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_EDGE_COLOR);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_TYPEFACE);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE,
+ SecureSettingsProto.ACCESSIBILITY_CAPTIONING_FONT_SCALE);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+ SecureSettingsProto.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+ SecureSettingsProto.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
+ SecureSettingsProto.ACCESSIBILITY_DISPLAY_DALTONIZER);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
+ SecureSettingsProto.ACCESSIBILITY_AUTOCLICK_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
+ SecureSettingsProto.ACCESSIBILITY_AUTOCLICK_DELAY);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
+ SecureSettingsProto.ACCESSIBILITY_LARGE_POINTER_ICON);
+ dumpSetting(s, p,
+ Settings.Secure.LONG_PRESS_TIMEOUT,
+ SecureSettingsProto.LONG_PRESS_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.Secure.MULTI_PRESS_TIMEOUT,
+ SecureSettingsProto.MULTI_PRESS_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.Secure.ENABLED_PRINT_SERVICES,
+ SecureSettingsProto.ENABLED_PRINT_SERVICES);
+ dumpSetting(s, p,
+ Settings.Secure.DISABLED_PRINT_SERVICES,
+ SecureSettingsProto.DISABLED_PRINT_SERVICES);
+ dumpSetting(s, p,
+ Settings.Secure.DISPLAY_DENSITY_FORCED,
+ SecureSettingsProto.DISPLAY_DENSITY_FORCED);
+ dumpSetting(s, p,
+ Settings.Secure.TTS_DEFAULT_RATE,
+ SecureSettingsProto.TTS_DEFAULT_RATE);
+ dumpSetting(s, p,
+ Settings.Secure.TTS_DEFAULT_PITCH,
+ SecureSettingsProto.TTS_DEFAULT_PITCH);
+ dumpSetting(s, p,
+ Settings.Secure.TTS_DEFAULT_SYNTH,
+ SecureSettingsProto.TTS_DEFAULT_SYNTH);
+ dumpSetting(s, p,
+ Settings.Secure.TTS_DEFAULT_LOCALE,
+ SecureSettingsProto.TTS_DEFAULT_LOCALE);
+ dumpSetting(s, p,
+ Settings.Secure.TTS_ENABLED_PLUGINS,
+ SecureSettingsProto.TTS_ENABLED_PLUGINS);
+ dumpSetting(s, p,
+ Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
+ SecureSettingsProto.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS);
+ dumpSetting(s, p,
+ Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS,
+ SecureSettingsProto.ALLOWED_GEOLOCATION_ORIGINS);
+ dumpSetting(s, p,
+ Settings.Secure.PREFERRED_TTY_MODE,
+ SecureSettingsProto.PREFERRED_TTY_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.ENHANCED_VOICE_PRIVACY_ENABLED,
+ SecureSettingsProto.ENHANCED_VOICE_PRIVACY_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.TTY_MODE_ENABLED,
+ SecureSettingsProto.TTY_MODE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.BACKUP_ENABLED,
+ SecureSettingsProto.BACKUP_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.BACKUP_AUTO_RESTORE,
+ SecureSettingsProto.BACKUP_AUTO_RESTORE);
+ dumpSetting(s, p,
+ Settings.Secure.BACKUP_PROVISIONED,
+ SecureSettingsProto.BACKUP_PROVISIONED);
+ dumpSetting(s, p,
+ Settings.Secure.BACKUP_TRANSPORT,
+ SecureSettingsProto.BACKUP_TRANSPORT);
+ dumpSetting(s, p,
+ Settings.Secure.LAST_SETUP_SHOWN,
+ SecureSettingsProto.LAST_SETUP_SHOWN);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY,
+ SecureSettingsProto.SEARCH_GLOBAL_SEARCH_ACTIVITY);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_NUM_PROMOTED_SOURCES,
+ SecureSettingsProto.SEARCH_NUM_PROMOTED_SOURCES);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_MAX_RESULTS_TO_DISPLAY,
+ SecureSettingsProto.SEARCH_MAX_RESULTS_TO_DISPLAY);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_MAX_RESULTS_PER_SOURCE,
+ SecureSettingsProto.SEARCH_MAX_RESULTS_PER_SOURCE);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_WEB_RESULTS_OVERRIDE_LIMIT,
+ SecureSettingsProto.SEARCH_WEB_RESULTS_OVERRIDE_LIMIT);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_PROMOTED_SOURCE_DEADLINE_MILLIS,
+ SecureSettingsProto.SEARCH_PROMOTED_SOURCE_DEADLINE_MILLIS);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_SOURCE_TIMEOUT_MILLIS,
+ SecureSettingsProto.SEARCH_SOURCE_TIMEOUT_MILLIS);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_PREFILL_MILLIS,
+ SecureSettingsProto.SEARCH_PREFILL_MILLIS);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_MAX_STAT_AGE_MILLIS,
+ SecureSettingsProto.SEARCH_MAX_STAT_AGE_MILLIS);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_MAX_SOURCE_EVENT_AGE_MILLIS,
+ SecureSettingsProto.SEARCH_MAX_SOURCE_EVENT_AGE_MILLIS);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_MIN_IMPRESSIONS_FOR_SOURCE_RANKING,
+ SecureSettingsProto.SEARCH_MIN_IMPRESSIONS_FOR_SOURCE_RANKING);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_MIN_CLICKS_FOR_SOURCE_RANKING,
+ SecureSettingsProto.SEARCH_MIN_CLICKS_FOR_SOURCE_RANKING);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_MAX_SHORTCUTS_RETURNED,
+ SecureSettingsProto.SEARCH_MAX_SHORTCUTS_RETURNED);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_QUERY_THREAD_CORE_POOL_SIZE,
+ SecureSettingsProto.SEARCH_QUERY_THREAD_CORE_POOL_SIZE);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_QUERY_THREAD_MAX_POOL_SIZE,
+ SecureSettingsProto.SEARCH_QUERY_THREAD_MAX_POOL_SIZE);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_SHORTCUT_REFRESH_CORE_POOL_SIZE,
+ SecureSettingsProto.SEARCH_SHORTCUT_REFRESH_CORE_POOL_SIZE);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_SHORTCUT_REFRESH_MAX_POOL_SIZE,
+ SecureSettingsProto.SEARCH_SHORTCUT_REFRESH_MAX_POOL_SIZE);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_THREAD_KEEPALIVE_SECONDS,
+ SecureSettingsProto.SEARCH_THREAD_KEEPALIVE_SECONDS);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_PER_SOURCE_CONCURRENT_QUERY_LIMIT,
+ SecureSettingsProto.SEARCH_PER_SOURCE_CONCURRENT_QUERY_LIMIT);
+ dumpSetting(s, p,
+ Settings.Secure.MOUNT_PLAY_NOTIFICATION_SND,
+ SecureSettingsProto.MOUNT_PLAY_NOTIFICATION_SND);
+ dumpSetting(s, p,
+ Settings.Secure.MOUNT_UMS_AUTOSTART,
+ SecureSettingsProto.MOUNT_UMS_AUTOSTART);
+ dumpSetting(s, p,
+ Settings.Secure.MOUNT_UMS_PROMPT,
+ SecureSettingsProto.MOUNT_UMS_PROMPT);
+ dumpSetting(s, p,
+ Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED,
+ SecureSettingsProto.MOUNT_UMS_NOTIFY_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ANR_SHOW_BACKGROUND,
+ SecureSettingsProto.ANR_SHOW_BACKGROUND);
+ dumpSetting(s, p,
+ Settings.Secure.VOICE_RECOGNITION_SERVICE,
+ SecureSettingsProto.VOICE_RECOGNITION_SERVICE);
+ dumpSetting(s, p,
+ Settings.Secure.PACKAGE_VERIFIER_USER_CONSENT,
+ SecureSettingsProto.PACKAGE_VERIFIER_USER_CONSENT);
+ dumpSetting(s, p,
+ Settings.Secure.SELECTED_SPELL_CHECKER,
+ SecureSettingsProto.SELECTED_SPELL_CHECKER);
+ dumpSetting(s, p,
+ Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE,
+ SecureSettingsProto.SELECTED_SPELL_CHECKER_SUBTYPE);
+ dumpSetting(s, p,
+ Settings.Secure.SPELL_CHECKER_ENABLED,
+ SecureSettingsProto.SPELL_CHECKER_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
+ SecureSettingsProto.INCALL_POWER_BUTTON_BEHAVIOR);
+ dumpSetting(s, p,
+ Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR,
+ SecureSettingsProto.INCALL_BACK_BUTTON_BEHAVIOR);
+ dumpSetting(s, p,
+ Settings.Secure.WAKE_GESTURE_ENABLED,
+ SecureSettingsProto.WAKE_GESTURE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.DOZE_ENABLED,
+ SecureSettingsProto.DOZE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.DOZE_ALWAYS_ON,
+ SecureSettingsProto.DOZE_ALWAYS_ON);
+ dumpSetting(s, p,
+ Settings.Secure.DOZE_PULSE_ON_PICK_UP,
+ SecureSettingsProto.DOZE_PULSE_ON_PICK_UP);
+ dumpSetting(s, p,
+ Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP,
+ SecureSettingsProto.DOZE_PULSE_ON_DOUBLE_TAP);
+ dumpSetting(s, p,
+ Settings.Secure.UI_NIGHT_MODE,
+ SecureSettingsProto.UI_NIGHT_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.SCREENSAVER_ENABLED,
+ SecureSettingsProto.SCREENSAVER_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.SCREENSAVER_COMPONENTS,
+ SecureSettingsProto.SCREENSAVER_COMPONENTS);
+ dumpSetting(s, p,
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+ SecureSettingsProto.SCREENSAVER_ACTIVATE_ON_DOCK);
+ dumpSetting(s, p,
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ SecureSettingsProto.SCREENSAVER_ACTIVATE_ON_SLEEP);
+ dumpSetting(s, p,
+ Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
+ SecureSettingsProto.SCREENSAVER_DEFAULT_COMPONENT);
+ dumpSetting(s, p,
+ Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
+ SecureSettingsProto.NFC_PAYMENT_DEFAULT_COMPONENT);
+ dumpSetting(s, p,
+ Settings.Secure.NFC_PAYMENT_FOREGROUND,
+ SecureSettingsProto.NFC_PAYMENT_FOREGROUND);
+ dumpSetting(s, p,
+ Settings.Secure.SMS_DEFAULT_APPLICATION,
+ SecureSettingsProto.SMS_DEFAULT_APPLICATION);
+ dumpSetting(s, p,
+ Settings.Secure.DIALER_DEFAULT_APPLICATION,
+ SecureSettingsProto.DIALER_DEFAULT_APPLICATION);
+ dumpSetting(s, p,
+ Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
+ SecureSettingsProto.EMERGENCY_ASSISTANCE_APPLICATION);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_STRUCTURE_ENABLED,
+ SecureSettingsProto.ASSIST_STRUCTURE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_SCREENSHOT_ENABLED,
+ SecureSettingsProto.ASSIST_SCREENSHOT_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_DISCLOSURE_ENABLED,
+ SecureSettingsProto.ASSIST_DISCLOSURE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT,
+ SecureSettingsProto.ENABLED_NOTIFICATION_ASSISTANT);
+ dumpSetting(s, p,
+ Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+ SecureSettingsProto.ENABLED_NOTIFICATION_LISTENERS);
+ dumpSetting(s, p,
+ Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
+ SecureSettingsProto.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES);
+ dumpSetting(s, p,
+ Settings.Secure.SYNC_PARENT_SOUNDS,
+ SecureSettingsProto.SYNC_PARENT_SOUNDS);
+ dumpSetting(s, p,
+ Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+ SecureSettingsProto.IMMERSIVE_MODE_CONFIRMATIONS);
+ dumpSetting(s, p,
+ Settings.Secure.PRINT_SERVICE_SEARCH_URI,
+ SecureSettingsProto.PRINT_SERVICE_SEARCH_URI);
+ dumpSetting(s, p,
+ Settings.Secure.PAYMENT_SERVICE_SEARCH_URI,
+ SecureSettingsProto.PAYMENT_SERVICE_SEARCH_URI);
+ dumpSetting(s, p,
+ Settings.Secure.SKIP_FIRST_USE_HINTS,
+ SecureSettingsProto.SKIP_FIRST_USE_HINTS);
+ dumpSetting(s, p,
+ Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS,
+ SecureSettingsProto.UNSAFE_VOLUME_MUSIC_ACTIVE_MS);
+ dumpSetting(s, p,
+ Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ SecureSettingsProto.LOCK_SCREEN_SHOW_NOTIFICATIONS);
+ dumpSetting(s, p,
+ Settings.Secure.TV_INPUT_HIDDEN_INPUTS,
+ SecureSettingsProto.TV_INPUT_HIDDEN_INPUTS);
+ dumpSetting(s, p,
+ Settings.Secure.TV_INPUT_CUSTOM_LABELS,
+ SecureSettingsProto.TV_INPUT_CUSTOM_LABELS);
+ dumpSetting(s, p,
+ Settings.Secure.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED,
+ SecureSettingsProto.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED);
+ dumpSetting(s, p,
+ Settings.Secure.SLEEP_TIMEOUT,
+ SecureSettingsProto.SLEEP_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.Secure.DOUBLE_TAP_TO_WAKE,
+ SecureSettingsProto.DOUBLE_TAP_TO_WAKE);
+ dumpSetting(s, p,
+ Settings.Secure.ASSISTANT,
+ SecureSettingsProto.ASSISTANT);
+ dumpSetting(s, p,
+ Settings.Secure.CAMERA_GESTURE_DISABLED,
+ SecureSettingsProto.CAMERA_GESTURE_DISABLED);
+ dumpSetting(s, p,
+ Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED,
+ SecureSettingsProto.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED);
+ dumpSetting(s, p,
+ Settings.Secure.CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED,
+ SecureSettingsProto.CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.NIGHT_DISPLAY_ACTIVATED,
+ SecureSettingsProto.NIGHT_DISPLAY_ACTIVATED);
+ dumpSetting(s, p,
+ Settings.Secure.NIGHT_DISPLAY_AUTO_MODE,
+ SecureSettingsProto.NIGHT_DISPLAY_AUTO_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.NIGHT_DISPLAY_CUSTOM_START_TIME,
+ SecureSettingsProto.NIGHT_DISPLAY_CUSTOM_START_TIME);
+ dumpSetting(s, p,
+ Settings.Secure.NIGHT_DISPLAY_CUSTOM_END_TIME,
+ SecureSettingsProto.NIGHT_DISPLAY_CUSTOM_END_TIME);
+ dumpSetting(s, p,
+ Settings.Secure.BRIGHTNESS_USE_TWILIGHT,
+ SecureSettingsProto.BRIGHTNESS_USE_TWILIGHT);
+ dumpSetting(s, p,
+ Settings.Secure.ENABLED_VR_LISTENERS,
+ SecureSettingsProto.ENABLED_VR_LISTENERS);
+ dumpSetting(s, p,
+ Settings.Secure.VR_DISPLAY_MODE,
+ SecureSettingsProto.VR_DISPLAY_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.CARRIER_APPS_HANDLED,
+ SecureSettingsProto.CARRIER_APPS_HANDLED);
+ dumpSetting(s, p,
+ Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH,
+ SecureSettingsProto.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH);
+ dumpSetting(s, p,
+ Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
+ SecureSettingsProto.AUTOMATIC_STORAGE_MANAGER_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN,
+ SecureSettingsProto.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN);
+ dumpSetting(s, p,
+ Settings.Secure.AUTOMATIC_STORAGE_MANAGER_BYTES_CLEARED,
+ SecureSettingsProto.AUTOMATIC_STORAGE_MANAGER_BYTES_CLEARED);
+ dumpSetting(s, p,
+ Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN,
+ SecureSettingsProto.AUTOMATIC_STORAGE_MANAGER_LAST_RUN);
+ dumpSetting(s, p,
+ Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED,
+ SecureSettingsProto.SYSTEM_NAVIGATION_KEYS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.DOWNLOADS_BACKUP_ENABLED,
+ SecureSettingsProto.DOWNLOADS_BACKUP_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.DOWNLOADS_BACKUP_ALLOW_METERED,
+ SecureSettingsProto.DOWNLOADS_BACKUP_ALLOW_METERED);
+ dumpSetting(s, p,
+ Settings.Secure.DOWNLOADS_BACKUP_CHARGING_ONLY,
+ SecureSettingsProto.DOWNLOADS_BACKUP_CHARGING_ONLY);
+ dumpSetting(s, p,
+ Settings.Secure.AUTOMATIC_STORAGE_MANAGER_DOWNLOADS_DAYS_TO_RETAIN,
+ SecureSettingsProto.AUTOMATIC_STORAGE_MANAGER_DOWNLOADS_DAYS_TO_RETAIN);
+ dumpSetting(s, p,
+ Settings.Secure.QS_TILES,
+ SecureSettingsProto.QS_TILES);
+ dumpSetting(s, p,
+ Settings.Secure.DEMO_USER_SETUP_COMPLETE,
+ SecureSettingsProto.DEMO_USER_SETUP_COMPLETE);
+ dumpSetting(s, p,
+ Settings.Secure.WEB_ACTION_ENABLED,
+ SecureSettingsProto.WEB_ACTION_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.DEVICE_PAIRED,
+ SecureSettingsProto.DEVICE_PAIRED);
+ }
+
+ private static void dumpProtoSystemSettingsLocked(
+ @NonNull SettingsState s, @NonNull ProtoOutputStream p) {
+ dumpSetting(s, p,
+ Settings.System.END_BUTTON_BEHAVIOR,
+ SystemSettingsProto.END_BUTTON_BEHAVIOR);
+ dumpSetting(s, p,
+ Settings.System.ADVANCED_SETTINGS,
+ SystemSettingsProto.ADVANCED_SETTINGS);
+ dumpSetting(s, p,
+ Settings.System.BLUETOOTH_DISCOVERABILITY,
+ SystemSettingsProto.BLUETOOTH_DISCOVERABILITY);
+ dumpSetting(s, p,
+ Settings.System.BLUETOOTH_DISCOVERABILITY_TIMEOUT,
+ SystemSettingsProto.BLUETOOTH_DISCOVERABILITY_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.System.FONT_SCALE,
+ SystemSettingsProto.FONT_SCALE);
+ dumpSetting(s, p,
+ Settings.System.SYSTEM_LOCALES,
+ SystemSettingsProto.SYSTEM_LOCALES);
+ dumpSetting(s, p,
+ Settings.System.SCREEN_OFF_TIMEOUT,
+ SystemSettingsProto.SCREEN_OFF_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.System.SCREEN_BRIGHTNESS,
+ SystemSettingsProto.SCREEN_BRIGHTNESS);
+ dumpSetting(s, p,
+ Settings.System.SCREEN_BRIGHTNESS_FOR_VR,
+ SystemSettingsProto.SCREEN_BRIGHTNESS_FOR_VR);
+ dumpSetting(s, p,
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ SystemSettingsProto.SCREEN_BRIGHTNESS_MODE);
+ dumpSetting(s, p,
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+ SystemSettingsProto.SCREEN_AUTO_BRIGHTNESS_ADJ);
+ dumpSetting(s, p,
+ Settings.System.MODE_RINGER_STREAMS_AFFECTED,
+ SystemSettingsProto.MODE_RINGER_STREAMS_AFFECTED);
+ dumpSetting(s, p,
+ Settings.System.MUTE_STREAMS_AFFECTED,
+ SystemSettingsProto.MUTE_STREAMS_AFFECTED);
+ dumpSetting(s, p,
+ Settings.System.VIBRATE_ON,
+ SystemSettingsProto.VIBRATE_ON);
+ dumpSetting(s, p,
+ Settings.System.VIBRATE_INPUT_DEVICES,
+ SystemSettingsProto.VIBRATE_INPUT_DEVICES);
+ dumpSetting(s, p,
+ Settings.System.VOLUME_RING,
+ SystemSettingsProto.VOLUME_RING);
+ dumpSetting(s, p,
+ Settings.System.VOLUME_SYSTEM,
+ SystemSettingsProto.VOLUME_SYSTEM);
+ dumpSetting(s, p,
+ Settings.System.VOLUME_VOICE,
+ SystemSettingsProto.VOLUME_VOICE);
+ dumpSetting(s, p,
+ Settings.System.VOLUME_MUSIC,
+ SystemSettingsProto.VOLUME_MUSIC);
+ dumpSetting(s, p,
+ Settings.System.VOLUME_ALARM,
+ SystemSettingsProto.VOLUME_ALARM);
+ dumpSetting(s, p,
+ Settings.System.VOLUME_NOTIFICATION,
+ SystemSettingsProto.VOLUME_NOTIFICATION);
+ dumpSetting(s, p,
+ Settings.System.VOLUME_BLUETOOTH_SCO,
+ SystemSettingsProto.VOLUME_BLUETOOTH_SCO);
+ dumpSetting(s, p,
+ Settings.System.VOLUME_MASTER,
+ SystemSettingsProto.VOLUME_MASTER);
+ dumpSetting(s, p,
+ Settings.System.MASTER_MONO,
+ SystemSettingsProto.MASTER_MONO);
+ dumpSetting(s, p,
+ Settings.System.VIBRATE_IN_SILENT,
+ SystemSettingsProto.VIBRATE_IN_SILENT);
+ dumpSetting(s, p,
+ Settings.System.APPEND_FOR_LAST_AUDIBLE,
+ SystemSettingsProto.APPEND_FOR_LAST_AUDIBLE);
+ dumpSetting(s, p,
+ Settings.System.RINGTONE,
+ SystemSettingsProto.RINGTONE);
+ dumpSetting(s, p,
+ Settings.System.RINGTONE_CACHE,
+ SystemSettingsProto.RINGTONE_CACHE);
+ dumpSetting(s, p,
+ Settings.System.NOTIFICATION_SOUND,
+ SystemSettingsProto.NOTIFICATION_SOUND);
+ dumpSetting(s, p,
+ Settings.System.NOTIFICATION_SOUND_CACHE,
+ SystemSettingsProto.NOTIFICATION_SOUND_CACHE);
+ dumpSetting(s, p,
+ Settings.System.ALARM_ALERT,
+ SystemSettingsProto.ALARM_ALERT);
+ dumpSetting(s, p,
+ Settings.System.ALARM_ALERT_CACHE,
+ SystemSettingsProto.ALARM_ALERT_CACHE);
+ dumpSetting(s, p,
+ Settings.System.MEDIA_BUTTON_RECEIVER,
+ SystemSettingsProto.MEDIA_BUTTON_RECEIVER);
+ dumpSetting(s, p,
+ Settings.System.TEXT_AUTO_REPLACE,
+ SystemSettingsProto.TEXT_AUTO_REPLACE);
+ dumpSetting(s, p,
+ Settings.System.TEXT_AUTO_CAPS,
+ SystemSettingsProto.TEXT_AUTO_CAPS);
+ dumpSetting(s, p,
+ Settings.System.TEXT_AUTO_PUNCTUATE,
+ SystemSettingsProto.TEXT_AUTO_PUNCTUATE);
+ dumpSetting(s, p,
+ Settings.System.TEXT_SHOW_PASSWORD,
+ SystemSettingsProto.TEXT_SHOW_PASSWORD);
+ dumpSetting(s, p,
+ Settings.System.SHOW_GTALK_SERVICE_STATUS,
+ SystemSettingsProto.SHOW_GTALK_SERVICE_STATUS);
+ dumpSetting(s, p,
+ Settings.System.TIME_12_24,
+ SystemSettingsProto.TIME_12_24);
+ dumpSetting(s, p,
+ Settings.System.DATE_FORMAT,
+ SystemSettingsProto.DATE_FORMAT);
+ dumpSetting(s, p,
+ Settings.System.SETUP_WIZARD_HAS_RUN,
+ SystemSettingsProto.SETUP_WIZARD_HAS_RUN);
+ dumpSetting(s, p,
+ Settings.System.ACCELEROMETER_ROTATION,
+ SystemSettingsProto.ACCELEROMETER_ROTATION);
+ dumpSetting(s, p,
+ Settings.System.USER_ROTATION,
+ SystemSettingsProto.USER_ROTATION);
+ dumpSetting(s, p,
+ Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY,
+ SystemSettingsProto.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY);
+ dumpSetting(s, p,
+ Settings.System.VIBRATE_WHEN_RINGING,
+ SystemSettingsProto.VIBRATE_WHEN_RINGING);
+ dumpSetting(s, p,
+ Settings.System.DTMF_TONE_WHEN_DIALING,
+ SystemSettingsProto.DTMF_TONE_WHEN_DIALING);
+ dumpSetting(s, p,
+ Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
+ SystemSettingsProto.DTMF_TONE_TYPE_WHEN_DIALING);
+ dumpSetting(s, p,
+ Settings.System.HEARING_AID,
+ SystemSettingsProto.HEARING_AID);
+ dumpSetting(s, p,
+ Settings.System.TTY_MODE,
+ SystemSettingsProto.TTY_MODE);
+ dumpSetting(s, p,
+ Settings.System.SOUND_EFFECTS_ENABLED,
+ SystemSettingsProto.SOUND_EFFECTS_ENABLED);
+ dumpSetting(s, p,
+ Settings.System.HAPTIC_FEEDBACK_ENABLED,
+ SystemSettingsProto.HAPTIC_FEEDBACK_ENABLED);
+ dumpSetting(s, p,
+ Settings.System.NOTIFICATION_LIGHT_PULSE,
+ SystemSettingsProto.NOTIFICATION_LIGHT_PULSE);
+ dumpSetting(s, p,
+ Settings.System.POINTER_LOCATION,
+ SystemSettingsProto.POINTER_LOCATION);
+ dumpSetting(s, p,
+ Settings.System.SHOW_TOUCHES,
+ SystemSettingsProto.SHOW_TOUCHES);
+ dumpSetting(s, p,
+ Settings.System.WINDOW_ORIENTATION_LISTENER_LOG,
+ SystemSettingsProto.WINDOW_ORIENTATION_LISTENER_LOG);
+ dumpSetting(s, p,
+ Settings.System.LOCKSCREEN_SOUNDS_ENABLED,
+ SystemSettingsProto.LOCKSCREEN_SOUNDS_ENABLED);
+ dumpSetting(s, p,
+ Settings.System.LOCKSCREEN_DISABLED,
+ SystemSettingsProto.LOCKSCREEN_DISABLED);
+ dumpSetting(s, p,
+ Settings.System.SIP_RECEIVE_CALLS,
+ SystemSettingsProto.SIP_RECEIVE_CALLS);
+ dumpSetting(s, p,
+ Settings.System.SIP_CALL_OPTIONS,
+ SystemSettingsProto.SIP_CALL_OPTIONS);
+ dumpSetting(s, p,
+ Settings.System.SIP_ALWAYS,
+ SystemSettingsProto.SIP_ALWAYS);
+ dumpSetting(s, p,
+ Settings.System.SIP_ADDRESS_ONLY,
+ SystemSettingsProto.SIP_ADDRESS_ONLY);
+ dumpSetting(s, p,
+ Settings.System.POINTER_SPEED,
+ SystemSettingsProto.POINTER_SPEED);
+ dumpSetting(s, p,
+ Settings.System.LOCK_TO_APP_ENABLED,
+ SystemSettingsProto.LOCK_TO_APP_ENABLED);
+ dumpSetting(s, p,
+ Settings.System.EGG_MODE,
+ SystemSettingsProto.EGG_MODE);
+ dumpSetting(s, p,
+ Settings.System.WHEN_TO_MAKE_WIFI_CALLS,
+ SystemSettingsProto.WHEN_TO_MAKE_WIFI_CALLS);
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 527631e..6979995 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -17,6 +17,7 @@
package com.android.providers.settings;
import android.Manifest;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.backup.BackupManager;
@@ -65,6 +66,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
@@ -90,8 +92,9 @@
import java.util.regex.Pattern;
import static android.os.Process.ROOT_UID;
-import static android.os.Process.SYSTEM_UID;
import static android.os.Process.SHELL_UID;
+import static android.os.Process.SYSTEM_UID;
+
/**
* <p>
@@ -601,6 +604,22 @@
return cacheDir;
}
+ /**
+ * Dump all settings as a proto buf.
+ *
+ * @param fd The file to dump to
+ */
+ void dumpProto(@NonNull FileDescriptor fd) {
+ ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+ synchronized (mLock) {
+ SettingsProtoDumpUtil.dumpProtoLocked(mSettingsRegistry, proto);
+
+ }
+
+ proto.flush();
+ }
+
public void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mLock) {
final long identity = Binder.clearCallingIdentity();
@@ -663,7 +682,7 @@
pw.print(" value:"); pw.print(toDumpString(setting.getValue()));
if (setting.getDefaultValue() != null) {
pw.print(" default:"); pw.print(setting.getDefaultValue());
- pw.print(" defaultSystemSet:"); pw.print(setting.isDefaultSystemSet());
+ pw.print(" defaultSystemSet:"); pw.print(setting.isDefaultFromSystem());
}
if (setting.getTag() != null) {
pw.print(" tag:"); pw.print(setting.getTag());
@@ -2296,7 +2315,7 @@
Setting setting = settingsState.getSettingLocked(name);
if (!SettingsState.isSystemPackage(getContext(),
setting.getPackageName())) {
- if (setting.isDefaultSystemSet()) {
+ if (setting.isDefaultFromSystem()) {
if (settingsState.resetSettingLocked(name, packageName)) {
notifyForSettingsChange(key, name);
}
@@ -2310,7 +2329,7 @@
case Settings.RESET_MODE_TRUSTED_DEFAULTS: {
for (String name : settingsState.getSettingNamesLocked()) {
Setting setting = settingsState.getSettingLocked(name);
- if (setting.isDefaultSystemSet()) {
+ if (setting.isDefaultFromSystem()) {
if (settingsState.resetSettingLocked(name, packageName)) {
notifyForSettingsChange(key, name);
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
index fecc938..2d59324 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
@@ -65,6 +65,7 @@
}
int opti = 0;
+ boolean dumpAsProto = false;
while (opti < args.length) {
String opt = args[opti];
if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
@@ -74,16 +75,22 @@
if ("-h".equals(opt)) {
MyShellCommand.dumpHelp(pw, true);
return;
+ } else if ("--proto".equals(opt)) {
+ dumpAsProto = true;
} else {
pw.println("Unknown argument: " + opt + "; use -h for help");
}
}
- long caller = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
- mProvider.dumpInternal(fd, pw, args);
+ if (dumpAsProto) {
+ mProvider.dumpProto(fd);
+ } else {
+ mProvider.dumpInternal(fd, pw, args);
+ }
} finally {
- Binder.restoreCallingIdentity(caller);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -449,8 +456,9 @@
static void dumpHelp(PrintWriter pw, boolean dumping) {
if (dumping) {
pw.println("Settings provider dump options:");
- pw.println(" [-h]");
+ pw.println(" [-h] [--proto]");
pw.println(" -h: print this help.");
+ pw.println(" --proto: dump as protobuf.");
} else {
pw.println("Settings provider (settings) commands:");
pw.println(" help");
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 8f37b98..a74be35 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -16,6 +16,9 @@
package com.android.providers.settings;
+import static android.os.Process.FIRST_APPLICATION_UID;
+
+import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -30,6 +33,8 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.providers.settings.GlobalSettingsProto;
+import android.providers.settings.SettingsOperationProto;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AtomicFile;
@@ -38,10 +43,14 @@
import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.Xml;
+import android.util.proto.ProtoOutputStream;
+
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
+
import libcore.io.IoUtils;
import libcore.util.Objects;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -56,8 +65,6 @@
import java.util.ArrayList;
import java.util.List;
-import static android.os.Process.FIRST_APPLICATION_UID;
-
/**
* This class contains the state for one type of settings. It is responsible
* for saving the state asynchronously to an XML file after a mutation and
@@ -404,6 +411,38 @@
}
}
+ /**
+ * Dump historical operations as a proto buf.
+ *
+ * @param proto The proto buf stream to dump to
+ */
+ void dumpProtoHistoricalOperations(@NonNull ProtoOutputStream proto) {
+ synchronized (mLock) {
+ if (mHistoricalOperations == null) {
+ return;
+ }
+
+ final int operationCount = mHistoricalOperations.size();
+ for (int i = 0; i < operationCount; i++) {
+ int index = mNextHistoricalOpIdx - 1 - i;
+ if (index < 0) {
+ index = operationCount + index;
+ }
+ HistoricalOperation operation = mHistoricalOperations.get(index);
+ long settingsOperationToken = proto.start(GlobalSettingsProto.HISTORICAL_OP);
+ proto.write(SettingsOperationProto.TIMESTAMP, operation.mTimestamp);
+ proto.write(SettingsOperationProto.OPERATION, operation.mOperation);
+ if (operation.mSetting != null) {
+ // Only add the name of the setting, since we don't know the historical package
+ // and values for it so they would be misleading to add here (all we could
+ // add is what the current data is).
+ proto.write(SettingsOperationProto.SETTING, operation.mSetting.getName());
+ }
+ proto.end(settingsOperationToken);
+ }
+ }
+ }
+
public void dumpHistoricalOperations(PrintWriter pw) {
synchronized (mLock) {
if (mHistoricalOperations == null) {
@@ -544,7 +583,7 @@
writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(),
setting.getValue(), setting.getDefaultValue(), setting.getPackageName(),
- setting.getTag(), setting.isDefaultSystemSet());
+ setting.getTag(), setting.isDefaultFromSystem());
if (DEBUG_PERSISTENCE) {
Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "=" + setting.getValue());
@@ -763,7 +802,7 @@
private String id;
private String tag;
// Whether the default is set by the system
- private boolean defaultSystemSet;
+ private boolean defaultFromSystem;
public Setting(Setting other) {
name = other.name;
@@ -771,7 +810,7 @@
defaultValue = other.defaultValue;
packageName = other.packageName;
id = other.id;
- defaultSystemSet = other.defaultSystemSet;
+ defaultFromSystem = other.defaultFromSystem;
tag = other.tag;
}
@@ -798,7 +837,7 @@
this.defaultValue = defaultValue;
this.packageName = packageName;
this.id = id;
- this.defaultSystemSet = fromSystem;
+ this.defaultFromSystem = fromSystem;
}
public String getName() {
@@ -825,8 +864,8 @@
return packageName;
}
- public boolean isDefaultSystemSet() {
- return defaultSystemSet;
+ public boolean isDefaultFromSystem() {
+ return defaultFromSystem;
}
public String getId() {
@@ -854,22 +893,22 @@
}
String defaultValue = this.defaultValue;
- boolean defaultSystemSet = this.defaultSystemSet;
+ boolean defaultFromSystem = this.defaultFromSystem;
if (setDefault) {
if (!Objects.equal(value, this.defaultValue)
- && (!defaultSystemSet || callerSystem)) {
+ && (!defaultFromSystem || callerSystem)) {
defaultValue = value;
// Default null means no default, so the tag is irrelevant
// since it is used to reset a settings subset their defaults.
// Also it is irrelevant if the system set the canonical default.
if (defaultValue == null) {
tag = null;
- defaultSystemSet = false;
+ defaultFromSystem = false;
}
}
- if (!defaultSystemSet && value != null) {
+ if (!defaultFromSystem && value != null) {
if (callerSystem) {
- defaultSystemSet = true;
+ defaultFromSystem = true;
}
}
}
@@ -879,11 +918,11 @@
&& Objects.equal(defaultValue, this.defaultValue)
&& Objects.equal(packageName, this.packageName)
&& Objects.equal(tag, this.tag)
- && defaultSystemSet == this.defaultSystemSet) {
+ && defaultFromSystem == this.defaultFromSystem) {
return false;
}
- init(name, value, tag, defaultValue, packageName, defaultSystemSet,
+ init(name, value, tag, defaultValue, packageName, defaultFromSystem,
String.valueOf(mNextId++));
return true;
}
@@ -892,7 +931,7 @@
return "Setting{name=" + name + " value=" + value
+ (defaultValue != null ? " default=" + defaultValue : "")
+ " packageName=" + packageName + " tag=" + tag
- + " defaultSystemSet=" + defaultSystemSet + "}";
+ + " defaultFromSystem=" + defaultFromSystem + "}";
}
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index fede34d..f72d091 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -131,6 +131,9 @@
<!-- Assist -->
<uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
+ <!-- Doze mode temp whitelisting for notification dispatching. -->
+ <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
+
<!-- Listen for keyboard attachment / detachment -->
<uses-permission android:name="android.permission.TABLET_MODE" />
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java
index a9d1fa9..152dbc5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginFragment.java
@@ -14,17 +14,13 @@
package com.android.systemui.plugins;
-import android.annotation.Nullable;
import android.app.Fragment;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.view.LayoutInflater;
public abstract class PluginFragment extends Fragment implements Plugin {
- private static final String KEY_PLUGIN_PACKAGE = "plugin_package_name";
private Context mPluginContext;
@Override
@@ -33,45 +29,17 @@
}
@Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null) {
- Context sysuiContext = getContext();
- Context pluginContext = recreatePluginContext(sysuiContext, savedInstanceState);
- onCreate(sysuiContext, pluginContext);
- }
- if (mPluginContext == null) {
- throw new RuntimeException("PluginFragments must call super.onCreate("
- + "Context sysuiContext, Context pluginContext)");
- }
+ public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
+ return super.getLayoutInflater(savedInstanceState).cloneInContext(getContext());
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- outState.putString(KEY_PLUGIN_PACKAGE, getContext().getPackageName());
}
- private Context recreatePluginContext(Context sysuiContext, Bundle savedInstanceState) {
- final String pkg = savedInstanceState.getString(KEY_PLUGIN_PACKAGE);
- try {
- ApplicationInfo appInfo = sysuiContext.getPackageManager().getApplicationInfo(pkg, 0);
- return PluginManager.getInstance(sysuiContext).getContext(appInfo, pkg);
- } catch (NameNotFoundException e) {
- throw new RuntimeException("Plugin with invalid package? " + pkg, e);
- }
- }
-
- @Override
- public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
- return super.getLayoutInflater(savedInstanceState).cloneInContext(mPluginContext);
- }
-
- /**
- * Should only be called after {@link Plugin#onCreate(Context, Context)}.
- */
@Override
public Context getContext() {
- return mPluginContext != null ? mPluginContext : super.getContext();
+ return mPluginContext;
}
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
index 47b97bd..9f44bd4 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
@@ -177,8 +177,12 @@
if (DEBUG) Log.d(TAG, "onPluginConnected");
PluginPrefs.setHasPlugins(mContext);
PluginInfo<T> info = (PluginInfo<T>) msg.obj;
- info.mPlugin.onCreate(mContext, info.mPluginContext);
- mListener.onPluginConnected(info.mPlugin);
+ if (!(msg.obj instanceof PluginFragment)) {
+ // Only call onDestroy for plugins that aren't fragments, as fragments
+ // will get the onCreate as part of the fragment lifecycle.
+ info.mPlugin.onCreate(mContext, info.mPluginContext);
+ }
+ mListener.onPluginConnected(info.mPlugin, info.mPluginContext);
break;
case PLUGIN_DISCONNECTED:
if (DEBUG) Log.d(TAG, "onPluginDisconnected");
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java
index b2f92d6..b488d2a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java
@@ -14,6 +14,8 @@
package com.android.systemui.plugins;
+import android.content.Context;
+
/**
* Interface for listening to plugins being connected.
*/
@@ -24,7 +26,7 @@
* It may also be called in the future if the plugin package changes
* and needs to be reloaded.
*/
- void onPluginConnected(T plugin);
+ void onPluginConnected(T plugin, Context pluginContext);
/**
* Called when a plugin has been uninstalled/updated and should be removed
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index a9874fc..e21a282 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -37,7 +37,7 @@
// This should be incremented any time this class or ActivityStarter or BaseStatusBarHeader
// change in incompatible ways.
- public static final int VERSION = 4;
+ public static final int VERSION = 5;
String TAG = "QS";
@@ -105,24 +105,8 @@
public abstract void setExpansion(float headerExpansionFraction);
public abstract void setListening(boolean listening);
public abstract void updateEverything();
- public abstract void setActivityStarter(ActivityStarter activityStarter);
public abstract void setCallback(Callback qsPanelCallback);
public abstract View getExpandView();
}
- /**
- * An interface to start activities. This is used to as a callback from the views to
- * {@link PhoneStatusBar} to allow custom handling for starting the activity, i.e. dismissing the
- * Keyguard.
- */
- public static interface ActivityStarter {
-
- void startPendingIntentDismissingKeyguard(PendingIntent intent);
- void startActivity(Intent intent, boolean dismissShade);
- void startActivity(Intent intent, boolean dismissShade, Callback callback);
-
- interface Callback {
- void onActivityStarted(int resultCode);
- }
- }
}
diff --git a/packages/SystemUI/res/drawable/ic_volume_accessibility.xml b/packages/SystemUI/res/drawable/ic_volume_accessibility.xml
new file mode 100644
index 0000000..657efaa
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_volume_accessibility.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="32dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M20.5,6c-2.61,0.7 -5.67,1 -8.5,1s-5.89,-0.3 -8.5,-1L3,8c1.86,0.5 4,0.83 6,1v13h2v-6h2v6h2V9c2,-0.17 4.14,-0.5 6,-1l-0.5,-2zM12,6c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index b18b6ac..3040814 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 ac86439..d6ed9d8 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/ActivityStarter.java b/packages/SystemUI/src/com/android/systemui/ActivityStarter.java
new file mode 100644
index 0000000..a4d8a10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ActivityStarter.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+
+/**
+ * An interface to start activities. This is used as a callback from the views to
+ * {@link PhoneStatusBar} to allow custom handling for starting the activity, i.e. dismissing the
+ * Keyguard.
+ */
+public interface ActivityStarter {
+
+ void startPendingIntentDismissingKeyguard(PendingIntent intent);
+ void startActivity(Intent intent, boolean dismissShade);
+ void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade);
+ void startActivity(Intent intent, boolean dismissShade, Callback callback);
+ void postStartActivityDismissingKeyguard(Intent intent, int delay);
+ void postStartActivityDismissingKeyguard(PendingIntent intent);
+ void postQSRunnableDismissingKeyguard(Runnable runnable);
+
+ interface Callback {
+ void onActivityStarted(int resultCode);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
new file mode 100644
index 0000000..4ae81a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+
+/**
+ * Single common instance of ActivityStarter that can be gotten and referenced from anywhere, but
+ * delegates to an actual implementation such as PhoneStatusBar, assuming it exists.
+ */
+public class ActivityStarterDelegate implements ActivityStarter {
+
+ private ActivityStarter mActualStarter;
+
+ @Override
+ public void startPendingIntentDismissingKeyguard(PendingIntent intent) {
+ if (mActualStarter == null) return;
+ mActualStarter.startPendingIntentDismissingKeyguard(intent);
+ }
+
+ @Override
+ public void startActivity(Intent intent, boolean dismissShade) {
+ if (mActualStarter == null) return;
+ mActualStarter.startActivity(intent, dismissShade);
+ }
+
+ @Override
+ public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade) {
+ if (mActualStarter == null) return;
+ mActualStarter.startActivity(intent, onlyProvisioned, dismissShade);
+ }
+
+ @Override
+ public void startActivity(Intent intent, boolean dismissShade, Callback callback) {
+ if (mActualStarter == null) return;
+ mActualStarter.startActivity(intent, dismissShade, callback);
+ }
+
+ @Override
+ public void postStartActivityDismissingKeyguard(Intent intent, int delay) {
+ if (mActualStarter == null) return;
+ mActualStarter.postStartActivityDismissingKeyguard(intent, delay);
+ }
+
+ @Override
+ public void postStartActivityDismissingKeyguard(PendingIntent intent) {
+ if (mActualStarter == null) return;
+ mActualStarter.postStartActivityDismissingKeyguard(intent);
+ }
+
+ @Override
+ public void postQSRunnableDismissingKeyguard(Runnable runnable) {
+ if (mActualStarter == null) return;
+ mActualStarter.postQSRunnableDismissingKeyguard(runnable);
+ }
+
+ public void setActivityStarterImpl(ActivityStarter starter) {
+ mActualStarter = starter;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 030250a..b30b596 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -72,6 +72,10 @@
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
+ mBatteryController = Dependency.get(BatteryController.class);
+ mDrawable.setBatteryController(mBatteryController);
+ mBatteryController.addCallback(this);
+ mDrawable.startListening();
TunerService.get(getContext()).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
}
@@ -95,13 +99,6 @@
}
- public void setBatteryController(BatteryController mBatteryController) {
- this.mBatteryController = mBatteryController;
- mDrawable.setBatteryController(mBatteryController);
- mBatteryController.addCallback(this);
- mDrawable.startListening();
- }
-
public void setDarkIntensity(float f) {
mDrawable.setDarkIntensity(f);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ConfigurationChangedReceiver.java b/packages/SystemUI/src/com/android/systemui/ConfigurationChangedReceiver.java
new file mode 100644
index 0000000..4fba640
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ConfigurationChangedReceiver.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.content.res.Configuration;
+
+public interface ConfigurationChangedReceiver {
+ void onConfigurationChanged(Configuration newConfiguration);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
new file mode 100644
index 0000000..135b129
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.statusbar.phone.ManagedProfileController;
+import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
+import com.android.systemui.statusbar.policy.AccessibilityController;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BatteryControllerImpl;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.CastControllerImpl;
+import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
+import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.FlashlightControllerImpl;
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.HotspotControllerImpl;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.LocationControllerImpl;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.SecurityControllerImpl;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Class to handle ugly dependencies throughout sysui until we determine the
+ * long-term dependency injection solution.
+ *
+ * Classes added here should be things that are expected to live the lifetime of sysui,
+ * and are generally applicable to many parts of sysui. They will be lazily
+ * initialized to ensure they aren't created on form factors that don't need them
+ * (e.g. HotspotController on TV). Despite being lazily initialized, it is expected
+ * that all dependencies will be gotten during sysui startup, and not during runtime
+ * to avoid jank.
+ *
+ * All classes used here are expected to manage their own lifecycle, meaning if
+ * they have no clients they should not have any registered resources like bound
+ * services, registered receivers, etc.
+ */
+public class Dependency extends SystemUI {
+
+ /**
+ * Key for getting a background Looper for background work.
+ */
+ public static final String BG_LOOPER = "background_loooper";
+ /**
+ * Key for getting a Handler for receiving time tick broadcasts on.
+ */
+ public static final String TIME_TICK_HANDLER = "time_tick_handler";
+ /**
+ * Generic handler on the main thread.
+ */
+ public static final String MAIN_HANDLER = "main_handler";
+
+ private final ArrayMap<String, Object> mDependencies = new ArrayMap<>();
+ private final ArrayMap<String, DependencyProvider> mProviders = new ArrayMap<>();
+
+ @Override
+ public void start() {
+ sDependency = this;
+ // TODO: Think about ways to push these creation rules out of Dependency to cut down
+ // on imports.
+ mProviders.put(TIME_TICK_HANDLER, () -> {
+ HandlerThread thread = new HandlerThread("TimeTick");
+ thread.start();
+ return new Handler(thread.getLooper());
+ });
+ mProviders.put(BG_LOOPER, () -> {
+ HandlerThread thread = new HandlerThread("SysUiBg",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+ return thread.getLooper();
+ });
+ mProviders.put(MAIN_HANDLER, () -> new Handler(Looper.getMainLooper()));
+ mProviders.put(ActivityStarter.class.getName(), () -> new ActivityStarterDelegate());
+ mProviders.put(ActivityStarterDelegate.class.getName(), () ->
+ getDependency(ActivityStarter.class));
+
+ mProviders.put(BluetoothController.class.getName(), () ->
+ new BluetoothControllerImpl(mContext, getDependency(BG_LOOPER)));
+
+ mProviders.put(LocationController.class.getName(), () ->
+ new LocationControllerImpl(mContext, getDependency(BG_LOOPER)));
+
+ mProviders.put(RotationLockController.class.getName(), () ->
+ new RotationLockControllerImpl(mContext));
+
+ mProviders.put(NetworkController.class.getName(), () ->
+ new NetworkControllerImpl(mContext, getDependency(BG_LOOPER),
+ getDependency(DeviceProvisionedController.class)));
+
+ mProviders.put(ZenModeController.class.getName(), () ->
+ new ZenModeControllerImpl(mContext, getDependency(MAIN_HANDLER)));
+
+ mProviders.put(HotspotController.class.getName(), () ->
+ new HotspotControllerImpl(mContext));
+
+ mProviders.put(CastController.class.getName(), () ->
+ new CastControllerImpl(mContext));
+
+ mProviders.put(FlashlightController.class.getName(), () ->
+ new FlashlightControllerImpl(mContext));
+
+ mProviders.put(KeyguardMonitor.class.getName(), () ->
+ new KeyguardMonitorImpl(mContext));
+
+ mProviders.put(UserSwitcherController.class.getName(), () ->
+ new UserSwitcherController(mContext, getDependency(KeyguardMonitor.class),
+ getDependency(MAIN_HANDLER), getDependency(ActivityStarter.class)));
+
+ mProviders.put(UserInfoController.class.getName(), () ->
+ new UserInfoControllerImpl(mContext));
+
+ mProviders.put(BatteryController.class.getName(), () ->
+ new BatteryControllerImpl(mContext));
+
+ mProviders.put(ManagedProfileController.class.getName(), () ->
+ new ManagedProfileControllerImpl(mContext));
+
+ mProviders.put(NextAlarmController.class.getName(), () ->
+ new NextAlarmControllerImpl(mContext));
+
+ mProviders.put(DataSaverController.class.getName(), () ->
+ get(NetworkController.class).getDataSaverController());
+
+ mProviders.put(AccessibilityController.class.getName(), () ->
+ new AccessibilityController(mContext));
+
+ mProviders.put(DeviceProvisionedController.class.getName(), () ->
+ new DeviceProvisionedControllerImpl(mContext));
+
+ mProviders.put(AssistManager.class.getName(), () ->
+ new AssistManager(getDependency(DeviceProvisionedController.class), mContext));
+
+ mProviders.put(SecurityController.class.getName(), () ->
+ new SecurityControllerImpl(mContext));
+
+ // Put all dependencies above here so the factory can override them if it wants.
+ SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+ pw.println("Dumping existing controllers:");
+ mDependencies.values().stream().filter(obj -> obj instanceof Dumpable)
+ .forEach(o -> ((Dumpable) o).dump(fd, pw, args));
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDependencies.values().stream().filter(obj -> obj instanceof ConfigurationChangedReceiver)
+ .forEach(o -> ((ConfigurationChangedReceiver) o).onConfigurationChanged(newConfig));
+ }
+
+ protected final <T> T getDependency(Class<T> cls) {
+ return getDependency(cls.getName());
+ }
+
+ protected final <T> T getDependency(String cls) {
+ T obj = (T) mDependencies.get(cls);
+ if (obj == null) {
+ obj = createDependency(cls);
+ mDependencies.put(cls, obj);
+ }
+ return obj;
+ }
+
+ @VisibleForTesting
+ protected <T> T createDependency(String cls) {
+ DependencyProvider<T> provider = mProviders.get(cls);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unsupported dependency " + cls);
+ }
+ return provider.createDependency();
+ }
+
+ private static Dependency sDependency;
+
+ public interface DependencyProvider<T> {
+ T createDependency();
+ }
+
+ public static <T> T get(Class<T> cls) {
+ return sDependency.getDependency(cls.getName());
+ }
+
+ public static <T> T get(String cls) {
+ return sDependency.getDependency(cls);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dumpable.java b/packages/SystemUI/src/com/android/systemui/Dumpable.java
new file mode 100644
index 0000000..65a6844
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/Dumpable.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+public interface Dumpable {
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
index efa7cae..efddf20 100644
--- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
@@ -98,7 +98,7 @@
}
@Override
- public void onPluginConnected(ViewProvider plugin) {
+ public void onPluginConnected(ViewProvider plugin, Context context) {
mPluginView = plugin.getView();
inflateLayout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/SysUiServiceProvider.java b/packages/SystemUI/src/com/android/systemui/SysUiServiceProvider.java
index c4470cd..ff4b7cb 100644
--- a/packages/SystemUI/src/com/android/systemui/SysUiServiceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/SysUiServiceProvider.java
@@ -14,10 +14,16 @@
package com.android.systemui;
+import android.content.Context;
+
/**
* The interface for getting core components of SysUI. Exists for Testability
* since tests don't have SystemUIApplication as their ApplicationContext.
*/
public interface SysUiServiceProvider {
<T> T getComponent(Class<T> interfaceType);
+
+ public static <T> T getComponent(Context context, Class<T> interfaceType) {
+ return ((SysUiServiceProvider) context.getApplicationContext()).getComponent(interfaceType);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index bd4e3dc..f2aaec1 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -29,7 +29,6 @@
import android.os.UserHandle;
import android.util.Log;
-import com.android.systemui.doze.DozeFactory;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyboard.KeyboardUI;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -64,6 +63,7 @@
* The classes of the stuff to start.
*/
private final Class<?>[] SERVICES = new Class[] {
+ Dependency.class,
FragmentService.class,
TunerService.class,
CommandQueue.CommandQueueStart.class,
@@ -207,7 +207,7 @@
PluginManager.getInstance(this).addPluginListener(OverlayPlugin.ACTION,
new PluginListener<OverlayPlugin>() {
@Override
- public void onPluginConnected(OverlayPlugin plugin) {
+ public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) {
PhoneStatusBar phoneStatusBar = getComponent(PhoneStatusBar.class);
if (phoneStatusBar != null) {
plugin.setup(phoneStatusBar.getStatusBarWindow(),
@@ -239,8 +239,4 @@
public SystemUI[] getServices() {
return mServices;
}
-
- public static <T> T getComponent(Context context, Class<T> interfaceType) {
- return ((SysUiServiceProvider) context.getApplicationContext()).getComponent(interfaceType);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 10328a4..ec11812 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -18,19 +18,23 @@
import android.content.ComponentName;
import android.content.Context;
+import android.util.ArrayMap;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.Dependency.DependencyProvider;
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;
@@ -113,25 +117,20 @@
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,
- BluetoothController bluetooth, LocationController location,
- RotationLockController rotation, NetworkController network,
- ZenModeController zen, HotspotController hotspot,
- CastController cast, FlashlightController flashlight,
- UserSwitcherController userSwitcher, UserInfoController userInfo,
- KeyguardMonitor keyguard, SecurityController security,
- BatteryController battery, StatusBarIconController iconController,
- NextAlarmController nextAlarmController) {
- return new QSTileHost(context, statusBar, bluetooth, location, rotation, network, zen,
- hotspot, cast, flashlight, userSwitcher, userInfo, keyguard, security, battery,
- iconController, nextAlarmController);
+ StatusBarIconController iconController) {
+ return new QSTileHost(context, statusBar, iconController);
}
public <T> T createInstance(Class<T> classType) {
return null;
}
- public AssistManager createAssistManager(BaseStatusBar bar, Context context) {
- return new AssistManager(bar, context);
- }
+ public void injectDependencies(ArrayMap<String, DependencyProvider> providers,
+ Context context) { }
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 2576bb7..09fdf5a 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -9,14 +9,15 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
@@ -33,14 +34,17 @@
import com.android.internal.app.IVoiceInteractionSessionListener;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.settingslib.applications.InterestingConfigChanges;
+import com.android.systemui.ConfigurationChangedReceiver;
import com.android.systemui.R;
-import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
/**
* Class to manage everything related to assist in SystemUI.
*/
-public class AssistManager {
+public class AssistManager implements ConfigurationChangedReceiver {
private static final String TAG = "AssistManager";
private static final String ASSIST_ICON_METADATA_NAME =
@@ -52,9 +56,10 @@
protected final Context mContext;
private final WindowManager mWindowManager;
private final AssistDisclosure mAssistDisclosure;
+ private final InterestingConfigChanges mInterestingConfigChanges;
private AssistOrbContainer mView;
- private final BaseStatusBar mBar;
+ private final DeviceProvisionedController mDeviceProvisionedController;
protected final AssistUtils mAssistUtils;
private IVoiceInteractionSessionShowCallback mShowCallback =
@@ -79,14 +84,16 @@
}
};
- public AssistManager(BaseStatusBar bar, Context context) {
+ public AssistManager(DeviceProvisionedController controller, Context context) {
mContext = context;
- mBar = bar;
+ mDeviceProvisionedController = controller;
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mAssistUtils = new AssistUtils(context);
mAssistDisclosure = new AssistDisclosure(context, new Handler());
registerVoiceInteractionSessionListener();
+ mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION);
+ onConfigurationChanged(context.getResources().getConfiguration());
}
protected void registerVoiceInteractionSessionListener() {
@@ -104,7 +111,10 @@
});
}
- public void onConfigurationChanged() {
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ if (!mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
+ return;
+ }
boolean visible = false;
if (mView != null) {
visible = mView.isShowing();
@@ -183,13 +193,13 @@
}
private void startAssistActivity(Bundle args, @NonNull ComponentName assistComponent) {
- if (!mBar.isDeviceProvisioned()) {
+ if (!mDeviceProvisionedController.isDeviceProvisioned()) {
return;
}
// Close Recent Apps if needed
- mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL |
- CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL);
+ SysUiServiceProvider.getComponent(mContext, CommandQueue.class).animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL | CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL);
boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index 57857cc..50506a9 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -27,11 +27,13 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Parcelable;
+import android.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.View;
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.SystemUIApplication;
+import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginManager;
import java.io.FileDescriptor;
@@ -47,6 +49,7 @@
private final View mRootView;
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges();
private final FragmentService mManager;
+ private final PluginFragmentManager mPlugins = new PluginFragmentManager();
private FragmentController mFragments;
private FragmentLifecycleCallbacks mLifecycleCallbacks;
@@ -163,6 +166,10 @@
return mFragments.getFragmentManager();
}
+ PluginFragmentManager getPluginManager() {
+ return mPlugins;
+ }
+
public interface FragmentListener {
void onFragmentViewCreated(String tag, Fragment fragment);
@@ -198,6 +205,11 @@
}
@Override
+ public Fragment instantiate(Context context, String className, Bundle arguments) {
+ return mPlugins.instantiate(context, className, arguments);
+ }
+
+ @Override
public boolean onShouldSaveFragmentState(Fragment fragment) {
return true; // True for now.
}
@@ -237,4 +249,57 @@
return true;
}
}
+
+ class PluginFragmentManager {
+ private final ArrayMap<String, Context> mPluginLookup = new ArrayMap<>();
+
+ public void removePlugin(String tag, String currentClass, String defaultClass) {
+ Fragment fragment = getFragmentManager().findFragmentByTag(tag);
+ mPluginLookup.remove(currentClass);
+ getFragmentManager().beginTransaction()
+ .replace(((View) fragment.getView().getParent()).getId(),
+ instantiate(mContext, defaultClass, null), tag)
+ .commit();
+ reloadFragments();
+ }
+
+ public void setCurrentPlugin(String tag, String currentClass, Context context) {
+ Fragment fragment = getFragmentManager().findFragmentByTag(tag);
+ mPluginLookup.put(currentClass, context);
+ getFragmentManager().beginTransaction()
+ .replace(((View) fragment.getView().getParent()).getId(),
+ instantiate(context, currentClass, null), tag)
+ .commit();
+ reloadFragments();
+ }
+
+ private void reloadFragments() {
+ // Save the old state.
+ Parcelable p = destroyFragmentHost();
+ // Generate a new fragment host and restore its state.
+ createFragmentHost(p);
+ }
+
+ Fragment instantiate(Context context, String className, Bundle arguments) {
+ Context pluginContext = mPluginLookup.get(className);
+ if (pluginContext != null) {
+ Fragment f = Fragment.instantiate(pluginContext, className, arguments);
+ if (f instanceof Plugin) {
+ ((Plugin) f).onCreate(mContext, pluginContext);
+ }
+ return f;
+ }
+ return Fragment.instantiate(context, className, arguments);
+ }
+ }
+
+ private static class PluginState {
+ Context mContext;
+ String mCls;
+
+ private PluginState(String cls, Context context) {
+ mCls = cls;
+ mContext = context;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
index e107fcd..2e6de4a 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
@@ -15,6 +15,7 @@
package com.android.systemui.fragments;
import android.app.Fragment;
+import android.content.Context;
import android.util.Log;
import android.view.View;
@@ -30,27 +31,19 @@
private final FragmentHostManager mFragmentHostManager;
private final PluginManager mPluginManager;
private final Class<? extends Fragment> mDefaultClass;
- private final int mId;
- private final String mTag;
private final Class<? extends FragmentBase> mExpectedInterface;
+ private final String mTag;
- public PluginFragmentListener(View view, String tag, int id,
- Class<? extends Fragment> defaultFragment,
+ public PluginFragmentListener(View view, String tag, Class<? extends Fragment> defaultFragment,
Class<? extends FragmentBase> expectedInterface) {
+ mTag = tag;
mFragmentHostManager = FragmentHostManager.get(view);
mPluginManager = PluginManager.getInstance(view.getContext());
mExpectedInterface = expectedInterface;
- mTag = tag;
mDefaultClass = defaultFragment;
- mId = id;
}
public void startListening(String action, int version) {
- try {
- setFragment(mDefaultClass.newInstance());
- } catch (InstantiationException | IllegalAccessException e) {
- Log.e(TAG, "Couldn't instantiate " + mDefaultClass.getName(), e);
- }
mPluginManager.addPluginListener(action, this, version, false /* Only allow one */);
}
@@ -58,17 +51,13 @@
mPluginManager.removePluginListener(this);
}
- private void setFragment(Fragment fragment) {
- mFragmentHostManager.getFragmentManager().beginTransaction()
- .replace(mId, fragment, mTag)
- .commit();
- }
-
@Override
- public void onPluginConnected(Plugin plugin) {
+ public void onPluginConnected(Plugin plugin, Context pluginContext) {
try {
mExpectedInterface.cast(plugin);
- setFragment((Fragment) plugin);
+ Fragment.class.cast(plugin);
+ mFragmentHostManager.getPluginManager().setCurrentPlugin(mTag,
+ plugin.getClass().getName(), pluginContext);
} catch (ClassCastException e) {
Log.e(TAG, plugin.getClass().getName() + " must be a Fragment and implement "
+ mExpectedInterface.getName(), e);
@@ -77,10 +66,7 @@
@Override
public void onPluginDisconnected(Plugin plugin) {
- try {
- setFragment(mDefaultClass.newInstance());
- } catch (InstantiationException | IllegalAccessException e) {
- Log.e(TAG, "Couldn't instantiate " + mDefaultClass.getName(), e);
- }
+ mFragmentHostManager.getPluginManager().removePlugin(mTag,
+ plugin.getClass().getName(), mDefaultClass.getName());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 3103267..3df557d 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 @@
// 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 2284013..d96baa6 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 @@
*/
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 9c03830..a8cdd1b 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 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 @@
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 28ca6a3..1d4a5c7 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.BatteryManager;
import android.os.Handler;
@@ -221,11 +222,15 @@
};
private void initTemperatureWarning() {
- if (!mContext.getResources().getBoolean(R.bool.config_showTemperatureWarning)) {
+ ContentResolver resolver = mContext.getContentResolver();
+ Resources resources = mContext.getResources();
+ if (Settings.Global.getInt(resolver, Settings.Global.SHOW_TEMPERATURE_WARNING,
+ resources.getInteger(R.integer.config_showTemperatureWarning)) == 0) {
return;
}
- mThrottlingTemp = mContext.getResources().getInteger(R.integer.config_warningTemperature);
+ mThrottlingTemp = Settings.Global.getFloat(resolver, Settings.Global.WARNING_TEMPERATURE,
+ resources.getInteger(R.integer.config_warningTemperature));
if (mThrottlingTemp < 0f) {
// Get the throttling temperature. No need to check if we're not throttling.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 5027144..a20b7ba 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -33,11 +33,15 @@
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
+import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.BaseStatusBarHeader;
import com.android.systemui.plugins.qs.QS.Callback;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.QSTileHost;
public class QSDetail extends LinearLayout {
@@ -160,7 +164,8 @@
setupDetailHeader(adapter);
if (toggleQs && !mFullyExpanded) {
mTriggeredExpand = true;
- mHost.animateToggleQSExpansion();
+ SysUiServiceProvider.getComponent(mContext, CommandQueue.class)
+ .animateExpandSettingsPanel(null);
} else {
mTriggeredExpand = false;
}
@@ -171,7 +176,8 @@
x = mOpenX;
y = mOpenY;
if (toggleQs && mTriggeredExpand) {
- mHost.animateToggleQSExpansion();
+ SysUiServiceProvider.getComponent(mContext, CommandQueue.class)
+ .animateCollapsePanels();
mTriggeredExpand = false;
}
}
@@ -231,12 +237,8 @@
protected void setupDetailFooter(DetailAdapter adapter) {
final Intent settingsIntent = adapter.getSettingsIntent();
mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
- mDetailSettingsButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mHost.startActivityDismissingKeyguard(settingsIntent);
- }
- });
+ mDetailSettingsButton.setOnClickListener(v -> Dependency.get(ActivityStarter.class)
+ .postStartActivityDismissingKeyguard(settingsIntent, 0));
}
protected void setupDetailHeader(final DetailAdapter adapter) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index fb3b1d9..0bf3f15 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -19,11 +19,9 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.UserHandle;
import android.provider.Settings;
import android.text.SpannableStringBuilder;
import android.text.method.LinkMovementMethod;
@@ -33,12 +31,13 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
-import android.view.Window;
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.systemui.Dependency;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.SecurityController;
@@ -55,12 +54,13 @@
private final ImageView mFooterIcon2;
private final Context mContext;
private final Callback mCallback = new Callback();
+ private final SecurityController mSecurityController;
+ private final ActivityStarter mActivityStarter;
+ private final Handler mMainHandler;
- private SecurityController mSecurityController;
private AlertDialog mDialog;
private QSTileHost mHost;
protected Handler mHandler;
- private final Handler mMainHandler;
private boolean mIsVisible;
private boolean mIsIconVisible;
@@ -81,13 +81,13 @@
mFooterIcon2Id = R.drawable.ic_qs_network_logging;
mContext = context;
mMainHandler = new Handler(Looper.getMainLooper());
+ mActivityStarter = Dependency.get(ActivityStarter.class);
+ mSecurityController = Dependency.get(SecurityController.class);
+ mHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
}
- public void setHostEnvironment(QSTileHost host, SecurityController securityController,
- Looper looper) {
+ public void setHostEnvironment(QSTileHost host) {
mHost = host;
- mSecurityController = securityController;
- mHandler = new H(looper);
}
public void setListening(boolean listening) {
@@ -173,7 +173,7 @@
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_NEGATIVE) {
final Intent settingsIntent = new Intent(ACTION_VPN_SETTINGS);
- mHost.startActivityDismissingKeyguard(settingsIntent);
+ mActivityStarter.postStartActivityDismissingKeyguard(settingsIntent, 0);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 2de32cc..e004828 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -190,12 +190,11 @@
mHost = host;
mHost.addCallback(this);
setTiles(mHost.getTiles());
- mFooter.setHostEnvironment(host, host.getSecurityController(), host.getLooper());
+ mFooter.setHostEnvironment(host);
mCustomizePanel = customizer;
if (mCustomizePanel != null) {
mCustomizePanel.setHost(mHost);
}
- mBrightnessController.setBackgroundLooper(host.getLooper());
}
public QSTileHost getHost() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index dad37b0..e18654e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -17,7 +17,6 @@
package com.android.systemui.qs;
import android.app.ActivityManager;
-import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -32,22 +31,11 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSTile.State;
import com.android.systemui.qs.external.TileServices;
-import com.android.systemui.statusbar.phone.ManagedProfileController;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.FlashlightController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.ZenModeController;
import java.util.ArrayList;
import java.util.Collection;
@@ -100,7 +88,7 @@
protected QSTile(Host host) {
mHost = host;
mContext = host.getContext();
- mHandler = new H(host.getLooper());
+ mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
}
/**
@@ -242,7 +230,8 @@
protected void handleLongClick() {
MetricsLogger.action(mContext, MetricsEvent.ACTION_QS_LONG_PRESS, getTileSpec());
- mHost.startActivityDismissingKeyguard(getLongClickIntent());
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
+ getLongClickIntent(), 0);
}
public abstract Intent getLongClickIntent();
@@ -381,7 +370,8 @@
if (mState.disabledByPolicy) {
Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
mContext, mState.enforcedAdmin);
- mHost.startActivityDismissingKeyguard(intent);
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
+ intent, 0);
} else {
mAnnounceNextStateChange = true;
handleClick();
@@ -436,36 +426,17 @@
}
public interface Host {
- void startActivityDismissingKeyguard(Intent intent);
- void startActivityDismissingKeyguard(PendingIntent intent);
- void startRunnableDismissingKeyguard(Runnable runnable);
void warn(String message, Throwable t);
void collapsePanels();
- void animateToggleQSExpansion();
void openPanels();
- Looper getLooper();
Context getContext();
Collection<QSTile<?>> getTiles();
void addCallback(Callback callback);
void removeCallback(Callback callback);
- BluetoothController getBluetoothController();
- LocationController getLocationController();
- RotationLockController getRotationLockController();
- NetworkController getNetworkController();
- ZenModeController getZenModeController();
- HotspotController getHotspotController();
- CastController getCastController();
- FlashlightController getFlashlightController();
- KeyguardMonitor getKeyguardMonitor();
- UserSwitcherController getUserSwitcherController();
- UserInfoController getUserInfoController();
- BatteryController getBatteryController();
TileServices getTileServices();
void removeTile(String tileSpec);
- ManagedProfileController getManagedProfileController();
-
- public interface Callback {
+ interface Callback {
void onTilesChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 0be53b4..730b55d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -36,13 +36,14 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.QSDetailClipper;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.QSTileHost;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
import java.util.ArrayList;
@@ -140,7 +141,7 @@
mNotifQsContainer.setCustomizerShowing(true);
announceForAccessibility(mContext.getString(
R.string.accessibility_desc_quick_settings_edit));
- mHost.getKeyguardMonitor().addCallback(mKeyguardCallback);
+ Dependency.get(KeyguardMonitor.class).addCallback(mKeyguardCallback);
}
}
@@ -156,7 +157,7 @@
mNotifQsContainer.setCustomizerShowing(false);
announceForAccessibility(mContext.getString(
R.string.accessibility_desc_quick_settings));
- mHost.getKeyguardMonitor().removeCallback(mKeyguardCallback);
+ Dependency.get(KeyguardMonitor.class).removeCallback(mKeyguardCallback);
}
}
@@ -206,12 +207,9 @@
mTileAdapter.saveSpecs(mHost);
}
- private final Callback mKeyguardCallback = new Callback() {
- @Override
- public void onKeyguardChanged() {
- if (mHost.getKeyguardMonitor().isShowing()) {
- hide(0, 0);
- }
+ private final Callback mKeyguardCallback = () -> {
+ if (Dependency.get(KeyguardMonitor.class).isShowing()) {
+ hide(0, 0);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 0cd6490..72e6fcc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -30,6 +30,7 @@
import android.service.quicksettings.TileService;
import android.widget.Button;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QSTile.DrawableIcon;
@@ -59,7 +60,7 @@
private void addSystemTiles(final QSTileHost host) {
String possible = mContext.getString(R.string.quick_settings_tiles_stock);
String[] possibleTiles = possible.split(",");
- final Handler qsHandler = new Handler(host.getLooper());
+ final Handler qsHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
final Handler mainHandler = new Handler(Looper.getMainLooper());
for (int i = 0; i < possibleTiles.length; i++) {
final String spec = possibleTiles[i];
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index cff4846..3afbc35 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -39,6 +39,8 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
+import com.android.systemui.Dependency;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
import com.android.systemui.statusbar.phone.QSTileHost;
@@ -306,13 +308,10 @@
}
public void startUnlockAndRun() {
- mHost.startRunnableDismissingKeyguard(new Runnable() {
- @Override
- public void run() {
- try {
- mService.onUnlockComplete();
- } catch (RemoteException e) {
- }
+ Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
+ try {
+ mService.onUnlockComplete();
+ } catch (RemoteException e) {
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 015a4c0..5c23eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -30,13 +30,13 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.quicksettings.IQSService;
-import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.Dependency;
import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -284,13 +284,13 @@
@Override
public boolean isLocked() {
- KeyguardMonitor keyguardMonitor = mHost.getKeyguardMonitor();
+ KeyguardMonitor keyguardMonitor = Dependency.get(KeyguardMonitor.class);
return keyguardMonitor.isShowing();
}
@Override
public boolean isSecure() {
- KeyguardMonitor keyguardMonitor = mHost.getKeyguardMonitor();
+ KeyguardMonitor keyguardMonitor = Dependency.get(KeyguardMonitor.class);
return keyguardMonitor.isSecure() && keyguardMonitor.isShowing();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
index a82f550..7e04b67 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
@@ -38,10 +38,12 @@
import com.android.settingslib.BatteryInfo;
import com.android.settingslib.graph.UsageView;
import com.android.systemui.BatteryMeterDrawable;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.qs.external.TileColorPicker;
import com.android.systemui.statusbar.policy.BatteryController;
import java.text.NumberFormat;
@@ -59,7 +61,7 @@
public BatteryTile(Host host) {
super(host);
- mBatteryController = host.getBatteryController();
+ mBatteryController = Dependency.get(BatteryController.class);
}
@Override
@@ -273,7 +275,7 @@
mDetailShown = true;
v.getContext().registerReceiver(mReceiver,
new IntentFilter(Intent.ACTION_TIME_TICK), null,
- PhoneStatusBar.getTimeTickHandler(v.getContext()));
+ Dependency.get(Dependency.TIME_TICK_HANDLER));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 15f3c90..91e76ca 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -32,7 +32,9 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
@@ -48,10 +50,12 @@
private final BluetoothController mController;
private final BluetoothDetailAdapter mDetailAdapter;
+ private final ActivityStarter mActivityStarter;
public BluetoothTile(Host host) {
super(host);
- mController = host.getBluetoothController();
+ mController = Dependency.get(BluetoothController.class);
+ mActivityStarter = Dependency.get(ActivityStarter.class);
mDetailAdapter = (BluetoothDetailAdapter) createDetailAdapter();
}
@@ -90,7 +94,8 @@
@Override
protected void handleSecondaryClick() {
if (!mController.canConfigBluetooth()) {
- mHost.startActivityDismissingKeyguard(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
+ mActivityStarter.postStartActivityDismissingKeyguard(
+ new Intent(Settings.ACTION_BLUETOOTH_SETTINGS), 0);
return;
}
showDetail(true);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index d61e2f2..7415765 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -28,7 +28,9 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
@@ -49,12 +51,14 @@
private final CastDetailAdapter mDetailAdapter;
private final KeyguardMonitor mKeyguard;
private final Callback mCallback = new Callback();
+ private final ActivityStarter mActivityStarter;
public CastTile(Host host) {
super(host);
- mController = host.getCastController();
+ mController = Dependency.get(CastController.class);
mDetailAdapter = new CastDetailAdapter();
- mKeyguard = host.getKeyguardMonitor();
+ mKeyguard = Dependency.get(KeyguardMonitor.class);
+ mActivityStarter = Dependency.get(ActivityStarter.class);
}
@Override
@@ -101,13 +105,10 @@
@Override
protected void handleClick() {
if (mKeyguard.isSecure() && !mKeyguard.canSkipBouncer()) {
- mHost.startRunnableDismissingKeyguard(new Runnable() {
- @Override
- public void run() {
- MetricsLogger.action(mContext, getMetricsCategory());
- showDetail(true);
- mHost.openPanels();
- }
+ mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
+ MetricsLogger.action(mContext, getMetricsCategory());
+ showDetail(true);
+ mHost.openPanels();
});
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 5f7c803..75c4a75 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -29,7 +29,9 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.net.DataUsageController;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSIconView;
import com.android.systemui.qs.QSTile;
@@ -48,10 +50,12 @@
private final CellularDetailAdapter mDetailAdapter;
private final CellSignalCallback mSignalCallback = new CellSignalCallback();
+ private final ActivityStarter mActivityStarter;
public CellularTile(Host host) {
super(host);
- mController = host.getNetworkController();
+ mController = Dependency.get(NetworkController.class);
+ mActivityStarter = Dependency.get(ActivityStarter.class);
mDataController = mController.getMobileDataController();
mDetailAdapter = new CellularDetailAdapter();
}
@@ -96,7 +100,7 @@
if (mDataController.isMobileDataSupported()) {
showDetail(true);
} else {
- mHost.startActivityDismissingKeyguard(CELLULAR_SETTINGS);
+ mActivityStarter.postStartActivityDismissingKeyguard(CELLULAR_SETTINGS, 0);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index aadc8e7..412fe3d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -21,11 +21,13 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.NetworkController;
public class DataSaverTile extends QSTile<QSTile.BooleanState> implements
DataSaverController.Listener{
@@ -34,7 +36,7 @@
public DataSaverTile(Host host) {
super(host);
- mDataSaverController = host.getNetworkController().getDataSaverController();
+ mDataSaverController = Dependency.get(NetworkController.class).getDataSaverController();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index a25b7ea..3c1f504 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -35,9 +35,11 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SysUIToast;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -74,7 +76,7 @@
public DndTile(Host host) {
super(host);
- mController = host.getZenModeController();
+ mController = Dependency.get(ZenModeController.class);
mDetailAdapter = new DndDetailAdapter();
mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_SET_VISIBLE));
mReceiverRegistered = true;
@@ -313,7 +315,8 @@
private final ZenModePanel.Callback mZenModePanelCallback = new ZenModePanel.Callback() {
@Override
public void onPrioritySettings() {
- mHost.startActivityDismissingKeyguard(ZEN_PRIORITY_SETTINGS);
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
+ ZEN_PRIORITY_SETTINGS, 0);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 4bbc458..ac82753 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -27,6 +27,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.FlashlightController;
@@ -45,14 +46,12 @@
public FlashlightTile(Host host) {
super(host);
- mFlashlightController = host.getFlashlightController();
- mFlashlightController.addCallback(this);
+ mFlashlightController = Dependency.get(FlashlightController.class);
}
@Override
protected void handleDestroy() {
super.handleDestroy();
- mFlashlightController.removeCallback(this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 9d495ce..70f8109 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -29,6 +29,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.qs.GlobalSetting;
import com.android.systemui.qs.QSTile;
@@ -52,7 +53,7 @@
public HotspotTile(Host host) {
super(host);
- mController = host.getHotspotController();
+ mController = Dependency.get(HotspotController.class);
mAirplaneMode = new GlobalSetting(mContext, mHandler, Global.AIRPLANE_MODE_ON) {
@Override
protected void handleValueChanged(int value) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
index f968816..fcc9596 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -31,6 +31,8 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.qs.QSTile;
import java.util.Arrays;
@@ -105,7 +107,7 @@
try {
if (pi != null) {
if (pi.isActivity()) {
- getHost().startActivityDismissingKeyguard(pi);
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(pi);
} else {
pi.send();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index 002e2a6..5374f18 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -18,14 +18,15 @@
import android.content.Intent;
import android.os.UserManager;
-
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.widget.Switch;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.LocationController;
@@ -47,8 +48,8 @@
public LocationTile(Host host) {
super(host);
- mController = host.getLocationController();
- mKeyguard = host.getKeyguardMonitor();
+ mController = Dependency.get(LocationController.class);
+ mKeyguard = Dependency.get(KeyguardMonitor.class);
}
@Override
@@ -75,18 +76,15 @@
@Override
protected void handleClick() {
if (mKeyguard.isSecure() && mKeyguard.isShowing()) {
- mHost.startRunnableDismissingKeyguard(new Runnable() {
- @Override
- public void run() {
- final boolean wasEnabled = (Boolean) mState.value;
- mHost.openPanels();
- MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
- mController.setLocationEnabled(!wasEnabled);
- }
+ Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
+ final boolean wasEnabled = mState.value;
+ mHost.openPanels();
+ MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
+ mController.setLocationEnabled(!wasEnabled);
});
return;
}
- final boolean wasEnabled = (Boolean) mState.value;
+ final boolean wasEnabled = mState.value;
MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
mController.setLocationEnabled(!wasEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 9be67da..2c0af17 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -26,6 +26,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.RotationLockController;
@@ -51,7 +52,7 @@
public RotationLockTile(Host host) {
super(host);
- mController = host.getRotationLockController();
+ mController = Dependency.get(RotationLockController.class);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
index 246c23e..c20c6bb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
@@ -22,6 +22,7 @@
import android.util.Pair;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.UserInfoController;
@@ -35,8 +36,8 @@
public UserTile(Host host) {
super(host);
- mUserSwitcherController = host.getUserSwitcherController();
- mUserInfoController = host.getUserInfoController();
+ mUserSwitcherController = Dependency.get(UserSwitcherController.class);
+ mUserInfoController = Dependency.get(UserInfoController.class);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 7d99041..54b41ac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -31,7 +31,9 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.wifi.AccessPoint;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
@@ -55,12 +57,14 @@
private final QSTile.SignalState mStateBeforeClick = newTileState();
protected final WifiSignalCallback mSignalCallback = new WifiSignalCallback();
+ private final ActivityStarter mActivityStarter;
public WifiTile(Host host) {
super(host);
- mController = host.getNetworkController();
+ mController = Dependency.get(NetworkController.class);
mWifiController = mController.getAccessPointController();
mDetailAdapter = (WifiDetailAdapter) createDetailAdapter();
+ mActivityStarter = Dependency.get(ActivityStarter.class);
}
@Override
@@ -115,7 +119,8 @@
@Override
protected void handleSecondaryClick() {
if (!mWifiController.canConfigWifi()) {
- mHost.startActivityDismissingKeyguard(new Intent(Settings.ACTION_WIFI_SETTINGS));
+ mActivityStarter.postStartActivityDismissingKeyguard(
+ new Intent(Settings.ACTION_WIFI_SETTINGS), 0);
return;
}
showDetail(true);
@@ -329,7 +334,7 @@
@Override
public void onSettingsActivityTriggered(Intent settingsIntent) {
- mHost.startActivityDismissingKeyguard(settingsIntent);
+ mActivityStarter.postStartActivityDismissingKeyguard(settingsIntent, 0);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 207deff..ae4d6c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -23,6 +23,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.phone.ManagedProfileController;
@@ -41,7 +42,7 @@
public WorkModeTile(Host host) {
super(host);
- mProfileController = host.getManagedProfileController();
+ mProfileController = Dependency.get(ManagedProfileController.class);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 6ea51e5..ededf96 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -209,7 +209,8 @@
// When svelte, we trim the memory to just the visible thumbnails when
// leaving, so don't thrash the cache as the user scrolls (just load
// them from scratch each time)
- if (config.svelteLevel < RecentsConfiguration.SVELTE_LIMIT_CACHE) {
+ if (config.svelteLevel < RecentsConfiguration.SVELTE_LIMIT_CACHE
+ && !ActivityManager.ENABLE_TASK_SNAPSHOTS) {
mThumbnailCache.put(t.key, cachedThumbnailData);
}
}
@@ -553,7 +554,9 @@
// Load the thumbnail from the system
thumbnailData = ssp.getTaskThumbnail(taskKey.id);
if (thumbnailData.thumbnail != null) {
- mThumbnailCache.put(taskKey, thumbnailData);
+ if (!ActivityManager.ENABLE_TASK_SNAPSHOTS) {
+ mThumbnailCache.put(taskKey, thumbnailData);
+ }
return thumbnailData.thumbnail;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index 792679b..4ac0f9e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -16,6 +16,7 @@
package com.android.systemui.recents.views;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -114,8 +115,12 @@
mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
mBgFillPaint.setColor(Color.WHITE);
mLockedPaint.setColor(Color.WHITE);
- mFullscreenThumbnailScale = res.getFraction(
- com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
+ if (ActivityManager.ENABLE_TASK_SNAPSHOTS) {
+ mFullscreenThumbnailScale = 1f;
+ } else {
+ mFullscreenThumbnailScale = res.getFraction(
+ com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
+ }
mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
index 3059a05..7825e9e 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
@@ -37,6 +37,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import java.util.ArrayList;
@@ -70,7 +71,7 @@
private final CurrentUserTracker mUserTracker;
private final IVrManager mVrManager;
- private Handler mBackgroundHandler;
+ private final Handler mBackgroundHandler;
private final BrightnessObserver mBrightnessObserver;
private ArrayList<BrightnessStateChangeCallback> mChangeCallbacks =
@@ -276,7 +277,7 @@
mContext = context;
mIcon = icon;
mControl = control;
- mBackgroundHandler = new Handler(Looper.getMainLooper());
+ mBackgroundHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
mUserTracker = new CurrentUserTracker(mContext) {
@Override
public void onUserSwitched(int newUserId) {
@@ -298,10 +299,6 @@
mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService("vrmanager"));
}
- public void setBackgroundLooper(Looper backgroundLooper) {
- mBackgroundHandler = new Handler(backgroundLooper);
- }
-
public void addStateChangedCallback(BrightnessStateChangeCallback cb) {
mChangeCallbacks.add(cb);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index b5358bf..b9ed725 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -463,6 +463,7 @@
}
mDark = dark;
updateBackground();
+ updateBackgroundTint(fade);
if (!dark && fade && !shouldHideBackground()) {
fadeInFromDark(delay);
}
@@ -700,8 +701,8 @@
protected void updateBackground() {
cancelFadeAnimations();
if (shouldHideBackground()) {
- mBackgroundDimmed.setVisibility(View.INVISIBLE);
- mBackgroundNormal.setVisibility(View.INVISIBLE);
+ mBackgroundDimmed.setVisibility(INVISIBLE);
+ mBackgroundNormal.setVisibility(mActivated ? VISIBLE : INVISIBLE);
} else if (mDimmed) {
// When groups are animating to the expanded state from the lockscreen, show the
// normal background instead of the dimmed background
@@ -940,6 +941,9 @@
* @return the calculated background color
*/
private int calculateBgColor(boolean withTint, boolean withOverRide) {
+ if (mDark) {
+ return getContext().getColor(R.color.notification_material_background_dark_color);
+ }
if (withOverRide && mOverrideTint != NO_COLOR) {
int defaultTint = calculateBgColor(withTint, false);
return NotificationUtils.interpolateColors(defaultTint, mOverrideTint, mOverrideAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index faf143e..d9298ed 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.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.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.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;
@@ -71,7 +74,6 @@
import android.view.Display;
import android.view.IWindowManager;
import android.view.LayoutInflater;
-import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
@@ -92,6 +94,7 @@
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.DejankUtils;
+import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
@@ -100,11 +103,11 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.recents.Recents;
import com.android.systemui.statusbar.NotificationData.Entry;
-import com.android.systemui.statusbar.NotificationGuts.OnGutsClosedListener;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.phone.NavigationBarView;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.PreviewInflater;
import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -223,6 +226,7 @@
protected KeyguardManager mKeyguardManager;
private LockPatternUtils mLockPatternUtils;
+ private DeviceProvisionedController mDeviceProvisionedController;
// UI-specific methods
@@ -237,8 +241,6 @@
protected Display mDisplay;
- private boolean mDeviceProvisioned = false;
-
protected RecentsComponent mRecents;
protected int mZenMode;
@@ -270,7 +272,7 @@
@Override // NotificationData.Environment
public boolean isDeviceProvisioned() {
- return mDeviceProvisioned;
+ return mDeviceProvisionedController.isDeviceProvisioned();
}
private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
@@ -284,15 +286,17 @@
return mVrMode;
}
+ private final DeviceProvisionedListener mDeviceProvisionedListener =
+ new DeviceProvisionedListener() {
+ @Override
+ public void onDeviceProvisionedChanged() {
+ updateNotifications();
+ }
+ };
+
protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
- final boolean provisioned = 0 != Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0);
- if (provisioned != mDeviceProvisioned) {
- mDeviceProvisioned = provisioned;
- updateNotifications();
- }
final int mode = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
setZenMode(mode);
@@ -315,9 +319,16 @@
};
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;
}
@@ -707,9 +718,8 @@
ServiceManager.checkService(DreamService.DREAM_SERVICE));
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true,
- mSettingsObserver);
+ mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
+ mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
mSettingsObserver);
@@ -1038,6 +1048,7 @@
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 @@
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 @@
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) {
@@ -2470,6 +2490,7 @@
} catch (RemoteException e) {
// Ignore.
}
+ mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index fed28e3..477701c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -185,7 +185,14 @@
public void animateCollapsePanels() {
synchronized (mLock) {
mHandler.removeMessages(MSG_COLLAPSE_PANELS);
- mHandler.sendEmptyMessage(MSG_COLLAPSE_PANELS);
+ mHandler.obtainMessage(MSG_COLLAPSE_PANELS, 0, 0).sendToTarget();
+ }
+ }
+
+ public void animateCollapsePanels(int flags) {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_COLLAPSE_PANELS);
+ mHandler.obtainMessage(MSG_COLLAPSE_PANELS, flags, 0).sendToTarget();
}
}
@@ -450,7 +457,7 @@
break;
case MSG_COLLAPSE_PANELS:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).animateCollapsePanels(0);
+ mCallbacks.get(i).animateCollapsePanels(msg.arg1);
}
break;
case MSG_EXPAND_SETTINGS:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index f451aef..d599ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -42,11 +42,12 @@
import com.android.internal.app.IBatteryStats;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
import com.android.systemui.statusbar.phone.LockIcon;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.UserInfoController;
/**
* Controls the indications and error messages shown on the Keyguard
@@ -83,6 +84,8 @@
private int mChargingWattage;
private String mMessageToShowOnScreenOn;
+ private KeyguardUpdateMonitorCallback mUpdateMonitor;
+
private final DevicePolicyManager mDevicePolicyManager;
public KeyguardIndicationController(Context context, ViewGroup indicationArea,
@@ -106,14 +109,31 @@
mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(
Context.DEVICE_POLICY_SERVICE);
- KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitor);
+ KeyguardUpdateMonitor.getInstance(context).registerCallback(getKeyguardCallback());
context.registerReceiverAsUser(mTickReceiver, UserHandle.SYSTEM,
new IntentFilter(Intent.ACTION_TIME_TICK), null,
- PhoneStatusBar.getTimeTickHandler(mContext));
+ Dependency.get(Dependency.TIME_TICK_HANDLER));
updateDisclosure();
}
+ /**
+ * Gets the {@link KeyguardUpdateMonitorCallback} instance associated with this
+ * {@link KeyguardIndicationController}.
+ *
+ * <p>Subclasses may override this method to extend or change the callback behavior by extending
+ * the {@link BaseKeyguardCallback}.
+ *
+ * @return A KeyguardUpdateMonitorCallback. Multiple calls to this method <b>must</b> return the
+ * same instance.
+ */
+ protected KeyguardUpdateMonitorCallback getKeyguardCallback() {
+ if (mUpdateMonitor == null) {
+ mUpdateMonitor = new BaseKeyguardCallback();
+ }
+ return mUpdateMonitor;
+ }
+
private void updateDisclosure() {
if (mDevicePolicyManager == null) {
return;
@@ -152,6 +172,12 @@
}
/**
+ * Sets the active controller managing changes and callbacks to user information.
+ */
+ public void setUserInfoController(UserInfoController userInfoController) {
+ }
+
+ /**
* Hides transient indication in {@param delayMs}.
*/
public void hideTransientIndicationDelayed(long delayMs) {
@@ -264,8 +290,37 @@
}
}
- KeyguardUpdateMonitorCallback mUpdateMonitor = new KeyguardUpdateMonitorCallback() {
- public int mLastSuccessiveErrorMessage = -1;
+ public void setStatusBarKeyguardViewManager(
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ }
+
+ BroadcastReceiver mTickReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mHandler.post(() -> {
+ if (mVisible) {
+ updateIndication();
+ }
+ });
+ }
+ };
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_HIDE_TRANSIENT && mTransientIndication != null) {
+ mTransientIndication = null;
+ updateIndication();
+ } else if (msg.what == MSG_CLEAR_FP_MSG) {
+ mLockIcon.setTransientFpError(false);
+ hideTransientIndication();
+ }
+ }
+ };
+
+ protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback {
+ private int mLastSuccessiveErrorMessage = -1;
@Override
public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
@@ -372,34 +427,4 @@
}
}
};
-
- BroadcastReceiver mTickReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mHandler.post(() -> {
- if (mVisible) {
- updateIndication();
- }
- });
- }
- };
-
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_HIDE_TRANSIENT && mTransientIndication != null) {
- mTransientIndication = null;
- updateIndication();
- } else if (msg.what == MSG_CLEAR_FP_MSG) {
- mLockIcon.setTransientFpError(false);
- hideTransientIndication();
- }
- }
- };
-
- public void setStatusBarKeyguardViewManager(
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 458daf1..3a89186 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 @@
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 @@
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 @@
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 c7adb60..83104e6 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 @@
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 @@
}
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 @@
if (selectedImportance == mStartingUserImportance) {
return;
}
- final NotificationChannel channel = mStatusBarNotification.getNotificationChannel();
MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
selectedImportance - mStartingUserImportance);
- channel.setImportance(selectedImportance);
+ mNotificationChannel.setImportance(selectedImportance);
try {
mINotificationManager.updateNotificationChannelForPackage(
mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
- channel);
+ mNotificationChannel);
} catch (RemoteException e) {
// :(
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index d77e9ed..1128101 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -37,8 +37,10 @@
import android.widget.ImageView;
import android.widget.LinearLayout;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkControllerImpl;
import com.android.systemui.statusbar.policy.SecurityController;
@@ -62,8 +64,8 @@
private static final String SLOT_WIFI = "wifi";
private static final String SLOT_ETHERNET = "ethernet";
- NetworkControllerImpl mNC;
- SecurityController mSC;
+ private final NetworkController mNetworkController;
+ private final SecurityController mSecurityController;
private boolean mNoSimsVisible = false;
private boolean mVpnVisible = false;
@@ -131,6 +133,8 @@
TypedValue typedValue = new TypedValue();
res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
mIconScaleFactor = typedValue.getFloat();
+ mNetworkController = Dependency.get(NetworkController.class);
+ mSecurityController = Dependency.get(SecurityController.class);
}
@Override
@@ -151,25 +155,11 @@
mBlockEthernet = blockEthernet;
mBlockWifi = blockWifi;
// Re-register to get new callbacks.
- mNC.removeCallback(this);
- mNC.addCallback(this);
+ mNetworkController.removeCallback(this);
+ mNetworkController.addCallback(this);
}
}
- public void setNetworkController(NetworkControllerImpl nc) {
- if (DEBUG) Log.d(TAG, "NetworkController=" + nc);
- mNC = nc;
- mNC.addCallback(this);
- }
-
- public void setSecurityController(SecurityController sc) {
- if (DEBUG) Log.d(TAG, "SecurityController=" + sc);
- mSC = sc;
- mSC.addCallback(this);
- mVpnVisible = mSC.isVpnEnabled();
- mVpnIconId = currentVpnIconId(mSC.isVpnBranded());
- }
-
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -213,6 +203,8 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ mVpnVisible = mSecurityController.isVpnEnabled();
+ mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
for (PhoneState state : mPhoneStates) {
if (state.mMobileGroup.getParent() == null) {
@@ -227,14 +219,16 @@
apply();
applyIconTint();
+ mNetworkController.addCallback(this);
+ mSecurityController.addCallback(this);
}
@Override
protected void onDetachedFromWindow() {
mMobileSignalGroup.removeAllViews();
TunerService.get(mContext).removeTunable(this);
- mSC.removeCallback(this);
- mNC.removeCallback(this);
+ mSecurityController.removeCallback(this);
+ mNetworkController.removeCallback(this);
super.onDetachedFromWindow();
}
@@ -253,8 +247,8 @@
post(new Runnable() {
@Override
public void run() {
- mVpnVisible = mSC.isVpnEnabled();
- mVpnIconId = currentVpnIconId(mSC.isVpnBranded());
+ mVpnVisible = mSecurityController.isVpnEnabled();
+ mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
apply();
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
index 1c8c317..3bbda4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -30,7 +30,7 @@
import android.widget.LinearLayout;
import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QS.ActivityStarter;
+import com.android.systemui.ActivityStarter;
import java.net.URISyntaxException;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 7adb36d..f24e40b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -24,8 +24,6 @@
import android.content.IntentFilter;
import android.graphics.PixelFormat;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
@@ -35,15 +33,16 @@
import android.view.WindowManager;
import android.widget.LinearLayout;
import com.android.systemui.BatteryMeterView;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
-import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
/**
* A status bar (and navigation bar) tailored for the automotive use case.
@@ -73,6 +72,7 @@
SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
registerPackageChangeReceivers();
+ createBatteryController();
mCarBatteryController.startListening();
mConnectedDeviceSignalController.startListening();
}
@@ -105,7 +105,7 @@
R.dimen.status_bar_connected_device_signal_margin_end));
mConnectedDeviceSignalController = new ConnectedDeviceSignalController(mContext,
- mSignalsView, mBluetoothController);
+ mSignalsView);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "makeStatusBarView(). mBatteryMeterView: " + mBatteryMeterView);
@@ -114,8 +114,7 @@
return statusBarView;
}
- @Override
- protected BatteryController createBatteryController() {
+ private BatteryController createBatteryController() {
mCarBatteryController = new CarBatteryController(mContext);
mCarBatteryController.addBatteryViewHandler(this);
return mCarBatteryController;
@@ -211,8 +210,11 @@
@Override
protected void createUserSwitcher() {
- if (mUserSwitcherController.useFullscreenUserSwitcher()) {
- mFullscreenUserSwitcher = new FullscreenUserSwitcher(this, mUserSwitcherController,
+ UserSwitcherController userSwitcherController =
+ Dependency.get(UserSwitcherController.class);
+ if (userSwitcherController.useFullscreenUserSwitcher()) {
+ mFullscreenUserSwitcher = new FullscreenUserSwitcher(this,
+ userSwitcherController,
(ViewStub) mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub));
} else {
super.createUserSwitcher();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
index a3e1b3a..c308930 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/ConnectedDeviceSignalController.java
@@ -15,6 +15,7 @@
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.ScalingDrawableWrapper;
import com.android.systemui.statusbar.policy.BluetoothController;
@@ -67,10 +68,9 @@
private BluetoothHeadsetClient mBluetoothHeadsetClient;
- public ConnectedDeviceSignalController(Context context, View signalsView,
- BluetoothController controller) {
+ public ConnectedDeviceSignalController(Context context, View signalsView) {
mContext = context;
- mController = controller;
+ mController = Dependency.get(BluetoothController.class);
mSignalsView = signalsView;
mNetworkSignalView = (ImageView)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index a011162..31cfa66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -16,7 +16,10 @@
import android.content.Context;
import android.os.Handler;
+import android.os.Looper;
import android.provider.Settings.Secure;
+
+import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.Prefs.Key;
import com.android.systemui.qs.SecureSetting;
@@ -37,12 +40,12 @@
public AutoTileManager(Context context, QSTileHost host) {
mContext = context;
mHost = host;
- mHandler = new Handler(mHost.getLooper());
+ mHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
if (!Prefs.getBoolean(context, Key.QS_HOTSPOT_ADDED, false)) {
- host.getHotspotController().addCallback(mHotspotCallback);
+ Dependency.get(HotspotController.class).addCallback(mHotspotCallback);
}
if (!Prefs.getBoolean(context, Key.QS_DATA_SAVER_ADDED, false)) {
- host.getNetworkController().getDataSaverController().addCallback(mDataSaverListener);
+ Dependency.get(DataSaverController.class).addCallback(mDataSaverListener);
}
if (!Prefs.getBoolean(context, Key.QS_INVERT_COLORS_ADDED, false)) {
mColorsSetting = new SecureSetting(mContext, mHandler,
@@ -52,43 +55,33 @@
if (value != 0) {
mHost.addTile("inversion");
Prefs.putBoolean(mContext, Key.QS_INVERT_COLORS_ADDED, true);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mColorsSetting.setListening(false);
- }
- });
+ mHandler.post(() -> mColorsSetting.setListening(false));
}
}
};
mColorsSetting.setListening(true);
}
if (!Prefs.getBoolean(context, Key.QS_WORK_ADDED, false)) {
- host.getManagedProfileController().addCallback(mProfileCallback);
+ Dependency.get(ManagedProfileController.class).addCallback(mProfileCallback);
}
}
public void destroy() {
mColorsSetting.setListening(false);
- mHost.getHotspotController().removeCallback(mHotspotCallback);
- mHost.getNetworkController().getDataSaverController().removeCallback(mDataSaverListener);
- mHost.getManagedProfileController().removeCallback(mProfileCallback);
+ Dependency.get(HotspotController.class).removeCallback(mHotspotCallback);
+ Dependency.get(DataSaverController.class).removeCallback(mDataSaverListener);
+ Dependency.get(ManagedProfileController.class).removeCallback(mProfileCallback);
}
private final ManagedProfileController.Callback mProfileCallback =
new ManagedProfileController.Callback() {
@Override
public void onManagedProfileChanged() {
- if (mHost.getManagedProfileController().hasActiveProfile()) {
+ if (Dependency.get(ManagedProfileController.class).hasActiveProfile()) {
mHost.addTile("work");
Prefs.putBoolean(mContext, Key.QS_WORK_ADDED, true);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mHost.getManagedProfileController().removeCallback(
- mProfileCallback);
- }
- });
+ mHandler.post(() -> Dependency.get(ManagedProfileController.class)
+ .removeCallback(mProfileCallback));
}
}
@@ -105,13 +98,8 @@
if (isDataSaving) {
mHost.addTile("saver");
Prefs.putBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mHost.getNetworkController().getDataSaverController().removeCallback(
- mDataSaverListener);
- }
- });
+ mHandler.post(() -> Dependency.get(DataSaverController.class).removeCallback(
+ mDataSaverListener));
}
}
};
@@ -122,12 +110,8 @@
if (enabled) {
mHost.addTile("hotspot");
Prefs.putBoolean(mContext, Key.QS_HOTSPOT_ADDED, true);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mHost.getHotspotController().removeCallback(mHotspotCallback);
- }
- });
+ mHandler.post(() -> Dependency.get(HotspotController.class)
+ .removeCallback(mHotspotCallback));
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index a2c106a..79120d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -58,6 +58,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
+import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.assist.AssistManager;
@@ -66,7 +67,7 @@
import com.android.systemui.plugins.IntentButtonProvider.IntentButton.IconState;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.plugins.qs.QS.ActivityStarter;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -230,6 +231,11 @@
mRightAffordanceView.setOnClickListener(this);
mLeftAffordanceView.setOnClickListener(this);
initAccessibility();
+ mActivityStarter = Dependency.get(ActivityStarter.class);
+ mFlashlightController = Dependency.get(FlashlightController.class);
+ mAccessibilityController = Dependency.get(AccessibilityController.class);
+ mAssistManager = Dependency.get(AssistManager.class);
+ updateLeftAffordance();
}
@Override
@@ -299,20 +305,6 @@
mRightAffordanceView.setContentDescription(state.contentDescription);
}
- public void setActivityStarter(ActivityStarter activityStarter) {
- mActivityStarter = activityStarter;
- }
-
- public void setFlashlightController(FlashlightController flashlightController) {
- mFlashlightController = flashlightController;
- }
-
- public void setAccessibilityController(AccessibilityController accessibilityController) {
- mAccessibilityController = accessibilityController;
- mLockIcon.setAccessibilityController(accessibilityController);
- accessibilityController.addStateChangedCallback(this);
- }
-
public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
mPhoneStatusBar = phoneStatusBar;
updateCameraVisibility(); // in case onFinishInflate() was called too early
@@ -761,11 +753,6 @@
mIndicationController = keyguardIndicationController;
}
- public void setAssistManager(AssistManager assistManager) {
- mAssistManager = assistManager;
- updateLeftAffordance();
- }
-
public void updateLeftAffordance() {
updateLeftAffordanceIcon();
updateLeftPreview();
@@ -792,7 +779,7 @@
private final PluginListener<IntentButtonProvider> mRightListener =
new PluginListener<IntentButtonProvider>() {
@Override
- public void onPluginConnected(IntentButtonProvider plugin) {
+ public void onPluginConnected(IntentButtonProvider plugin, Context pluginContext) {
setRightButton(plugin.getIntentButton());
}
@@ -805,7 +792,7 @@
private final PluginListener<IntentButtonProvider> mLeftListener =
new PluginListener<IntentButtonProvider>() {
@Override
- public void onPluginConnected(IntentButtonProvider plugin) {
+ public void onPluginConnected(IntentButtonProvider plugin, Context pluginContext) {
setLeftButton(plugin.getIntentButton());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index e4c778c..ff58e54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -28,13 +28,15 @@
import android.widget.RelativeLayout;
import android.widget.TextView;
-import com.android.systemui.BatteryMeterView;
+import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import java.text.NumberFormat;
@@ -43,7 +45,7 @@
* The header group on Keyguard.
*/
public class KeyguardStatusBarView extends RelativeLayout
- implements BatteryController.BatteryStateChangeCallback {
+ implements BatteryStateChangeCallback, OnUserInfoChangedListener {
private boolean mBatteryCharging;
private boolean mKeyguardUserSwitcherShowing;
@@ -78,6 +80,7 @@
mCarrierLabel = (TextView) findViewById(R.id.keyguard_carrier_text);
loadDimens();
updateUserSwitcher();
+ mBatteryController = Dependency.get(BatteryController.class);
}
@Override
@@ -203,23 +206,25 @@
mMultiUserSwitch.setKeyguardMode(keyguardSwitcherAvailable);
}
- public void setBatteryController(BatteryController batteryController) {
- mBatteryController = batteryController;
- ((BatteryMeterView) findViewById(R.id.battery)).setBatteryController(batteryController);
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ UserInfoController userInfoController = Dependency.get(UserInfoController.class);
+ userInfoController.addCallback(this);
+ mUserSwitcherController = Dependency.get(UserSwitcherController.class);
+ mMultiUserSwitch.setUserSwitcherController(mUserSwitcherController);
+ userInfoController.reloadUserInfo();
}
- public void setUserSwitcherController(UserSwitcherController controller) {
- mUserSwitcherController = controller;
- mMultiUserSwitch.setUserSwitcherController(controller);
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ Dependency.get(UserInfoController.class).removeCallback(this);
}
- public void setUserInfoController(UserInfoController userInfoController) {
- userInfoController.addCallback(new UserInfoController.OnUserInfoChangedListener() {
- @Override
- public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
- mMultiUserAvatar.setImageDrawable(picture);
- }
- });
+ @Override
+ public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
+ mMultiUserAvatar.setImageDrawable(picture);
}
public void setQSPanel(QSPanel qsp) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 26b0d53..4535992 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -19,6 +19,7 @@
import android.graphics.Rect;
import android.view.View;
+import com.android.systemui.Dependency;
import com.android.systemui.statusbar.policy.BatteryController;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
@@ -60,11 +61,10 @@
private final Rect mLastFullscreenBounds = new Rect();
private final Rect mLastDockedBounds = new Rect();
- public LightBarController(StatusBarIconController statusBarIconController,
- BatteryController batteryController) {
+ public LightBarController(StatusBarIconController statusBarIconController) {
mStatusBarIconController = statusBarIconController;
- mBatteryController = batteryController;
- batteryController.addCallback(this);
+ mBatteryController = Dependency.get(BatteryController.class);
+ mBatteryController.addCallback(this);
}
public void setNavigationBar(LightBarTransitionsController navigationBar) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 695b500..ef42b2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -29,11 +29,12 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.policy.AccessibilityController;
+import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
/**
* Manages the different states and animations of the unlock icon.
*/
-public class LockIcon extends KeyguardAffordanceView {
+public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChangedListener {
private static final int FP_DRAW_OFF_TIMEOUT = 800;
@@ -49,6 +50,7 @@
private boolean mDeviceInteractive;
private boolean mScreenOn;
private boolean mLastScreenOn;
+ private Drawable mUserAvatarIcon;
private TrustDrawable mTrustDrawable;
private final UnlockMethodCache mUnlockMethodCache;
private AccessibilityController mAccessibilityController;
@@ -80,6 +82,12 @@
mTrustDrawable.stop();
}
+ @Override
+ public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
+ mUserAvatarIcon = picture;
+ update();
+ }
+
public void setTransientFpError(boolean transientFpError) {
mTransientFpError = transientFpError;
update();
@@ -126,27 +134,33 @@
boolean trustHidden = anyFingerprintIcon;
if (state != mLastState || mDeviceInteractive != mLastDeviceInteractive
|| mScreenOn != mLastScreenOn || force) {
- boolean isAnim = true;
- int iconRes = getAnimationResForTransition(mLastState, state, mLastDeviceInteractive,
+ int iconAnimRes =
+ getAnimationResForTransition(mLastState, state, mLastDeviceInteractive,
mDeviceInteractive, mLastScreenOn, mScreenOn);
- if (iconRes == R.drawable.lockscreen_fingerprint_draw_off_animation) {
+ boolean isAnim = iconAnimRes != -1;
+ if (iconAnimRes == R.drawable.lockscreen_fingerprint_draw_off_animation) {
anyFingerprintIcon = true;
useAdditionalPadding = true;
trustHidden = true;
- } else if (iconRes == R.drawable.trusted_state_to_error_animation) {
+ } else if (iconAnimRes == R.drawable.trusted_state_to_error_animation) {
anyFingerprintIcon = true;
useAdditionalPadding = false;
trustHidden = true;
- } else if (iconRes == R.drawable.error_to_trustedstate_animation) {
+ } else if (iconAnimRes == R.drawable.error_to_trustedstate_animation) {
anyFingerprintIcon = true;
useAdditionalPadding = false;
trustHidden = false;
}
- if (iconRes == -1) {
- iconRes = getIconForState(state, mScreenOn, mDeviceInteractive);
- isAnim = false;
+
+ Drawable icon;
+ if (isAnim) {
+ // Load the animation resource.
+ icon = mContext.getDrawable(iconAnimRes);
+ } else {
+ // Load the static icon resource based on the current state.
+ icon = getIconForState(state, mScreenOn, mDeviceInteractive);
}
- Drawable icon = mContext.getDrawable(iconRes);
+
final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
? (AnimatedVectorDrawable) icon
: null;
@@ -175,7 +189,7 @@
animation.start();
}
- if (iconRes == R.drawable.lockscreen_fingerprint_draw_off_animation) {
+ if (iconAnimRes == R.drawable.lockscreen_fingerprint_draw_off_animation) {
removeCallbacks(mDrawOffTimeout);
postDelayed(mDrawOffTimeout, FP_DRAW_OFF_TIMEOUT);
} else {
@@ -225,25 +239,38 @@
mAccessibilityController = accessibilityController;
}
- private int getIconForState(int state, boolean screenOn, boolean deviceInteractive) {
+ private Drawable getIconForState(int state, boolean screenOn, boolean deviceInteractive) {
+ int iconRes;
switch (state) {
case STATE_LOCKED:
- return R.drawable.ic_lock_24dp;
+ iconRes = R.drawable.ic_lock_24dp;
+ break;
case STATE_LOCK_OPEN:
- return R.drawable.ic_lock_open_24dp;
+ if (mUnlockMethodCache.isTrustManaged() && mUnlockMethodCache.isTrusted()
+ && mUserAvatarIcon != null) {
+ return mUserAvatarIcon;
+ } else {
+ iconRes = R.drawable.ic_lock_open_24dp;
+ }
+ break;
case STATE_FACE_UNLOCK:
- return com.android.internal.R.drawable.ic_account_circle;
+ iconRes = com.android.internal.R.drawable.ic_account_circle;
+ break;
case STATE_FINGERPRINT:
// If screen is off and device asleep, use the draw on animation so the first frame
// gets drawn.
- return screenOn && deviceInteractive
+ iconRes = screenOn && deviceInteractive
? R.drawable.ic_fingerprint
: R.drawable.lockscreen_fingerprint_draw_on_animation;
+ break;
case STATE_FINGERPRINT_ERROR:
- return R.drawable.ic_fingerprint_error;
+ iconRes = R.drawable.ic_fingerprint_error;
+ break;
default:
throw new IllegalArgumentException();
}
+
+ return mContext.getDrawable(iconRes);
}
private int getAnimationResForTransition(int oldState, int newState,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index fc33ace..316bd5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -38,8 +38,8 @@
private boolean mListening;
private int mCurrentUser;
- public ManagedProfileControllerImpl(QSTileHost host) {
- mContext = host.getContext();
+ public ManagedProfileControllerImpl(Context context) {
+ mContext = context;
mUserManager = UserManager.get(mContext);
mProfiles = new LinkedList<UserInfo>();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index 4d4f9d2..3cbac17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -29,7 +29,9 @@
import android.widget.Button;
import android.widget.FrameLayout;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
@@ -65,7 +67,7 @@
public void setQsPanel(QSPanel qsPanel) {
mQsPanel = qsPanel;
- setUserSwitcherController(qsPanel.getHost().getUserSwitcherController());
+ setUserSwitcherController(Dependency.get(UserSwitcherController.class));
}
public boolean hasMultipleUsers() {
@@ -134,7 +136,7 @@
Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(
getContext(), v, ContactsContract.Profile.CONTENT_URI,
ContactsContract.QuickContact.MODE_LARGE, null);
- mQsPanel.getHost().startActivityDismissingKeyguard(intent);
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 3423a3c..3c46d26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -64,7 +64,9 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.LatencyTracker;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.fragments.FragmentHostManager;
@@ -124,16 +126,17 @@
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mCommandQueue = SystemUIApplication.getComponent(getContext(), CommandQueue.class);
+ mCommandQueue = SysUiServiceProvider.getComponent(getContext(), CommandQueue.class);
mCommandQueue.addCallbacks(this);
- mPhoneStatusBar = SystemUIApplication.getComponent(getContext(), PhoneStatusBar.class);
- mRecents = SystemUIApplication.getComponent(getContext(), Recents.class);
- mDivider = SystemUIApplication.getComponent(getContext(), Divider.class);
+ mPhoneStatusBar = SysUiServiceProvider.getComponent(getContext(), PhoneStatusBar.class);
+ mRecents = SysUiServiceProvider.getComponent(getContext(), Recents.class);
+ mDivider = SysUiServiceProvider.getComponent(getContext(), Divider.class);
mWindowManager = getContext().getSystemService(WindowManager.class);
mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
if (savedInstanceState != null) {
mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0);
}
+ mAssistManager = Dependency.get(AssistManager.class);
try {
WindowManagerGlobal.getWindowManagerService()
@@ -400,10 +403,6 @@
ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
homeButton.setOnTouchListener(this::onHomeTouch);
homeButton.setOnLongClickListener(this::onHomeLongClick);
-
- if (mAssistManager != null) {
- mAssistManager.onConfigurationChanged();
- }
}
private boolean onHomeTouch(View v, MotionEvent event) {
@@ -436,10 +435,6 @@
}
private void onVerticalChanged(boolean isVertical) {
- if (mAssistManager != null) {
- // TODO: Clean this up.
- mAssistManager.onConfigurationChanged();
- }
mPhoneStatusBar.setQsScrimEnabled(!isVertical);
}
@@ -562,11 +557,6 @@
// ----- Methods that PhoneStatusBar talks to (should be minimized) -----
- public void setAssistManager(AssistManager assistManager) {
- mAssistManager = assistManager;
- mAssistManager.onConfigurationChanged();
- }
-
public void setLightBarController(LightBarController lightBarController) {
mLightBarController = lightBarController;
mLightBarController.setNavigationBar(mNavigationBarView.getLightTransitionsController());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index b6feb0e..f04a9ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -365,7 +365,7 @@
}
@Override
- public void onPluginConnected(NavBarButtonProvider plugin) {
+ public void onPluginConnected(NavBarButtonProvider plugin, Context context) {
mPlugins.add(plugin);
clearViews();
inflateLayout(mCurrentLayout);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 31c78c8f..319f124 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -765,7 +765,7 @@
}
@Override
- public void onPluginConnected(NavGesture plugin) {
+ public void onPluginConnected(NavGesture plugin, Context context) {
mGestureHelper = plugin.getGestureHelper();
updateTaskSwitchHelper();
}
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 001edb3..fdf7296 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -20,6 +20,7 @@
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.windowStateToString;
+
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
@@ -71,13 +72,11 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
-import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -120,8 +119,9 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.BatteryMeterView;
+import com.android.systemui.ActivityStarterDelegate;
import com.android.systemui.DemoMode;
+import com.android.systemui.Dependency;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.Interpolators;
@@ -130,6 +130,7 @@
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.SystemUIFactory;
+import com.android.systemui.assist.AssistManager;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.doze.DozeHost;
@@ -138,7 +139,7 @@
import com.android.systemui.fragments.PluginFragmentListener;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.qs.QS.ActivityStarter;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.BaseStatusBarHeader;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanel;
@@ -168,27 +169,22 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
-import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.BatteryControllerImpl;
-import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
-import com.android.systemui.statusbar.policy.CastControllerImpl;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.EncryptionHelper;
-import com.android.systemui.statusbar.policy.FlashlightControllerImpl;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.HotspotControllerImpl;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
-import com.android.systemui.statusbar.policy.LocationControllerImpl;
import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl;
-import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.PreviewInflater;
-import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
-import com.android.systemui.statusbar.policy.SecurityControllerImpl;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -305,25 +301,8 @@
PhoneStatusBarPolicy mIconPolicy;
- // These are no longer handled by the policy, because we need custom strategies for them
- protected BluetoothControllerImpl mBluetoothController;
- SecurityControllerImpl mSecurityController;
- protected BatteryController mBatteryController;
- LocationControllerImpl mLocationController;
- NetworkControllerImpl mNetworkController;
- HotspotControllerImpl mHotspotController;
- RotationLockControllerImpl mRotationLockController;
- UserInfoControllerImpl mUserInfoController;
- protected ZenModeController mZenModeController;
- CastControllerImpl mCastController;
VolumeComponent mVolumeComponent;
- KeyguardUserSwitcher mKeyguardUserSwitcher;
- FlashlightControllerImpl mFlashlightController;
- protected UserSwitcherController mUserSwitcherController;
- NextAlarmControllerImpl mNextAlarmController;
- protected KeyguardMonitorImpl mKeyguardMonitor;
BrightnessMirrorController mBrightnessMirrorController;
- AccessibilityController mAccessibilityController;
protected FingerprintUnlockController mFingerprintUnlockController;
LightBarController mLightBarController;
protected LockscreenWallpaper mLockscreenWallpaper;
@@ -410,23 +389,16 @@
: null;
private ScreenPinningRequest mScreenPinningRequest;
- private HandlerThread mHandlerThread;
- private HandlerThread mTimeTickThread;
- private Handler mTimeTickHandler;
// ensure quick settings is disabled until the current user makes it through the setup wizard
private boolean mUserSetup = false;
- private ContentObserver mUserSetupObserver = new ContentObserver(new Handler()) {
+ private DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() {
@Override
- public void onChange(boolean selfChange) {
- final boolean userSetup = 0 != Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.USER_SETUP_COMPLETE,
- 0 /*default */,
- mCurrentUserId);
+ public void onUserSetupChanged() {
+ final boolean userSetup = mDeviceProvisionedController.isUserSetup(
+ mDeviceProvisionedController.getCurrentUser());
if (MULTIUSER_DEBUG) Log.d(TAG, String.format("User setup changed: " +
- "selfChange=%s userSetup=%s mUserSetup=%s",
- selfChange, userSetup, mUserSetup));
+ "userSetup=%s mUserSetup=%s", userSetup, mUserSetup));
if (userSetup != mUserSetup) {
mUserSetup = userSetup;
@@ -435,9 +407,7 @@
if (mKeyguardBottomArea != null) {
mKeyguardBottomArea.setUserSetupComplete(mUserSetup);
}
- if (mNetworkController != null) {
- mNetworkController.setUserSetupComplete(mUserSetup);
- }
+ updateQsExpansionEnabled();
}
if (mIconPolicy != null) {
mIconPolicy.setCurrentUserSetup(mUserSetup);
@@ -638,6 +608,13 @@
}
};
+ private KeyguardUserSwitcher mKeyguardUserSwitcher;
+ private UserSwitcherController mUserSwitcherController;
+ private NetworkController mNetworkController;
+ private KeyguardMonitorImpl mKeyguardMonitor;
+ private BatteryController mBatteryController;
+ private DeviceProvisionedController mDeviceProvisionedController;
+
private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
final int N = array.size();
for (int i = 0 ; i < N; i++) {
@@ -671,19 +648,20 @@
@Override
public void start() {
+ mNetworkController = Dependency.get(NetworkController.class);
+ mUserSwitcherController = Dependency.get(UserSwitcherController.class);
+ mKeyguardMonitor = (KeyguardMonitorImpl) Dependency.get(KeyguardMonitor.class);
+ mBatteryController = Dependency.get(BatteryController.class);
+ mAssistManager = Dependency.get(AssistManager.class);
+ mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
+
mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
updateDisplaySize();
mScrimSrcModeEnabled = mContext.getResources().getBoolean(
R.bool.config_status_bar_scrim_behind_use_src);
- // Background thread for any controllers that need it.
- mHandlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
- mHandlerThread.start();
- mTimeTickThread = new HandlerThread("TimeTick");
- mTimeTickThread.start();
- mTimeTickHandler = new Handler(mTimeTickThread.getLooper());
- DateTimeView.setReceiverHandler(mTimeTickHandler);
+ DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER));
putComponent(PhoneStatusBar.class, this);
super.start(); // calls createAndAddWindows()
@@ -694,9 +672,7 @@
// in session state
// Lastly, call to the icon policy to install/update all the icons.
- mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController, mCastController,
- mHotspotController, mUserInfoController, mBluetoothController,
- mRotationLockController, mNetworkController.getDataSaverController(), mNextAlarmController);
+ mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
mIconPolicy.setCurrentUserSetup(mUserSetup);
mSettingsObserver.onChange(false); // set up
@@ -717,12 +693,11 @@
mDozeServiceHost = new DozeServiceHost();
putComponent(DozeHost.class, mDozeServiceHost);
- setControllerUsers();
-
notifyUserAboutHiddenNotifications();
mScreenPinningRequest = new ScreenPinningRequest(mContext);
mFalsingManager = FalsingManager.getInstance(mContext);
+ Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(this);
}
protected void createIconController() {
@@ -796,8 +771,6 @@
// no window manager? good luck with that
}
- mAssistManager = SystemUIFactory.getInstance().createAssistManager(this, context);
-
// figure out which pixel-format to use for the status bar.
mPixelFormat = PixelFormat.OPAQUE;
@@ -828,9 +801,8 @@
(KeyguardStatusView) mStatusBarWindow.findViewById(R.id.keyguard_status_view);
mKeyguardBottomArea =
(KeyguardBottomAreaView) mStatusBarWindow.findViewById(R.id.keyguard_bottom_area);
- mKeyguardBottomArea.setActivityStarter(this);
- mKeyguardBottomArea.setAssistManager(mAssistManager);
- mKeyguardIndicationController = new KeyguardIndicationController(mContext,
+ mKeyguardIndicationController =
+ SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
(ViewGroup) mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
mKeyguardBottomArea.getLockIcon());
mKeyguardBottomArea.setKeyguardIndicationController(mKeyguardIndicationController);
@@ -840,7 +812,7 @@
createIconController();
- mBatteryController = createBatteryController();
+ // TODO: Find better place for this callback.
mBatteryController.addCallback(new BatteryStateChangeCallback() {
@Override
public void onPowerSaveChanged(boolean isPowerSave) {
@@ -856,7 +828,7 @@
}
});
- mLightBarController = new LightBarController(mIconController, mBatteryController);
+ mLightBarController = new LightBarController(mIconController);
if (mNavigationBar != null) {
mNavigationBar.setLightBarController(mLightBarController);
}
@@ -885,36 +857,12 @@
mNotificationPanel);
// Other icons
- mLocationController = new LocationControllerImpl(mContext,
- mHandlerThread.getLooper()); // will post a notification
- mNetworkController = new NetworkControllerImpl(mContext, mHandlerThread.getLooper());
- mNetworkController.setUserSetupComplete(mUserSetup);
- mHotspotController = new HotspotControllerImpl(mContext);
- mBluetoothController = new BluetoothControllerImpl(mContext, mHandlerThread.getLooper());
- mSecurityController = new SecurityControllerImpl(mContext);
- if (mContext.getResources().getBoolean(R.bool.config_showRotationLock)) {
- mRotationLockController = new RotationLockControllerImpl(mContext);
- }
- mUserInfoController = new UserInfoControllerImpl(mContext);
mVolumeComponent = getComponent(VolumeComponent.class);
- if (mVolumeComponent != null) {
- mZenModeController = mVolumeComponent.getZenController();
- }
- mCastController = new CastControllerImpl(mContext);
- initSignalCluster(mStatusBarView);
- initSignalCluster(mKeyguardStatusBar);
initEmergencyCryptkeeperText();
- mFlashlightController = new FlashlightControllerImpl(mContext);
- mKeyguardBottomArea.setFlashlightController(mFlashlightController);
mKeyguardBottomArea.setPhoneStatusBar(this);
mKeyguardBottomArea.setUserSetupComplete(mUserSetup);
- mAccessibilityController = new AccessibilityController(mContext);
- mKeyguardBottomArea.setAccessibilityController(mAccessibilityController);
- mNextAlarmController = new NextAlarmControllerImpl(mContext);
- mKeyguardMonitor = new KeyguardMonitorImpl(mContext);
- mUserSwitcherController = createUserSwitcherController();
if (UserManager.get(mContext).isUserSwitcherEnabled()) {
createUserSwitcher();
}
@@ -923,15 +871,13 @@
View container = mStatusBarWindow.findViewById(R.id.qs_frame);
if (container != null) {
FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
- new PluginFragmentListener(container, QS.TAG, R.id.qs_frame, QSFragment.class, QS.class)
+ fragmentHostManager.getFragmentManager().beginTransaction()
+ .replace(R.id.qs_frame, new QSFragment(), QS.TAG)
+ .commit();
+ new PluginFragmentListener(container, QS.TAG, QSFragment.class, QS.class)
.startListening(QS.ACTION, QS.VERSION);
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
- mBluetoothController, mLocationController, mRotationLockController,
- mNetworkController, mZenModeController, mHotspotController,
- mCastController, mFlashlightController,
- mUserSwitcherController, mUserInfoController, mKeyguardMonitor,
- mSecurityController, mBatteryController, mIconController,
- mNextAlarmController);
+ mIconController);
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
@@ -941,21 +887,9 @@
mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
mKeyguardStatusBar.setQSPanel(mQSPanel);
}
- mHeader = qs.getHeader();
- initSignalCluster(mHeader);
- mHeader.setActivityStarter(PhoneStatusBar.this);
});
}
- // User info. Trigger first load.
- mKeyguardStatusBar.setUserInfoController(mUserInfoController);
- mKeyguardStatusBar.setUserSwitcherController(mUserSwitcherController);
- mUserInfoController.reloadUserInfo();
-
- ((BatteryMeterView) mStatusBarView.findViewById(R.id.battery)).setBatteryController(
- mBatteryController);
- mKeyguardStatusBar.setBatteryController(mBatteryController);
-
mReportRejectedTouch = mStatusBarWindow.findViewById(R.id.report_rejected_touch);
if (mReportRejectedTouch != null) {
updateReportRejectedTouchVisibility();
@@ -1016,7 +950,8 @@
android.Manifest.permission.DUMP, null);
// listen for USER_SETUP_COMPLETE setting (per-user)
- resetUserSetupObserver();
+ mDeviceProvisionedController.addCallback(mUserSetupObserver);
+ mUserSetupObserver.onUserSetupChanged();
// disable profiling bars, since they overlap and clutter the output on app windows
ThreadedRenderer.overrideProperty("disableProfileBars", "true");
@@ -1027,21 +962,9 @@
return mStatusBarView;
}
- public Handler getTimeTickHandler() {
- return mTimeTickHandler;
- }
-
- public static Handler getTimeTickHandler(Context context) {
- PhoneStatusBar statusBar = ((SysUiServiceProvider) context.getApplicationContext())
- .getComponent(PhoneStatusBar.class);
- return statusBar != null ? statusBar.getTimeTickHandler() :
- new Handler(Looper.getMainLooper());
- }
-
protected void createNavigationBar() {
mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
mNavigationBar = (NavigationBarFragment) fragment;
- mNavigationBar.setAssistManager(mAssistManager);
if (mLightBarController != null) {
mNavigationBar.setLightBarController(mLightBarController);
}
@@ -1067,10 +990,6 @@
}
}
- protected BatteryController createBatteryController() {
- return new BatteryControllerImpl(mContext);
- }
-
private void inflateShelf() {
mNotificationShelf =
(NotificationShelf) LayoutInflater.from(mContext).inflate(
@@ -1096,10 +1015,10 @@
inflateEmptyShadeView();
updateEmptyShadeView();
mStatusBarKeyguardViewManager.onDensityOrFontScaleChanged();
- mUserInfoController.onDensityOrFontScaleChanged();
- if (mUserSwitcherController != null) {
- mUserSwitcherController.onDensityOrFontScaleChanged();
- }
+ // TODO: Bring these out of PhoneStatusBar.
+ ((UserInfoControllerImpl) Dependency.get(UserInfoController.class))
+ .onDensityOrFontScaleChanged();
+ Dependency.get(UserSwitcherController.class).onDensityOrFontScaleChanged();
if (mKeyguardUserSwitcher != null) {
mKeyguardUserSwitcher.onDensityOrFontScaleChanged();
}
@@ -1129,8 +1048,6 @@
R.dimen.signal_cluster_margin_start),
0, 0, 0);
newCluster.setLayoutParams(layoutParams);
- newCluster.setSecurityController(mSecurityController);
- newCluster.setNetworkController(mNetworkController);
viewParent.addView(newCluster, index);
return newCluster;
}
@@ -1161,11 +1078,7 @@
protected void createUserSwitcher() {
mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
(ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher),
- mKeyguardStatusBar, mNotificationPanel, mUserSwitcherController);
- }
-
- protected UserSwitcherController createUserSwitcherController() {
- return new UserSwitcherController(mContext, mKeyguardMonitor, mHandler, this);
+ mKeyguardStatusBar, mNotificationPanel);
}
protected void inflateStatusBarWindow(Context context) {
@@ -1173,15 +1086,6 @@
R.layout.super_status_bar, null);
}
- protected void initSignalCluster(View containerView) {
- SignalClusterView signalCluster =
- (SignalClusterView) containerView.findViewById(R.id.signal_cluster);
- if (signalCluster != null) {
- signalCluster.setSecurityController(mSecurityController);
- signalCluster.setNetworkController(mNetworkController);
- }
- }
-
public void clearAllNotifications() {
// animate-swipe all dismissable notifications, then animate the shade closed
@@ -1276,6 +1180,8 @@
mFingerprintUnlockController);
mKeyguardIndicationController.setStatusBarKeyguardViewManager(
mStatusBarKeyguardViewManager);
+ mKeyguardIndicationController.setUserInfoController(
+ Dependency.get(UserInfoController.class));
mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mIconPolicy.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mRemoteInputController.addCallback(mStatusBarKeyguardViewManager);
@@ -1485,7 +1391,7 @@
newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
- sbn.getOpPkg(), sbn.getNotificationChannel(),
+ sbn.getOpPkg(),
sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
@@ -2383,6 +2289,11 @@
}
@Override
+ public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade) {
+ startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade);
+ }
+
+ @Override
public void startActivity(Intent intent, boolean dismissShade, Callback callback) {
startActivityDismissingKeyguard(intent, false, dismissShade, callback);
}
@@ -3229,30 +3140,7 @@
if (mStatusBarWindowManager != null) {
mStatusBarWindowManager.dump(fd, pw, args);
}
- if (mNetworkController != null) {
- mNetworkController.dump(fd, pw, args);
- }
- if (mBluetoothController != null) {
- mBluetoothController.dump(fd, pw, args);
- }
- if (mHotspotController != null) {
- mHotspotController.dump(fd, pw, args);
- }
- if (mCastController != null) {
- mCastController.dump(fd, pw, args);
- }
- if (mUserSwitcherController != null) {
- mUserSwitcherController.dump(fd, pw, args);
- }
- if (mBatteryController != null) {
- mBatteryController.dump(fd, pw, args);
- }
- if (mNextAlarmController != null) {
- mNextAlarmController.dump(fd, pw, args);
- }
- if (mSecurityController != null) {
- mSecurityController.dump(fd, pw, args);
- }
+
if (mHeadsUpManager != null) {
mHeadsUpManager.dump(fd, pw, args);
} else {
@@ -3266,9 +3154,6 @@
if (KeyguardUpdateMonitor.getInstance(mContext) != null) {
KeyguardUpdateMonitor.getInstance(mContext).dump(fd, pw, args);
}
- if (mFlashlightController != null) {
- mFlashlightController.dump(fd, pw, args);
- }
FalsingManager.getInstance(mContext).dump(pw);
FalsingLog.dump(pw);
@@ -3495,7 +3380,6 @@
updateRowStates();
mScreenPinningRequest.onConfigurationChanged();
- mNetworkController.onConfigurationChanged();
}
@Override
@@ -3505,8 +3389,6 @@
animateCollapsePanels();
updatePublicMode();
updateNotifications();
- resetUserSetupObserver();
- setControllerUsers();
clearCurrentMediaNotification();
setLockscreenUser(newUserId);
}
@@ -3517,26 +3399,6 @@
updateMediaMetaData(true, false);
}
- private void setControllerUsers() {
- if (mZenModeController != null) {
- mZenModeController.setUserId(mCurrentUserId);
- }
- if (mSecurityController != null) {
- mSecurityController.onUserSwitched(mCurrentUserId);
- }
- if (mNetworkController != null) {
- mNetworkController.onUserSwitched(mCurrentUserId);
- }
- }
-
- private void resetUserSetupObserver() {
- mContext.getContentResolver().unregisterContentObserver(mUserSetupObserver);
- mUserSetupObserver.onChange(false);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), true,
- mUserSetupObserver, mCurrentUserId);
- }
-
/**
* Reload some of our resources when the configuration changes.
*
@@ -3717,32 +3579,23 @@
}
};
+ @Override
public void postQSRunnableDismissingKeyguard(final Runnable runnable) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mLeaveOpenOnKeyguardHide = true;
- executeRunnableDismissingKeyguard(runnable, null, false, false, false);
- }
+ mHandler.post(() -> {
+ mLeaveOpenOnKeyguardHide = true;
+ executeRunnableDismissingKeyguard(runnable, null, false, false, false);
});
}
+ @Override
public void postStartActivityDismissingKeyguard(final PendingIntent intent) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- startPendingIntentDismissingKeyguard(intent);
- }
- });
+ mHandler.post(() -> startPendingIntentDismissingKeyguard(intent));
}
+ @Override
public void postStartActivityDismissingKeyguard(final Intent intent, int delay) {
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- handleStartActivityDismissingKeyguard(intent, true /*onlyProvisioned*/);
- }
- }, delay);
+ mHandler.postDelayed(() ->
+ handleStartActivityDismissingKeyguard(intent, true /*onlyProvisioned*/), delay);
}
private void handleStartActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned) {
@@ -3794,10 +3647,6 @@
mWindowManager.removeViewImmediate(mNavigationBarView);
mNavigationBarView = null;
}
- if (mHandlerThread != null) {
- mHandlerThread.quitSafely();
- mHandlerThread = null;
- }
mContext.unregisterReceiver(mBroadcastReceiver);
mContext.unregisterReceiver(mDemoReceiver);
mAssistManager.destroy();
@@ -3808,12 +3657,11 @@
(SignalClusterView) mKeyguardStatusBar.findViewById(R.id.signal_cluster);
final SignalClusterView signalClusterQs =
(SignalClusterView) mHeader.findViewById(R.id.signal_cluster);
- mNetworkController.removeCallback(signalCluster);
- mNetworkController.removeCallback(signalClusterKeyguard);
- mNetworkController.removeCallback(signalClusterQs);
if (mQSPanel != null && mQSPanel.getHost() != null) {
mQSPanel.getHost().destroy();
}
+ Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(null);
+ mDeviceProvisionedController.removeCallback(mUserSetupObserver);
}
private boolean mDemoModeAllowed;
@@ -4759,12 +4607,13 @@
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();
@@ -4988,7 +4837,7 @@
@Override
public boolean isPowerSaveActive() {
- return mBatteryController != null && mBatteryController.isPowerSave();
+ return mBatteryController.isPowerSave();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 9ee1e8f..1044ecf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -36,6 +36,7 @@
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.TelephonyIntents;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
@@ -99,22 +100,19 @@
private BluetoothController mBluetooth;
- public PhoneStatusBarPolicy(Context context, StatusBarIconController iconController,
- CastController cast, HotspotController hotspot, UserInfoController userInfoController,
- BluetoothController bluetooth, RotationLockController rotationLockController,
- DataSaverController dataSaver, NextAlarmController nextAlarm) {
+ public PhoneStatusBarPolicy(Context context, StatusBarIconController iconController) {
mContext = context;
mIconController = iconController;
- mCast = cast;
- mHotspot = hotspot;
- mBluetooth = bluetooth;
+ mCast = Dependency.get(CastController.class);
+ mHotspot = Dependency.get(HotspotController.class);
+ mBluetooth = Dependency.get(BluetoothController.class);
mBluetooth.addCallback(this);
- mNextAlarm = nextAlarm;
+ mNextAlarm = Dependency.get(NextAlarmController.class);
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- mUserInfoController = userInfoController;
+ mUserInfoController = Dependency.get(UserInfoController.class);
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mRotationLockController = rotationLockController;
- mDataSaver = dataSaver;
+ mRotationLockController = Dependency.get(RotationLockController.class);
+ mDataSaver = Dependency.get(DataSaverController.class);
mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index d4cf533..2f76cb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -17,15 +17,11 @@
package com.android.systemui.statusbar.phone;
import android.app.ActivityManager;
-import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -33,8 +29,8 @@
import android.service.quicksettings.Tile;
import android.text.TextUtils;
import android.util.Log;
-import android.view.View;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.external.CustomTile;
@@ -58,20 +54,6 @@
import com.android.systemui.qs.tiles.UserTile;
import com.android.systemui.qs.tiles.WifiTile;
import com.android.systemui.qs.tiles.WorkModeTile;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.FlashlightController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -80,7 +62,6 @@
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
/** Platform implementation of the quick settings tile host **/
public class QSTileHost implements QSTile.Host, Tunable {
@@ -93,81 +74,31 @@
private final PhoneStatusBar mStatusBar;
private final LinkedHashMap<String, QSTile<?>> mTiles = new LinkedHashMap<>();
protected final ArrayList<String> mTileSpecs = new ArrayList<>();
- private final BluetoothController mBluetooth;
- private final LocationController mLocation;
- private final RotationLockController mRotation;
- private final NetworkController mNetwork;
- private final ZenModeController mZen;
- private final HotspotController mHotspot;
- private final CastController mCast;
- private final Looper mLooper;
- private final FlashlightController mFlashlight;
- private final UserSwitcherController mUserSwitcherController;
- private final UserInfoController mUserInfoController;
- private final KeyguardMonitor mKeyguard;
- private final SecurityController mSecurity;
- private final BatteryController mBattery;
- private final StatusBarIconController mIconController;
private final TileServices mServices;
private final List<Callback> mCallbacks = new ArrayList<>();
private final AutoTileManager mAutoTiles;
- private final ManagedProfileController mProfileController;
- private final NextAlarmController mNextAlarmController;
- private final HandlerThread mHandlerThread;
- private View mHeader;
+ private final StatusBarIconController mIconController;
private int mCurrentUser;
public QSTileHost(Context context, PhoneStatusBar statusBar,
- BluetoothController bluetooth, LocationController location,
- RotationLockController rotation, NetworkController network,
- ZenModeController zen, HotspotController hotspot,
- CastController cast, FlashlightController flashlight,
- UserSwitcherController userSwitcher, UserInfoController userInfo,
- KeyguardMonitor keyguard, SecurityController security,
- BatteryController battery, StatusBarIconController iconController,
- NextAlarmController nextAlarmController) {
+ StatusBarIconController iconController) {
+ mIconController = iconController;
mContext = context;
mStatusBar = statusBar;
- mBluetooth = bluetooth;
- mLocation = location;
- mRotation = rotation;
- mNetwork = network;
- mZen = zen;
- mHotspot = hotspot;
- mCast = cast;
- mFlashlight = flashlight;
- mUserSwitcherController = userSwitcher;
- mUserInfoController = userInfo;
- mKeyguard = keyguard;
- mSecurity = security;
- mBattery = battery;
- mIconController = iconController;
- mNextAlarmController = nextAlarmController;
- mProfileController = new ManagedProfileControllerImpl(this);
- mHandlerThread = new HandlerThread(QSTileHost.class.getSimpleName(),
- Process.THREAD_PRIORITY_BACKGROUND);
- mHandlerThread.start();
- mLooper = mHandlerThread.getLooper();
-
- mServices = new TileServices(this, mLooper);
+ mServices = new TileServices(this, Dependency.get(Dependency.BG_LOOPER));
TunerService.get(mContext).addTunable(this, TILES_SETTING);
// AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
mAutoTiles = new AutoTileManager(context, this);
}
- public NextAlarmController getNextAlarmController() {
- return mNextAlarmController;
- }
-
- public void setHeaderView(View view) {
- mHeader = view;
+ public StatusBarIconController getIconController() {
+ return mIconController;
}
public void destroy() {
- mHandlerThread.quitSafely();
mTiles.values().forEach(tile -> tile.destroy());
mAutoTiles.destroy();
TunerService.get(mContext).removeTunable(this);
@@ -190,30 +121,10 @@
}
@Override
- public void startActivityDismissingKeyguard(final Intent intent) {
- mStatusBar.postStartActivityDismissingKeyguard(intent, 0);
- }
-
- @Override
- public void startActivityDismissingKeyguard(PendingIntent intent) {
- mStatusBar.postStartActivityDismissingKeyguard(intent);
- }
-
- @Override
- public void startRunnableDismissingKeyguard(Runnable runnable) {
- mStatusBar.postQSRunnableDismissingKeyguard(runnable);
- }
-
- @Override
public void warn(String message, Throwable t) {
// already logged
}
- public void animateToggleQSExpansion() {
- // TODO: Better path to animated panel expansion.
- mHeader.callOnClick();
- }
-
@Override
public void collapsePanels() {
mStatusBar.postAnimateCollapsePanels();
@@ -225,91 +136,15 @@
}
@Override
- public Looper getLooper() {
- return mLooper;
- }
-
- @Override
public Context getContext() {
return mContext;
}
- @Override
- public BluetoothController getBluetoothController() {
- return mBluetooth;
- }
-
- @Override
- public LocationController getLocationController() {
- return mLocation;
- }
-
- @Override
- public RotationLockController getRotationLockController() {
- return mRotation;
- }
-
- @Override
- public NetworkController getNetworkController() {
- return mNetwork;
- }
-
- @Override
- public ZenModeController getZenModeController() {
- return mZen;
- }
-
- @Override
- public HotspotController getHotspotController() {
- return mHotspot;
- }
-
- @Override
- public CastController getCastController() {
- return mCast;
- }
-
- @Override
- public FlashlightController getFlashlightController() {
- return mFlashlight;
- }
-
- @Override
- public KeyguardMonitor getKeyguardMonitor() {
- return mKeyguard;
- }
-
- @Override
- public UserSwitcherController getUserSwitcherController() {
- return mUserSwitcherController;
- }
-
- @Override
- public UserInfoController getUserInfoController() {
- return mUserInfoController;
- }
-
- @Override
- public BatteryController getBatteryController() {
- return mBattery;
- }
-
- public SecurityController getSecurityController() {
- return mSecurity;
- }
public TileServices getTileServices() {
return mServices;
}
- public StatusBarIconController getIconController() {
- return mIconController;
- }
-
- public ManagedProfileController getManagedProfileController() {
- return mProfileController;
- }
-
@Override
public void onTuningChanged(String key, String newValue) {
if (!TILES_SETTING.equals(key)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 2fa961d..9e93802 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -40,10 +40,11 @@
import com.android.internal.logging.nano.MetricsProto;
import com.android.keyguard.KeyguardStatusView;
import com.android.settingslib.Utils;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.BatteryMeterView;
+import com.android.systemui.Dependency;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QS.ActivityStarter;
import com.android.systemui.plugins.qs.QS.BaseStatusBarHeader;
import com.android.systemui.plugins.qs.QS.Callback;
import com.android.systemui.qs.QSPanel;
@@ -51,10 +52,9 @@
import com.android.systemui.qs.TouchAnimator;
import com.android.systemui.qs.TouchAnimator.Builder;
import com.android.systemui.statusbar.SignalClusterView;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
+import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
import com.android.systemui.statusbar.policy.UserInfoController;
@@ -71,6 +71,7 @@
private ActivityStarter mActivityStarter;
private NextAlarmController mNextAlarmController;
+ private UserInfoController mUserInfoController;
private SettingsButton mSettingsButton;
protected View mSettingsContainer;
@@ -118,7 +119,8 @@
mEdit = findViewById(android.R.id.edit);
findViewById(android.R.id.edit).setOnClickListener(view ->
- mHost.startRunnableDismissingKeyguard(() -> mQsPanel.showEdit(view)));
+ Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() ->
+ mQsPanel.showEdit(view)));
mDateTimeAlarmGroup = (ViewGroup) findViewById(R.id.date_time_alarm_group);
mDateTimeAlarmGroup.findViewById(R.id.empty_time_view).setVisibility(View.GONE);
@@ -151,6 +153,15 @@
((RippleDrawable) mExpandIndicator.getBackground()).setForceSoftware(true);
updateResources();
+
+ // Set the light/dark theming on the header status UI to match the current theme.
+ SignalClusterView cluster = (SignalClusterView) findViewById(R.id.signal_cluster);
+ int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground);
+ float intensity = colorForeground / (float) Color.WHITE;
+ cluster.setIconTint(colorForeground, intensity, new Rect(0, 0, 0, 0));
+ BatteryMeterView battery = (BatteryMeterView) findViewById(R.id.battery);
+ int colorSecondary = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary);
+ battery.setRawColors(colorForeground, colorSecondary);
}
@Override
@@ -248,11 +259,18 @@
mExpandIndicator.setExpanded(headerExpansionFraction > EXPAND_INDICATOR_THRESHOLD);
}
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mNextAlarmController = Dependency.get(NextAlarmController.class);
+ mUserInfoController = Dependency.get(UserInfoController.class);
+ mActivityStarter = Dependency.get(ActivityStarter.class);
+ }
+
+ @Override
@VisibleForTesting
public void onDetachedFromWindow() {
setListening(false);
- mHost.getUserInfoController().removeCallback(this);
- mHost.getNetworkController().removeEmergencyListener(this);
super.onDetachedFromWindow();
}
@@ -304,16 +322,17 @@
private void updateListeners() {
if (mListening) {
mNextAlarmController.addCallback(this);
+ mUserInfoController.addCallback(this);
+ if (Dependency.get(NetworkController.class).hasVoiceCallingFeature()) {
+ Dependency.get(NetworkController.class).addEmergencyListener(this);
+ }
} else {
mNextAlarmController.removeCallback(this);
+ mUserInfoController.removeCallback(this);
+ Dependency.get(NetworkController.class).removeEmergencyListener(this);
}
}
- @Override
- public void setActivityStarter(ActivityStarter activityStarter) {
- mActivityStarter = activityStarter;
- }
-
public void setQSPanel(final QSPanel qsPanel) {
mQsPanel = qsPanel;
setupHost(qsPanel.getHost());
@@ -324,30 +343,9 @@
public void setupHost(final QSTileHost host) {
mHost = host;
- host.setHeaderView(mExpandIndicator);
+ //host.setHeaderView(mExpandIndicator);
mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this);
mHeaderQsPanel.setHost(host, null /* No customization in header */);
- setUserInfoController(host.getUserInfoController());
- setBatteryController(host.getBatteryController());
- setNextAlarmController(host.getNextAlarmController());
-
- final boolean isAPhone = mHost.getNetworkController().hasVoiceCallingFeature();
- if (isAPhone) {
- mHost.getNetworkController().addEmergencyListener(this);
- }
-
- // Set the light/dark theming on the header status UI to match the current theme.
- SignalClusterView cluster = (SignalClusterView) findViewById(R.id.signal_cluster);
- cluster.setNetworkController((NetworkControllerImpl) host.getNetworkController());
- cluster.setSecurityController(host.getSecurityController());
- int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground);
- float intensity = colorForeground / (float) Color.WHITE;
- cluster.setIconTint(colorForeground, intensity,
- new Rect(0, 0, 0, 0));
- BatteryMeterView battery = (BatteryMeterView) findViewById(R.id.battery);
- battery.setBatteryController(host.getBatteryController());
- int colorSecondary = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary);
- battery.setRawColors(colorForeground, colorSecondary);
}
@Override
@@ -357,7 +355,7 @@
mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
: MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
if (mSettingsButton.isTunerClick()) {
- mHost.startRunnableDismissingKeyguard(() -> post(() -> {
+ Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
if (TunerService.isTunerEnabled(mContext)) {
TunerService.showResetRequest(mContext, () -> {
// Relaunch settings so that the tuner disappears.
@@ -370,7 +368,7 @@
}
startSettingsActivity();
- }));
+ });
} else {
startSettingsActivity();
}
@@ -385,18 +383,6 @@
true /* dismissShade */);
}
- public void setNextAlarmController(NextAlarmController nextAlarmController) {
- mNextAlarmController = nextAlarmController;
- }
-
- public void setBatteryController(BatteryController batteryController) {
- batteryController.addCallback(this);
- }
-
- public void setUserInfoController(UserInfoController userInfoController) {
- userInfoController.addCallback(this);
- }
-
@Override
public void setCallback(Callback qsPanelCallback) {
mHeaderQsPanel.setCallback(qsPanelCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 1b73a3f..aa29e43 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 @@
if (mNotificationPanel.isFullyExpanded()
&& mStackScrollLayout.getVisibility() == View.VISIBLE
&& mService.getBarState() == StatusBarState.KEYGUARD
- && !mService.isBouncerShowing()) {
+ && !mService.isBouncerShowing()
+ && !mService.isDozing()) {
intercept = mDragDownHelper.onInterceptTouchEvent(ev);
- // wake up on a touch down event, if dozing
- if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mService.wakeUpIfDozing(ev.getEventTime(), ev);
- }
}
if (!intercept) {
super.onInterceptTouchEvent(ev);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 19dcf03..48ff1c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -17,12 +17,13 @@
package com.android.systemui.statusbar.policy;
import com.android.systemui.DemoMode;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-public interface BatteryController extends DemoMode,
+public interface BatteryController extends DemoMode, Dumpable,
CallbackController<BatteryStateChangeCallback> {
/**
* Prints the current state of the {@link BatteryController} to the given {@link PrintWriter}.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index 4c1c378..df30e20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -17,11 +17,12 @@
package com.android.systemui.statusbar.policy;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.BluetoothController.Callback;
import java.util.Collection;
-public interface BluetoothController extends CallbackController<Callback> {
+public interface BluetoothController extends CallbackController<Callback>, Dumpable {
boolean isBluetoothSupported();
boolean isBluetoothEnabled();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
index 3142ddf..e7056a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -47,7 +47,7 @@
private final ArrayList<SignalCallback> mSignalCallbacks = new ArrayList<>();
public CallbackHandler() {
- super();
+ super(Looper.getMainLooper());
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
index 6988af7..97be6ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
@@ -16,11 +16,12 @@
package com.android.systemui.statusbar.policy;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.CastController.Callback;
import java.util.Set;
-public interface CastController extends CallbackController<Callback> {
+public interface CastController extends CallbackController<Callback>, Dumpable {
void setDiscovering(boolean request);
void setCurrentUserId(int currentUserId);
Set<CastDevice> getCastDevices();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 1dbc664..9cc9749 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -38,8 +38,8 @@
import android.widget.TextView;
import com.android.systemui.DemoMode;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -107,7 +107,7 @@
filter.addAction(Intent.ACTION_USER_SWITCHED);
getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter,
- null, PhoneStatusBar.getTimeTickHandler(getContext()));
+ null, Dependency.get(Dependency.TIME_TICK_HANDLER));
TunerService.get(getContext()).addTunable(this, CLOCK_SECONDS,
StatusBarIconController.ICON_BLACKLIST);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
index 5544c70..dc33633 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
@@ -23,12 +23,11 @@
import android.content.res.TypedArray;
import android.icu.text.DateFormat;
import android.icu.text.DisplayContext;
-import android.os.Handler;
import android.util.AttributeSet;
import android.widget.TextView;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
import java.util.Date;
import java.util.Locale;
@@ -87,7 +86,7 @@
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
getContext().registerReceiver(mIntentReceiver, filter, null,
- PhoneStatusBar.getTimeTickHandler(getContext()));
+ Dependency.get(Dependency.TIME_TICK_HANDLER));
updateClock();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
new file mode 100644
index 0000000..aa4eaa7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.Context;
+
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+
+public interface DeviceProvisionedController extends CallbackController<DeviceProvisionedListener> {
+
+ boolean isDeviceProvisioned();
+ boolean isUserSetup(int currentUser);
+ int getCurrentUser();
+
+ interface DeviceProvisionedListener {
+ default void onDeviceProvisionedChanged() { }
+ default void onUserSwitched() { }
+ default void onUserSetupChanged() { }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
new file mode 100644
index 0000000..528fefe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings.Global;
+import android.provider.Settings.Secure;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.settings.CurrentUserTracker;
+
+import java.util.ArrayList;
+
+public class DeviceProvisionedControllerImpl extends CurrentUserTracker implements
+ DeviceProvisionedController {
+
+ private final ArrayList<DeviceProvisionedListener> mListeners = new ArrayList<>();
+ private final ContentResolver mContentResolver;
+ private final Context mContext;
+ private final Uri mDeviceProvisionedUri;
+ private final Uri mUserSetupUri;
+
+ public DeviceProvisionedControllerImpl(Context context) {
+ super(context);
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ mDeviceProvisionedUri = Global.getUriFor(Global.DEVICE_PROVISIONED);
+ mUserSetupUri = Secure.getUriFor(Secure.USER_SETUP_COMPLETE);
+ }
+
+ @Override
+ public boolean isDeviceProvisioned() {
+ return Global.getInt(mContentResolver, Global.DEVICE_PROVISIONED, 0) != 0;
+ }
+
+ @Override
+ public boolean isUserSetup(int currentUser) {
+ return Secure.getIntForUser(mContentResolver, Secure.USER_SETUP_COMPLETE, 0, currentUser)
+ != 0;
+ }
+
+ @Override
+ public int getCurrentUser() {
+ return ActivityManager.getCurrentUser();
+ }
+
+ @Override
+ public void addCallback(DeviceProvisionedListener listener) {
+ mListeners.add(listener);
+ if (mListeners.size() == 1) {
+ startListening(getCurrentUser());
+ }
+ }
+
+ @Override
+ public void removeCallback(DeviceProvisionedListener listener) {
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ stopListening();
+ }
+ }
+
+ private void startListening(int user) {
+ mContentResolver.registerContentObserver(mDeviceProvisionedUri, true,
+ mSettingsObserver, 0);
+ mContentResolver.registerContentObserver(mUserSetupUri, true,
+ mSettingsObserver, user);
+ startTracking();
+ }
+
+ private void stopListening() {
+ stopTracking();
+ mContentResolver.unregisterContentObserver(mSettingsObserver);
+ }
+
+ @Override
+ public void onUserSwitched(int newUserId) {
+ mContentResolver.unregisterContentObserver(mSettingsObserver);
+ mContentResolver.registerContentObserver(mDeviceProvisionedUri, true,
+ mSettingsObserver, 0);
+ mContentResolver.registerContentObserver(mUserSetupUri, true,
+ mSettingsObserver, newUserId);
+ notifyUserChanged();
+ }
+
+ private void notifyUserChanged() {
+ mListeners.forEach(c -> c.onUserSwitched());
+ }
+
+ private void notifySetupChanged() {
+ mListeners.forEach(c -> c.onUserSetupChanged());
+ }
+
+ private void notifyProvisionedChanged() {
+ mListeners.forEach(c -> c.onDeviceProvisionedChanged());
+ }
+
+ protected final ContentObserver mSettingsObserver = new ContentObserver(Dependency.get(
+ Dependency.MAIN_HANDLER)) {
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ if (mUserSetupUri.equals(uri)) {
+ notifySetupChanged();
+ } else {
+ notifyProvisionedChanged();
+ }
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
index 6023f3e..e576f36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
@@ -14,9 +14,10 @@
package com.android.systemui.statusbar.policy;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
-public interface FlashlightController extends CallbackController<FlashlightListener> {
+public interface FlashlightController extends CallbackController<FlashlightListener>, Dumpable {
boolean hasFlashlight();
void setFlashlight(boolean newState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
index daf9d6b..0543678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
@@ -16,9 +16,10 @@
package com.android.systemui.statusbar.policy;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.HotspotController.Callback;
-public interface HotspotController extends CallbackController<Callback> {
+public interface HotspotController extends CallbackController<Callback>, Dumpable {
boolean isHotspotEnabled();
void setHotspotEnabled(boolean enabled);
boolean isHotspotSupported();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index 1cf4050..4b283fed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -30,6 +30,7 @@
import android.widget.FrameLayout;
import com.android.settingslib.animation.AppearAnimationUtils;
+import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.qs.tiles.UserDetailItemView;
@@ -56,10 +57,10 @@
private boolean mAnimating;
public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
- KeyguardStatusBarView statusBarView, NotificationPanelView panelView,
- UserSwitcherController userSwitcherController) {
+ KeyguardStatusBarView statusBarView, NotificationPanelView panelView) {
boolean keyguardUserSwitcherEnabled =
context.getResources().getBoolean(R.bool.config_keyguardUserSwitcher) || ALWAYS_ON;
+ UserSwitcherController userSwitcherController = Dependency.get(UserSwitcherController.class);
if (userSwitcherController != null && keyguardUserSwitcherEnabled) {
mUserSwitcherContainer = (Container) userSwitcher.inflate();
mBackground = new KeyguardUserSwitcherScrim(context);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 082fe82..a22fc6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -21,17 +21,18 @@
import android.telephony.SubscriptionInfo;
import com.android.settingslib.net.DataUsageController;
import com.android.settingslib.wifi.AccessPoint;
+import com.android.systemui.DemoMode;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import java.util.List;
-public interface NetworkController extends CallbackController<SignalCallback> {
+public interface NetworkController extends CallbackController<SignalCallback>, DemoMode {
boolean hasMobileDataFeature();
void addCallback(SignalCallback cb);
void removeCallback(SignalCallback cb);
void setWifiEnabled(boolean enabled);
- void onUserSwitched(int newUserId);
AccessPointController getAccessPointController();
DataUsageController getMobileDataController();
DataSaverController getDataSaverController();
@@ -40,6 +41,9 @@
void addEmergencyListener(EmergencyListener listener);
void removeEmergencyListener(EmergencyListener listener);
+ void setUserSetupComplete(boolean userSetup);
+ boolean hasEmergencyCryptKeeperText();
+ boolean isRadioOn();
public interface SignalCallback {
default void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index a7fab41..edf2c8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
@@ -42,8 +43,12 @@
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.settingslib.net.DataUsageController;
+import com.android.systemui.ConfigurationChangedReceiver;
import com.android.systemui.DemoMode;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
+import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -60,7 +65,8 @@
/** Platform implementation of the network controller. **/
public class NetworkControllerImpl extends BroadcastReceiver
- implements NetworkController, DemoMode, DataUsageController.NetworkNameProvider {
+ implements NetworkController, DemoMode, DataUsageController.NetworkNameProvider,
+ ConfigurationChangedReceiver, Dumpable {
// debug
static final String TAG = "NetworkController";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -80,6 +86,7 @@
private final boolean mHasMobileDataFeature;
private final SubscriptionDefaults mSubDefaults;
private final DataSaverController mDataSaverController;
+ private final CurrentUserTracker mUserTracker;
private Config mConfig;
// Subcontrollers.
@@ -135,7 +142,8 @@
/**
* Construct this controller object and register for updates.
*/
- public NetworkControllerImpl(Context context, Looper bgLooper) {
+ public NetworkControllerImpl(Context context, Looper bgLooper,
+ DeviceProvisionedController deviceProvisionedController) {
this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
(WifiManager) context.getSystemService(Context.WIFI_SERVICE),
@@ -143,7 +151,8 @@
new CallbackHandler(),
new AccessPointControllerImpl(context, bgLooper),
new DataUsageController(context),
- new SubscriptionDefaults());
+ new SubscriptionDefaults(),
+ deviceProvisionedController);
mReceiverHandler.post(mRegisterListeners);
}
@@ -154,7 +163,8 @@
CallbackHandler callbackHandler,
AccessPointControllerImpl accessPointController,
DataUsageController dataUsageController,
- SubscriptionDefaults defaultsHandler) {
+ SubscriptionDefaults defaultsHandler,
+ DeviceProvisionedController deviceProvisionedController) {
mContext = context;
mConfig = config;
mReceiverHandler = new Handler(bgLooper);
@@ -191,6 +201,20 @@
// AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
updateAirplaneMode(true /* force callback */);
+ mUserTracker = new CurrentUserTracker(mContext) {
+ @Override
+ public void onUserSwitched(int newUserId) {
+ NetworkControllerImpl.this.onUserSwitched(newUserId);
+ }
+ };
+ mUserTracker.startTracking();
+ deviceProvisionedController.addCallback(new DeviceProvisionedListener() {
+ @Override
+ public void onUserSetupChanged() {
+ setUserSetupComplete(deviceProvisionedController.isUserSetup(
+ deviceProvisionedController.getCurrentUser()));
+ }
+ });
}
public DataSaverController getDataSaverController() {
@@ -358,8 +382,7 @@
}.execute();
}
- @Override
- public void onUserSwitched(int newUserId) {
+ private void onUserSwitched(int newUserId) {
mCurrentUserId = newUserId;
mAccessPoints.onUserSwitched(newUserId);
updateConnectivity();
@@ -413,7 +436,7 @@
}
}
- public void onConfigurationChanged() {
+ public void onConfigurationChanged(Configuration newConfig) {
mConfig = Config.readConfig(mContext);
mReceiverHandler.post(new Runnable() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
index e5b0c03..366a752 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
@@ -16,9 +16,10 @@
import android.app.AlarmManager;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
-public interface NextAlarmController extends CallbackController<NextAlarmChangeCallback> {
+public interface NextAlarmController extends CallbackController<NextAlarmChangeCallback>, Dumpable {
public interface NextAlarmChangeCallback {
void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index 3142228..3f8e41a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -15,9 +15,11 @@
*/
package com.android.systemui.statusbar.policy;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback;
-public interface SecurityController extends CallbackController<SecurityControllerCallback> {
+public interface SecurityController extends CallbackController<SecurityControllerCallback>,
+ Dumpable {
/** Whether the device has device owner, even if not on this user. */
boolean isDeviceManaged();
boolean hasProfileOwner();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index df959bd..19ced23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -39,12 +39,13 @@
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.systemui.R;
+import com.android.systemui.settings.CurrentUserTracker;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-public class SecurityControllerImpl implements SecurityController {
+public class SecurityControllerImpl extends CurrentUserTracker implements SecurityController {
private static final String TAG = "SecurityController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -73,6 +74,7 @@
private int mVpnUserId;
public SecurityControllerImpl(Context context) {
+ super(context);
mContext = context;
mDevicePolicyManager = (DevicePolicyManager)
context.getSystemService(Context.DEVICE_POLICY_SERVICE);
@@ -87,6 +89,7 @@
// TODO: re-register network callback on user change.
mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
onUserSwitched(ActivityManager.getCurrentUser());
+ startTracking();
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 4785ba9..f71c5d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -52,13 +52,14 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.util.UserIcons;
import com.android.settingslib.RestrictedLockUtils;
+import com.android.systemui.Dependency;
import com.android.systemui.GuestResumeSessionReceiver;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUISecondaryUserService;
import com.android.systemui.plugins.qs.QS.DetailAdapter;
import com.android.systemui.qs.tiles.UserDetailView;
-import com.android.systemui.plugins.qs.QS.ActivityStarter;
+import com.android.systemui.ActivityStarter;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import java.io.FileDescriptor;
@@ -645,11 +646,6 @@
}
@VisibleForTesting
- public KeyguardMonitor getKeyguardMonitor() {
- return mKeyguardMonitor;
- }
-
- @VisibleForTesting
public ArrayList<UserRecord> getUsers() {
return mUsers;
}
@@ -657,17 +653,19 @@
public static abstract class BaseUserAdapter extends BaseAdapter {
final UserSwitcherController mController;
+ private final KeyguardMonitor mKeyguardMonitor;
protected BaseUserAdapter(UserSwitcherController controller) {
mController = controller;
+ mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
controller.addAdapter(new WeakReference<>(this));
}
@Override
public int getCount() {
- boolean secureKeyguardShowing = mController.getKeyguardMonitor().isShowing()
- && mController.getKeyguardMonitor().isSecure()
- && !mController.getKeyguardMonitor().canSkipBouncer();
+ boolean secureKeyguardShowing = mKeyguardMonitor.isShowing()
+ && mKeyguardMonitor.isSecure()
+ && !mKeyguardMonitor.canSkipBouncer();
if (!secureKeyguardShowing) {
return mController.getUsers().size();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
index bcdb62d..f195f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
@@ -30,7 +30,6 @@
ZenRule getManualRule();
ZenModeConfig getConfig();
long getNextAlarm();
- void setUserId(int userId);
boolean isZenAvailable();
ComponentName getEffectsSuppressor();
boolean isCountdownConditionSupported();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 96efea1..e80d3b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -40,13 +40,14 @@
import android.util.Slog;
import com.android.systemui.qs.GlobalSetting;
+import com.android.systemui.settings.CurrentUserTracker;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Objects;
/** Platform implementation of the zen mode controller. **/
-public class ZenModeControllerImpl implements ZenModeController {
+public class ZenModeControllerImpl extends CurrentUserTracker implements ZenModeController {
private static final String TAG = "ZenModeController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -66,6 +67,7 @@
private ZenModeConfig mConfig;
public ZenModeControllerImpl(Context context, Handler handler) {
+ super(context);
mContext = context;
mModeSetting = new GlobalSetting(mContext, handler, Global.ZEN_MODE) {
@Override
@@ -87,6 +89,7 @@
mSetupObserver = new SetupObserver(handler);
mSetupObserver.register();
mUserManager = context.getSystemService(UserManager.class);
+ startTracking();
}
@Override
@@ -137,7 +140,7 @@
}
@Override
- public void setUserId(int userId) {
+ public void onUserSwitched(int userId) {
mUserId = userId;
if (mRegistered) {
mContext.unregisterReceiver(mReceiver);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
index f6b8891..266f053 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
@@ -23,6 +23,7 @@
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
+import android.provider.Settings;
import android.support.v14.preference.PreferenceFragment;
import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.PreferenceCategory;
@@ -30,9 +31,9 @@
import android.support.v7.preference.PreferenceViewHolder;
import android.view.View;
+import com.android.systemui.R;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.PluginPrefs;
-import com.android.systemui.R;
import java.util.List;
import java.util.Set;
@@ -147,6 +148,12 @@
result.activityInfo.name)));
}
});
+ holder.itemView.setOnLongClickListener(v -> {
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ intent.setData(Uri.fromParts("package", mComponent.getPackageName(), null));
+ getContext().startActivity(intent);
+ return true;
+ });
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
index 1f0ee57..36c673c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java
@@ -25,7 +25,6 @@
import java.io.PrintWriter;
public interface VolumeComponent extends DemoMode {
- ZenModeController getZenController();
void dismissNow();
void onConfigurationChanged(Configuration newConfig);
void dump(FileDescriptor fd, PrintWriter pw, String[] args);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index d23ebc1..d057d863 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -127,6 +127,7 @@
private boolean mShowing;
private boolean mExpanded;
+ private boolean mShowA11yStream;
private int mActiveStream;
private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
@@ -244,7 +245,6 @@
if (!AudioSystem.isSingleVolume(mContext)) {
addRow(AudioManager.STREAM_RING,
R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true);
-
addRow(AudioManager.STREAM_ALARM,
R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, false);
addRow(AudioManager.STREAM_VOICE_CALL,
@@ -253,6 +253,8 @@
R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false);
addRow(AudioManager.STREAM_SYSTEM,
R.drawable.ic_volume_system, R.drawable.ic_volume_system_mute, false);
+ addRow(AudioManager.STREAM_ACCESSIBILITY, R.drawable.ic_volume_accessibility,
+ R.drawable.ic_volume_accessibility, true);
}
} else {
addExistingRows();
@@ -307,10 +309,24 @@
}
private void addRow(int stream, int iconRes, int iconMuteRes, boolean important) {
+ addRow(stream, iconRes, iconMuteRes, important, false);
+ }
+
+ private void addRow(int stream, int iconRes, int iconMuteRes, boolean important,
+ boolean dynamic) {
VolumeRow row = new VolumeRow();
initRow(row, stream, iconRes, iconMuteRes, important);
- mDialogRowsView.addView(row.view);
- mRows.add(row);
+ int rowSize;
+ int viewSize;
+ if (mShowA11yStream && dynamic && (rowSize = mRows.size()) > 1
+ && (viewSize = mDialogRowsView.getChildCount()) > 1) {
+ // A11y Stream should be the last in the list
+ mDialogRowsView.addView(row.view, viewSize - 2);
+ mRows.add(rowSize - 2, row);
+ } else {
+ mDialogRowsView.addView(row.view);
+ mRows.add(row);
+ }
}
private void addExistingRows() {
@@ -592,6 +608,9 @@
}
private boolean shouldBeVisibleH(VolumeRow row, boolean isActive) {
+ if (row.stream == AudioSystem.STREAM_ACCESSIBILITY) {
+ return mShowA11yStream;
+ }
return mExpanded && row.view.getVisibility() == View.VISIBLE
|| (mExpanded && (row.important || isActive))
|| !mExpanded && isActive;
@@ -644,7 +663,8 @@
if (!ss.dynamic) continue;
mDynamic.put(stream, true);
if (findRow(stream) == null) {
- addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true);
+ addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true,
+ true);
}
}
@@ -1009,6 +1029,14 @@
public void onShowSafetyWarning(int flags) {
showSafetyWarningH(flags);
}
+
+ @Override
+ public void onAccessibilityModeChanged(Boolean showA11yStream) {
+ boolean show = showA11yStream == null ? false : showA11yStream;
+ mShowA11yStream = show;
+ updateRowsH(getActiveRow());
+
+ }
};
private final ZenModePanel.Callback mZenPanelCallback = new ZenModePanel.Callback() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index f195a0b..137a12f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -16,20 +16,17 @@
package com.android.systemui.volume;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.media.VolumePolicy;
import android.os.Bundle;
import android.os.Handler;
-import android.provider.Settings;
-import android.util.Log;
import android.view.WindowManager;
-import com.android.systemui.R;
+import com.android.systemui.ActivityStarter;
+import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -37,11 +34,9 @@
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
-import com.android.systemui.volume.car.CarVolumeDialogController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.lang.reflect.Constructor;
/**
* Implementation of VolumeComponent backed by the new volume dialog.
@@ -69,15 +64,14 @@
400 // vibrateToSilentDebounce
);
- public VolumeDialogComponent(SystemUI sysui, Context context, Handler handler,
- ZenModeController zen) {
+ public VolumeDialogComponent(SystemUI sysui, Context context, Handler handler) {
mSysui = sysui;
mContext = context;
mController = SystemUIFactory.getInstance().createVolumeDialogController(context, null);
mController.setUserActivityListener(this);
- mZenModeController = zen;
+ mZenModeController = Dependency.get(ZenModeController.class);
mDialog = new VolumeDialog(context, WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY,
- mController, zen, mVolumeDialogCallback);
+ mController, mZenModeController, mVolumeDialogCallback);
applyConfiguration();
TunerService.get(mContext).addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
VOLUME_SILENT_DO_NOT_DISTURB);
@@ -134,11 +128,6 @@
}
@Override
- public ZenModeController getZenController() {
- return mZenModeController;
- }
-
- @Override
public void onConfigurationChanged(Configuration newConfig) {
// noop
}
@@ -166,7 +155,7 @@
}
private void startSettings(Intent intent) {
- mSysui.getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard(intent,
+ Dependency.get(ActivityStarter.class).startActivity(intent,
true /* onlyProvisioned */, true /* dismissShade */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index 0e5ff43..276b7c3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -75,13 +75,13 @@
STREAMS.put(AudioSystem.STREAM_BLUETOOTH_SCO, R.string.stream_bluetooth_sco);
STREAMS.put(AudioSystem.STREAM_DTMF, R.string.stream_dtmf);
STREAMS.put(AudioSystem.STREAM_MUSIC, R.string.stream_music);
+ STREAMS.put(AudioSystem.STREAM_ACCESSIBILITY, R.string.stream_accessibility);
STREAMS.put(AudioSystem.STREAM_NOTIFICATION, R.string.stream_notification);
STREAMS.put(AudioSystem.STREAM_RING, R.string.stream_ring);
STREAMS.put(AudioSystem.STREAM_SYSTEM, R.string.stream_system);
STREAMS.put(AudioSystem.STREAM_SYSTEM_ENFORCED, R.string.stream_system_enforced);
STREAMS.put(AudioSystem.STREAM_TTS, R.string.stream_tts);
STREAMS.put(AudioSystem.STREAM_VOICE_CALL, R.string.stream_voice_call);
- STREAMS.put(AudioSystem.STREAM_ACCESSIBILITY, R.string.stream_accessibility);
}
private final HandlerThread mWorkerThread;
@@ -98,6 +98,7 @@
private final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks();
private final Vibrator mVibrator;
private final boolean mHasVibrator;
+ private boolean mShowA11yStream;
private boolean mDestroyed;
private VolumePolicy mVolumePolicy;
@@ -204,6 +205,7 @@
pw.print(" mHasVibrator: "); pw.println(mHasVibrator);
pw.print(" mRemoteStreams: "); pw.println(mMediaSessionsCallbacksW.mRemoteStreams
.values());
+ pw.print(" mShowA11yStream: "); pw.println(mShowA11yStream);
pw.println();
mMediaSessions.dump(pw);
}
@@ -301,6 +303,10 @@
mCallbacks.onShowSafetyWarning(flags);
}
+ private void onAccessibilityModeChanged(Boolean showA11yStream) {
+ mCallbacks.onAccessibilityModeChanged(showA11yStream);
+ }
+
private boolean checkRoutedToBluetoothW(int stream) {
boolean changed = false;
if (stream == AudioManager.STREAM_MUSIC) {
@@ -570,13 +576,16 @@
switch (mode) {
case VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME:
// "legacy" mode
+ mShowA11yStream = false;
break;
case VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME:
+ mShowA11yStream = true;
break;
default:
Log.e(TAG, "Invalid accessibility mode " + mode);
break;
}
+ mWorker.obtainMessage(W.ACCESSIBILITY_MODE_CHANGED, mShowA11yStream).sendToTarget();
}
}
@@ -595,6 +604,7 @@
private static final int NOTIFY_VISIBLE = 12;
private static final int USER_ACTIVITY = 13;
private static final int SHOW_SAFETY_WARNING = 14;
+ private static final int ACCESSIBILITY_MODE_CHANGED = 15;
W(Looper looper) {
super(looper);
@@ -617,6 +627,7 @@
case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0); break;
case USER_ACTIVITY: onUserActivityW(); break;
case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break;
+ case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj);
}
}
}
@@ -743,6 +754,19 @@
});
}
}
+
+ @Override
+ public void onAccessibilityModeChanged(Boolean showA11yStream) {
+ boolean show = showA11yStream == null ? false : showA11yStream;
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onAccessibilityModeChanged(show);
+ }
+ });
+ }
+ }
}
@@ -1004,6 +1028,7 @@
.append('[').append(ss.levelMin).append("..").append(ss.levelMax)
.append(']');
if (ss.muted) sb.append(" [MUTED]");
+ if (ss.dynamic) sb.append(" [DYNAMIC]");
}
sep(sb, indent); sb.append("ringerModeExternal:").append(ringerModeExternal);
sep(sb, indent); sb.append("ringerModeInternal:").append(ringerModeInternal);
@@ -1037,6 +1062,7 @@
void onShowSilentHint();
void onScreenOff();
void onShowSafetyWarning(int flags);
+ void onAccessibilityModeChanged(Boolean showA11yStream);
}
public interface UserActivityListener {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 73d9ea7..02969e4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -42,8 +42,7 @@
public void start() {
mEnabled = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
if (!mEnabled) return;
- final ZenModeController zenController = new ZenModeControllerImpl(mContext, mHandler);
- mVolumeComponent = new VolumeDialogComponent(this, mContext, null, zenController);
+ mVolumeComponent = new VolumeDialogComponent(this, mContext, null);
putComponent(VolumeComponent.class, getVolumeComponent());
setDefaultVolumeController();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
new file mode 100644
index 0000000..973f1f2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.os.Looper;
+
+import com.android.systemui.statusbar.policy.FlashlightController;
+
+import org.junit.Test;
+
+public class DependencyTest extends SysuiTestCase {
+
+ @Test
+ public void testClassDependency() {
+ FlashlightController f = mock(FlashlightController.class);
+ injectTestDependency(FlashlightController.class, f);
+ assertEquals(f, Dependency.get(FlashlightController.class));
+ }
+
+ @Test
+ public void testStringDependency() {
+ Looper l = Looper.getMainLooper();
+ injectTestDependency(Dependency.BG_LOOPER, l);
+ assertEquals(l, Dependency.get(Dependency.BG_LOOPER));
+ }
+
+ @Test
+ public void testDump() {
+ Dumpable d = mock(Dumpable.class);
+ injectTestDependency("test", d);
+ Dependency.get("test");
+ mDependency.dump(null, null, null);
+ verify(d).dump(eq(null), eq(null), eq(null));
+ }
+
+ @Test
+ public void testConfigurationChanged() {
+ ConfigurationChangedReceiver d = mock(ConfigurationChangedReceiver.class);
+ injectTestDependency("test", d);
+ Dependency.get("test");
+ mDependency.onConfigurationChanged(null);
+ verify(d).onConfigurationChanged(eq(null));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index d1d7520..5fe5174 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -15,11 +15,14 @@
*/
package com.android.systemui;
+import static org.mockito.Mockito.mock;
+
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
+import android.util.ArrayMap;
import com.android.systemui.utils.TestableContext;
import com.android.systemui.utils.leaks.Tracker;
@@ -34,11 +37,15 @@
private Handler mHandler;
protected TestableContext mContext;
+ protected TestDependency mDependency;
@Before
public void SysuiSetup() throws Exception {
System.setProperty("dexmaker.share_classloader", "true");
mContext = new TestableContext(InstrumentationRegistry.getTargetContext(), this);
+ mDependency = new TestDependency();
+ mDependency.mContext = mContext;
+ mDependency.start();
}
@After
@@ -78,11 +85,37 @@
return null;
}
+ public void injectMockDependency(Class<?> cls) {
+ mDependency.injectTestDependency(cls.getName(), mock(cls));
+ }
+
+ public void injectTestDependency(Class<?> cls, Object obj) {
+ mDependency.injectTestDependency(cls.getName(), obj);
+ }
+
+ public void injectTestDependency(String key, Object obj) {
+ mDependency.injectTestDependency(key, obj);
+ }
+
public static final class EmptyRunnable implements Runnable {
public void run() {
}
}
+ public static class TestDependency extends Dependency {
+ private final ArrayMap<String, Object> mObjs = new ArrayMap<>();
+
+ private void injectTestDependency(String key, Object obj) {
+ mObjs.put(key, obj);
+ }
+
+ @Override
+ protected <T> T createDependency(String cls) {
+ if (mObjs.containsKey(cls)) return (T) mObjs.get(cls);
+ return super.createDependency(cls);
+ }
+ }
+
public static final class Idler implements MessageQueue.IdleHandler {
private final Runnable mCallback;
private boolean mIdle;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
index d529ee1..3715df2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
@@ -113,8 +113,7 @@
waitForIdleSync(mPluginInstanceManager.mPluginHandler);
waitForIdleSync(mPluginInstanceManager.mMainHandler);
- verify(mMockListener, Mockito.never()).onPluginConnected(
- ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(mMockListener, Mockito.never()).onPluginConnected(any(), any());
}
@Test
@@ -124,7 +123,7 @@
// Verify startup lifecycle
verify(sMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(),
ArgumentCaptor.forClass(Context.class).capture());
- verify(mMockListener).onPluginConnected(ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(mMockListener).onPluginConnected(any(), any());
}
@Test
@@ -154,8 +153,7 @@
waitForIdleSync(mPluginInstanceManager.mMainHandler);
// Plugin shouldn't be connected because it is the wrong version.
- verify(mMockListener, Mockito.never()).onPluginConnected(
- ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(mMockListener, Mockito.never()).onPluginConnected(any(), any());
verify(nm).notifyAsUser(eq(TestPlugin.class.getName()), eq(SystemMessage.NOTE_PLUGIN),
any(), eq(UserHandle.ALL));
}
@@ -176,8 +174,7 @@
verify(sMockPlugin, Mockito.times(2)).onCreate(
ArgumentCaptor.forClass(Context.class).capture(),
ArgumentCaptor.forClass(Context.class).capture());
- verify(mMockListener, Mockito.times(2)).onPluginConnected(
- ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(mMockListener, Mockito.times(2)).onPluginConnected(any(), any());
}
@Test
@@ -193,8 +190,7 @@
waitForIdleSync(mPluginInstanceManager.mMainHandler);;
// Non-debuggable build should receive no plugins.
- verify(mMockListener, Mockito.never()).onPluginConnected(
- ArgumentCaptor.forClass(Plugin.class).capture());
+ verify(mMockListener, Mockito.never()).onPluginConnected(any(), any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
index 8acd6ba..2f6487b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
@@ -29,6 +29,7 @@
import android.view.ViewGroup;
import android.widget.TextView;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.SecurityController;
@@ -56,6 +57,8 @@
@Before
public void setUp() {
+ injectTestDependency(SecurityController.class, mSecurityController);
+ injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper());
mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE,
new LayoutInflaterBuilder(mContext)
.replace("ImageView", TestableImageView.class)
@@ -67,7 +70,7 @@
mFooterText = (TextView) mRootView.findViewById(R.id.footer_text);
mFooterIcon = (TestableImageView) mRootView.findViewById(R.id.footer_icon);
mFooterIcon2 = (TestableImageView) mRootView.findViewById(R.id.footer_icon2);
- mFooter.setHostEnvironment(null, mSecurityController, Looper.getMainLooper());
+ mFooter.setHostEnvironment(null);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index c0d5bbd..e3ee851 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -18,11 +18,12 @@
import static org.mockito.Mockito.when;
import android.os.Handler;
+import android.os.Looper;
import android.support.test.runner.AndroidJUnit4;
+import com.android.systemui.Dependency;
import com.android.systemui.FragmentTestCase;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.phone.QuickStatusBarHeader;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -42,6 +43,7 @@
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,33 +56,24 @@
super(QSFragment.class);
}
+ @Before
+ public void addLeakCheckDependencies() {
+ injectMockDependency(UserSwitcherController.class);
+ injectLeakCheckedDependencies(BluetoothController.class, LocationController.class,
+ RotationLockController.class, NetworkController.class, ZenModeController.class,
+ HotspotController.class, CastController.class, FlashlightController.class,
+ UserInfoController.class, KeyguardMonitor.class, SecurityController.class,
+ BatteryController.class, NextAlarmController.class);
+ }
+
@Test
public void testListening() {
QSFragment qs = (QSFragment) mFragment;
postAndWait(() -> mFragments.dispatchResume());
- UserSwitcherController userSwitcher = mock(UserSwitcherController.class);
- KeyguardMonitor keyguardMonitor = getLeakChecker(KeyguardMonitor.class);
- when(userSwitcher.getKeyguardMonitor()).thenReturn(keyguardMonitor);
- when(userSwitcher.getUsers()).thenReturn(new ArrayList<>());
- QSTileHost host = new QSTileHost(mContext,
- null,
- getLeakChecker(BluetoothController.class),
- getLeakChecker(LocationController.class),
- getLeakChecker(RotationLockController.class),
- getLeakChecker(NetworkController.class),
- getLeakChecker(ZenModeController.class),
- getLeakChecker(HotspotController.class),
- getLeakChecker(CastController.class),
- getLeakChecker(FlashlightController.class),
- userSwitcher,
- getLeakChecker(UserInfoController.class),
- keyguardMonitor,
- getLeakChecker(SecurityController.class),
- getLeakChecker(BatteryController.class),
- mock(StatusBarIconController.class),
- getLeakChecker(NextAlarmController.class));
+ QSTileHost host = new QSTileHost(mContext, null,
+ mock(StatusBarIconController.class));
qs.setHost(host);
- Handler h = new Handler(host.getLooper());
+ Handler h = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
qs.setListening(true);
waitForIdleSync(h);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 3ee1372..4146cb81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -47,13 +47,7 @@
@Before
public void setUp() throws Exception {
mManagers = new ArrayList<>();
- final NetworkController networkController = Mockito.mock(NetworkController.class);
- Mockito.when(networkController.getDataSaverController()).thenReturn(
- Mockito.mock(DataSaverController.class));
- QSTileHost host = new QSTileHost(mContext, null, null, null, null,
- networkController, null,
- Mockito.mock(HotspotController.class), null,
- null, null, null, null, null, null, null, null);
+ QSTileHost host = new QSTileHost(mContext, null, null);
mTileService = new TestTileServices(host, Looper.getMainLooper());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
index c65f7150..cac0806 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 @@
// 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 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 @@
@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 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 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 @@
@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 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 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 @@
@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 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 @@
@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 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 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 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 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 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 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 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 void testEnabledSwitchOverridesOtherButtons() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, null, null, null);
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
RadioButton lowButton = (RadioButton) mNotificationGuts.findViewById(R.id.low_importance);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index e140e98..9fcb5f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -47,10 +47,6 @@
mContext.addMockSystemService(Context.WINDOW_SERVICE, mock(WindowManager.class));
NavigationBarFragment navigationBarFragment = (NavigationBarFragment) mFragment;
- AssistManager assistManager = new AssistManager(mContext.getComponent(PhoneStatusBar.class),
- mContext);
- navigationBarFragment.setAssistManager(assistManager);
-
postAndWait(() -> mFragments.dispatchResume());
navigationBarFragment.onHomeLongClick(navigationBarFragment.getView());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTest.java
new file mode 100644
index 0000000..d82566f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.keyguard.KeyguardHostView.OnDismissAction;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.BaseStatusBar;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PhoneStatusBarTest extends SysuiTestCase {
+
+ StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ PhoneStatusBar mPhoneStatusBar;
+
+ @Before
+ public void setup() {
+ mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager.class);
+ mPhoneStatusBar = new TestablePhoneStatusBar(mStatusBarKeyguardViewManager);
+
+ doAnswer(invocation -> {
+ OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0];
+ onDismissAction.onDismiss();
+ return null;
+ }).when(mStatusBarKeyguardViewManager).dismissWithAction(any(), any(), anyBoolean());
+
+ doAnswer(invocation -> {
+ Runnable runnable = (Runnable) invocation.getArguments()[0];
+ runnable.run();
+ return null;
+ }).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
+ }
+
+ @Test
+ public void executeRunnableDismissingKeyguard_nullRunnable_showingAndOccluded() {
+ when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(true);
+
+ mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null, false, false, false);
+ }
+
+ @Test
+ public void executeRunnableDismissingKeyguard_nullRunnable_showing() {
+ when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+
+ mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null, false, false, false);
+ }
+
+ @Test
+ public void executeRunnableDismissingKeyguard_nullRunnable_notShowing() {
+ when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
+
+ mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null, false, false, false);
+ }
+
+ static class TestablePhoneStatusBar extends PhoneStatusBar {
+ public TestablePhoneStatusBar(StatusBarKeyguardViewManager man) {
+ mStatusBarKeyguardViewManager = man;
+ }
+
+ @Override
+ protected BaseStatusBar.H createHandler() {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 6fe7768..23c635c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -118,7 +118,7 @@
mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
mConfig, Looper.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
- mMockSubDefaults);
+ mMockSubDefaults, mock(DeviceProvisionedController.class));
setupNetworkController();
// Trigger blank callbacks to always get the current state (some tests don't trigger
@@ -160,7 +160,8 @@
= new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
mConfig, mContext.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class),
- mock(DataUsageController.class), mMockSubDefaults);
+ mock(DataUsageController.class), mMockSubDefaults,
+ mock(DeviceProvisionedController.class));
setupNetworkController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 4f961ab..1f7ec1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -1,5 +1,7 @@
package com.android.systemui.statusbar.policy;
+import static org.mockito.Mockito.mock;
+
import android.net.NetworkCapabilities;
import android.os.Looper;
import android.support.test.runner.AndroidJUnit4;
@@ -100,8 +102,9 @@
mConfig.show4gForLte = true;
mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
mConfig, Looper.getMainLooper(), mCallbackHandler,
- Mockito.mock(AccessPointControllerImpl.class),
- Mockito.mock(DataUsageController.class), mMockSubDefaults);
+ mock(AccessPointControllerImpl.class),
+ mock(DataUsageController.class), mMockSubDefaults,
+ mock(DeviceProvisionedController.class));
setupNetworkController();
setupDefaultSignal();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index 4560aa7..1a61d80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -56,7 +56,7 @@
mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
mConfig, Looper.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
- mMockSubDefaults);
+ mMockSubDefaults, mock(DeviceProvisionedController.class));
setupNetworkController();
verifyLastMobileDataIndicators(false, 0, 0);
@@ -110,7 +110,7 @@
mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
mConfig, Looper.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
- mMockSubDefaults);
+ mMockSubDefaults, mock(DeviceProvisionedController.class));
setupNetworkController();
// No Subscriptions.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
index 0238bf7..b118fdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
@@ -14,9 +14,13 @@
package com.android.systemui.utils.leaks;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.CallbackController;
-public class BaseLeakChecker<T> implements CallbackController<T> {
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+public class BaseLeakChecker<T> implements CallbackController<T>, Dumpable {
private final Tracker mTracker;
@@ -37,4 +41,9 @@
public void removeCallback(T listener) {
mTracker.getLeakInfo(listener).clearAllocations();
}
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
index fcfe9aa..5497686 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
@@ -14,11 +14,16 @@
package com.android.systemui.utils.leaks;
+import android.os.Bundle;
+
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
public class FakeNetworkController extends BaseLeakChecker<SignalCallback>
implements NetworkController {
@@ -42,6 +47,21 @@
}
@Override
+ public void setUserSetupComplete(boolean userSetup) {
+
+ }
+
+ @Override
+ public boolean hasEmergencyCryptKeeperText() {
+ return false;
+ }
+
+ @Override
+ public boolean isRadioOn() {
+ return false;
+ }
+
+ @Override
public DataSaverController getDataSaverController() {
return mDataSaverController;
}
@@ -57,11 +77,6 @@
}
@Override
- public void onUserSwitched(int newUserId) {
-
- }
-
- @Override
public AccessPointController getAccessPointController() {
return null;
}
@@ -75,4 +90,9 @@
public boolean hasVoiceCallingFeature() {
return false;
}
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
index 13ea385..7581363 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
@@ -53,11 +53,6 @@
}
@Override
- public void setUserId(int userId) {
-
- }
-
- @Override
public boolean isZenAvailable() {
return false;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
index 728ed60..c182493 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
@@ -118,6 +118,12 @@
mTrackers.values().forEach(Tracker::verify);
}
+ public void injectLeakCheckedDependencies(Class<?>... cls) {
+ for (Class<?> c : cls) {
+ injectTestDependency(c, getLeakChecker(c));
+ }
+ }
+
public <T extends CallbackController> T addListening(T mock, Class<T> cls, String tag) {
doAnswer(new Answer<Void>() {
@Override
diff --git a/preloaded-classes b/preloaded-classes
index 86cbb69..2fad5dd 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -823,11 +823,6 @@
android.graphics.EmbossMaskFilter
android.graphics.FontFamily
android.graphics.FontListParser
-android.graphics.FontListParser$Alias
-android.graphics.FontListParser$Axis
-android.graphics.FontListParser$Config
-android.graphics.FontListParser$Family
-android.graphics.FontListParser$Font
android.graphics.Insets
android.graphics.Interpolator
android.graphics.Interpolator$Result
@@ -1001,10 +996,6 @@
android.hardware.input.InputDeviceIdentifier$1
android.hardware.input.InputManager
android.hardware.input.InputManager$InputDevicesChangedListener
-# These cannot be preloaded and need to be refactored into system server. b/17791590, b/21935130
-# android.hardware.location.ActivityRecognitionHardware
-# android.hardware.location.IActivityRecognitionHardware
-# android.hardware.location.IActivityRecognitionHardware$Stub
android.hardware.location.ContextHubManager
android.hardware.location.IContextHubService
android.hardware.location.IContextHubService$Stub
@@ -1847,6 +1838,11 @@
android.text.DynamicLayout$ChangeWatcher
android.text.Editable
android.text.Editable$Factory
+android.text.FontConfig
+android.text.FontConfig$Alias
+android.text.FontConfig$Axis
+android.text.FontConfig$Family
+android.text.FontConfig$Font
android.text.GetChars
android.text.GraphicsOperations
android.text.Html
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 88bc99f..341438d 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3311,6 +3311,26 @@
// 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/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 581aa05d..0e07ec0 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -504,8 +504,8 @@
for (int i = alarms.size()-1; i >= 0; i--) {
Alarm alarm = alarms.get(i);
try {
- if (alarm.uid == uid && ActivityManager.getService().getAppStartMode(
- uid, alarm.packageName) == ActivityManager.APP_START_MODE_DISABLED) {
+ if (alarm.uid == uid && ActivityManager.getService().isAppStartModeDisabled(
+ uid, alarm.packageName)) {
alarms.remove(i);
didRemove = true;
if (alarm.alarmClock != null) {
@@ -1089,8 +1089,7 @@
operation, directReceiver, listenerTag, workSource, flags, alarmClock,
callingUid, callingPackage);
try {
- if (ActivityManager.getService().getAppStartMode(callingUid, callingPackage)
- == ActivityManager.APP_START_MODE_DISABLED) {
+ if (ActivityManager.getService().isAppStartModeDisabled(callingUid, callingPackage)) {
Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a
+ " -- package not allowed to start");
return;
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 381fe89..f3f8da8 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -5512,6 +5512,18 @@
}
}
+ // Turn Always-on VPN off
+ if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mKeyStore.delete(Credentials.LOCKDOWN_VPN);
+ mLockdownEnabled = false;
+ setLockdownTracker(null);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
// Turn VPN off
VpnConfig vpnConfig = getVpnConfig(userId);
if (vpnConfig != null) {
diff --git a/services/core/java/com/android/server/FontManagerService.java b/services/core/java/com/android/server/FontManagerService.java
new file mode 100644
index 0000000..593c322
--- /dev/null
+++ b/services/core/java/com/android/server/FontManagerService.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.graphics.FontListParser;
+import android.os.ParcelFileDescriptor;
+import android.text.FontConfig;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.font.IFontManager;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+public class FontManagerService extends IFontManager.Stub {
+ private static final String TAG = "FontManagerService";
+ private static final String FONTS_CONFIG = "/system/etc/fonts.xml";
+
+ @GuardedBy("mLock")
+ private FontConfig mConfig;
+ private final Object mLock = new Object();
+
+ public static final class Lifecycle extends SystemService {
+ private final FontManagerService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ mService = new FontManagerService();
+ }
+
+ @Override
+ public void onStart() {
+ try {
+ publishBinderService(Context.FONT_SERVICE, mService);
+ } catch (Throwable t) {
+ // Starting this service is not critical to the running of this device and should
+ // therefore not crash the device. If it fails, log the error and continue.
+ Slog.e(TAG, "Could not start the FontManagerService.", t);
+ }
+ }
+ }
+
+ @Override
+ public FontConfig getSystemFonts() {
+ synchronized (mLock) {
+ if (mConfig != null) {
+ return new FontConfig(mConfig);
+ }
+
+ FontConfig config = loadFromSystem();
+ if (config == null) {
+ return null;
+ }
+
+ final int size = config.getFamilies().size();
+ for (int i = 0; i < size; ++i) {
+ FontConfig.Family family = config.getFamilies().get(i);
+ for (int j = 0; j < family.getFonts().size(); ++j) {
+ FontConfig.Font font = family.getFonts().get(j);
+ File fontFile = new File(font.getFontName());
+ try {
+ font.setFd(ParcelFileDescriptor.open(
+ fontFile, ParcelFileDescriptor.MODE_READ_ONLY));
+ } catch (IOException e) {
+ Slog.e(TAG, "Error opening font file " + font.getFontName(), e);
+ }
+ }
+ }
+
+ mConfig = config;
+ return new FontConfig(mConfig);
+ }
+ }
+
+ private FontConfig loadFromSystem() {
+ File configFilename = new File(FONTS_CONFIG);
+ try {
+ FileInputStream fontsIn = new FileInputStream(configFilename);
+ return FontListParser.parse(fontsIn);
+ } catch (IOException | XmlPullParserException e) {
+ Slog.e(TAG, "Error opening " + configFilename, e);
+ }
+ return null;
+ }
+
+ public FontManagerService() {
+ }
+}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index f718fa1..bee1f97 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -243,7 +243,6 @@
private PendingIntent mImeSwitchPendingIntent;
private boolean mShowOngoingImeSwitcherForPhones;
private boolean mNotificationShown;
- private final boolean mImeSelectedOnBoot;
static class SessionState {
final ClientState client;
@@ -566,7 +565,7 @@
}
}
- class ImmsBroadcastReceiver extends android.content.BroadcastReceiver {
+ class ImmsBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
@@ -587,6 +586,10 @@
Intent.EXTRA_SETTING_NEW_VALUE);
restoreEnabledInputMethods(mContext, prevValue, newValue);
}
+ } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+ synchronized (mMethodMap) {
+ resetStateIfCurrentLocaleChangedLocked();
+ }
} else {
Slog.w(TAG, "Unexpected intent " + intent);
}
@@ -845,9 +848,11 @@
return;
}
mSettings.switchCurrentUser(currentUserId, !mSystemReady);
- // We need to rebuild IMEs.
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
- updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
+ if (mSystemReady) {
+ // We need to rebuild IMEs.
+ buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
+ }
}
}
@@ -897,13 +902,6 @@
mShowOngoingImeSwitcherForPhones = false;
- final IntentFilter broadcastFilter = new IntentFilter();
- broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
- broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
- broadcastFilter.addAction(Intent.ACTION_SETTING_RESTORED);
- mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
-
mNotificationShown = false;
int userId = 0;
try {
@@ -911,7 +909,6 @@
} catch (RemoteException e) {
Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
}
- mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
// mSettings should be created before buildInputMethodListLocked
mSettings = new InputMethodSettings(
@@ -919,48 +916,8 @@
updateCurrentProfileIds();
mFileManager = new InputMethodFileManager(mMethodMap, userId);
- synchronized (mMethodMap) {
- mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
- mSettings, context);
- }
-
- // Just checking if defaultImiId is empty or not
- final String defaultImiId = mSettings.getSelectedInputMethod();
- if (DEBUG) {
- Slog.d(TAG, "Initial default ime = " + defaultImiId);
- }
- mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
-
- synchronized (mMethodMap) {
- buildInputMethodListLocked(!mImeSelectedOnBoot /* resetDefaultEnabledIme */);
- }
- mSettings.enableAllIMEsIfThereIsNoEnabledIME();
-
- if (!mImeSelectedOnBoot) {
- Slog.w(TAG, "No IME selected. Choose the most applicable IME.");
- synchronized (mMethodMap) {
- resetDefaultImeLocked(context);
- }
- }
-
- synchronized (mMethodMap) {
- mSettingsObserver.registerContentObserverLocked(userId);
- updateFromSettingsLocked(true);
- }
-
- // IMMS wants to receive Intent.ACTION_LOCALE_CHANGED in order to update the current IME
- // according to the new system locale.
- final IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_LOCALE_CHANGED);
- mContext.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- synchronized(mMethodMap) {
- resetStateIfCurrentLocaleChangedLocked();
- }
- }
- }, filter);
+ mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
+ mSettings, context);
}
private void resetDefaultImeLocked(Context context) {
@@ -1089,6 +1046,7 @@
}
if (!mSystemReady) {
mSystemReady = true;
+ mLastSystemLocales = mRes.getConfiguration().getLocales();
final int currentUserId = mSettings.getCurrentUserId();
mSettings.switchCurrentUser(currentUserId,
!mUserManager.isUserUnlockingOrUnlocked(currentUserId));
@@ -1105,14 +1063,25 @@
mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(
mHardKeyboardListener);
}
- if (!mImeSelectedOnBoot) {
- Slog.w(TAG, "Reset the default IME as \"Resource\" is ready here.");
- resetStateIfCurrentLocaleChangedLocked();
- InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
- mSettings.getEnabledInputMethodListLocked(),
- mSettings.getCurrentUserId(), mContext.getBasePackageName());
- }
- mLastSystemLocales = mRes.getConfiguration().getLocales();
+
+ mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
+ mSettingsObserver.registerContentObserverLocked(currentUserId);
+
+ final IntentFilter broadcastFilter = new IntentFilter();
+ broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
+ broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
+ broadcastFilter.addAction(Intent.ACTION_SETTING_RESTORED);
+ broadcastFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
+ mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
+
+ buildInputMethodListLocked(true /* resetDefaultEnabledIme */);
+ resetDefaultImeLocked(mContext);
+ updateFromSettingsLocked(true);
+ InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
+ mSettings.getEnabledInputMethodListLocked(), currentUserId,
+ mContext.getBasePackageName());
+
try {
startInputInnerLocked();
} catch (RuntimeException e) {
@@ -2624,6 +2593,9 @@
// additional input method subtypes to the IME.
if (TextUtils.isEmpty(imiId) || subtypes == null) return;
synchronized (mMethodMap) {
+ if (!mSystemReady) {
+ return;
+ }
final InputMethodInfo imi = mMethodMap.get(imiId);
if (imi == null) return;
final String[] packageInfos;
@@ -3048,6 +3020,10 @@
Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
+ " \n ------ caller=" + Debug.getCallers(10));
}
+ if (!mSystemReady) {
+ Slog.e(TAG, "buildInputMethodListLocked is not allowed until system is ready");
+ return;
+ }
mMethodList.clear();
mMethodMap.clear();
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index c9b59ade..6cc72de 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import android.app.ActivityManager;
import android.content.pm.PackageManagerInternal;
import com.android.internal.content.PackageMonitor;
import com.android.internal.location.ProviderProperties;
@@ -138,6 +139,9 @@
// The maximum interval a location request can have and still be considered "high power".
private static final long HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
+ // default background throttling interval if not overriden in settings
+ private static final long DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 1000;
+
// Location Providers may sometimes deliver location updates
// slightly faster that requested - provide grace period so
// we don't unnecessarily filter events that are otherwise on
@@ -157,6 +161,7 @@
private GeofenceManager mGeofenceManager;
private PackageManager mPackageManager;
private PowerManager mPowerManager;
+ private ActivityManager mActivityManager;
private UserManager mUserManager;
private GeocoderProxy mGeocodeProvider;
private IGnssStatusProvider mGnssStatusProvider;
@@ -171,47 +176,47 @@
// --- fields below are protected by mLock ---
// Set of providers that are explicitly enabled
// Only used by passive, fused & test. Network & GPS are controlled separately, and not listed.
- private final Set<String> mEnabledProviders = new HashSet<String>();
+ private final Set<String> mEnabledProviders = new HashSet<>();
// Set of providers that are explicitly disabled
- private final Set<String> mDisabledProviders = new HashSet<String>();
+ private final Set<String> mDisabledProviders = new HashSet<>();
// Mock (test) providers
private final HashMap<String, MockProvider> mMockProviders =
- new HashMap<String, MockProvider>();
+ new HashMap<>();
// all receivers
- private final HashMap<Object, Receiver> mReceivers = new HashMap<Object, Receiver>();
+ private final HashMap<Object, Receiver> mReceivers = new HashMap<>();
// currently installed providers (with mocks replacing real providers)
private final ArrayList<LocationProviderInterface> mProviders =
- new ArrayList<LocationProviderInterface>();
+ new ArrayList<>();
// real providers, saved here when mocked out
private final HashMap<String, LocationProviderInterface> mRealProviders =
- new HashMap<String, LocationProviderInterface>();
+ new HashMap<>();
// mapping from provider name to provider
private final HashMap<String, LocationProviderInterface> mProvidersByName =
- new HashMap<String, LocationProviderInterface>();
+ new HashMap<>();
// mapping from provider name to all its UpdateRecords
private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider =
- new HashMap<String, ArrayList<UpdateRecord>>();
+ new HashMap<>();
private final LocationRequestStatistics mRequestStatistics = new LocationRequestStatistics();
// mapping from provider name to last known location
- private final HashMap<String, Location> mLastLocation = new HashMap<String, Location>();
+ private final HashMap<String, Location> mLastLocation = new HashMap<>();
// same as mLastLocation, but is not updated faster than LocationFudger.FASTEST_INTERVAL_MS.
// locations stored here are not fudged for coarse permissions.
private final HashMap<String, Location> mLastLocationCoarseInterval =
- new HashMap<String, Location>();
+ new HashMap<>();
// all providers that operate over proxy, for authorizing incoming location
private final ArrayList<LocationProviderProxy> mProxyProviders =
- new ArrayList<LocationProviderProxy>();
+ new ArrayList<>();
// current active user on the device - other users are denied location data
private int mCurrentUserId = UserHandle.USER_SYSTEM;
@@ -252,6 +257,10 @@
// fetch power manager
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ // fetch activity manager
+ mActivityManager
+ = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+
// prepare worker thread
mLocationHandler = new LocationWorkerHandler(BackgroundThread.get().getLooper());
@@ -286,6 +295,40 @@
};
mPackageManager.addOnPermissionsChangeListener(permissionListener);
+ // listen for background/foreground changes
+ ActivityManager.OnUidImportanceListener uidImportanceListener
+ = new ActivityManager.OnUidImportanceListener() {
+ @Override
+ public void onUidImportance(int uid, int importance) {
+ boolean foreground = isImportanceForeground(importance);
+ HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
+ synchronized (mLock) {
+ for (Map.Entry<String, ArrayList<UpdateRecord>> entry
+ : mRecordsByProvider.entrySet()) {
+ String provider = entry.getKey();
+ for (UpdateRecord record : entry.getValue()) {
+ if (record.mReceiver.mUid == uid
+ && record.mIsForegroundUid != foreground) {
+ if (D) Log.d(TAG, "request from uid " + uid + " is now "
+ + (foreground ? "foreground" : "background)"));
+ record.mIsForegroundUid = foreground;
+
+ if (!isThrottlingExemptLocked(record.mReceiver)) {
+ affectedProviders.add(provider);
+ }
+ }
+ }
+ }
+ for (String provider : affectedProviders) {
+ applyRequirementsLocked(provider);
+ }
+ }
+
+ }
+ };
+ mActivityManager.addOnUidImportanceListener(uidImportanceListener,
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE);
+
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
updateUserProfiles(mCurrentUserId);
@@ -305,6 +348,17 @@
}
}
}, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS),
+ true,
+ new ContentObserver(mLocationHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ synchronized (mLock) {
+ updateProvidersLocked();
+ }
+ }
+ }, UserHandle.USER_ALL);
mPackageMonitor.register(mContext, mLocationHandler.getLooper(), true);
// listen for user change
@@ -334,6 +388,10 @@
}, UserHandle.ALL, intentFilter, null, mLocationHandler);
}
+ private static boolean isImportanceForeground(int importance) {
+ return importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+ }
+
/**
* Provides a way for components held by the {@link LocationManagerService} to clean-up
* gracefully on system's shutdown.
@@ -483,7 +541,7 @@
that matches the signature of at least one package on this list.
*/
Resources resources = mContext.getResources();
- ArrayList<String> providerPackageNames = new ArrayList<String>();
+ ArrayList<String> providerPackageNames = new ArrayList<>();
String[] pkgs = resources.getStringArray(
com.android.internal.R.array.config_locationProviderPackageNames);
if (D) Log.d(TAG, "certificates for location providers pulled from: " +
@@ -651,7 +709,7 @@
final boolean mHideFromAppOps; // True if AppOps should not monitor this receiver.
final Object mKey;
- final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<String,UpdateRecord>();
+ final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<>();
// True if app ops has started monitoring this receiver for locations.
boolean mOpMonitoring;
@@ -691,10 +749,7 @@
@Override
public boolean equals(Object otherObj) {
- if (otherObj instanceof Receiver) {
- return mKey.equals(((Receiver)otherObj).mKey);
- }
- return false;
+ return (otherObj instanceof Receiver) && mKey.equals(((Receiver) otherObj).mKey);
}
@Override
@@ -1011,13 +1066,25 @@
mProvidersByName.remove(provider.getName());
}
+ private boolean isOverlayProviderPackageLocked(String packageName) {
+ for (LocationProviderInterface provider : mProviders) {
+ if (provider instanceof LocationProviderProxy) {
+ if (packageName.equals(
+ ((LocationProviderProxy) provider).getConnectedPackageName())) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
/**
* Returns "true" if access to the specified location provider is allowed by the current
* user's settings. Access to all location providers is forbidden to non-location-provider
* processes belonging to background users.
*
* @param provider the name of the location provider
- * @return
*/
private boolean isAllowedByCurrentUserSettingsLocked(String provider) {
if (mEnabledProviders.contains(provider)) {
@@ -1039,7 +1106,6 @@
*
* @param provider the name of the location provider
* @param uid the requestor's UID
- * @return
*/
private boolean isAllowedByUserSettingsLocked(String provider, int uid) {
if (!isCurrentProfile(UserHandle.getUserId(uid)) && !isUidALocationProvider(uid)) {
@@ -1197,11 +1263,7 @@
}
}
- if (getAllowedResolutionLevel(pid, uid) < allowedResolutionLevel) {
- return false;
- }
-
- return true;
+ return getAllowedResolutionLevel(pid, uid) >= allowedResolutionLevel;
}
boolean checkLocationAccess(int pid, int uid, String packageName, int allowedResolutionLevel) {
@@ -1212,11 +1274,7 @@
}
}
- if (getAllowedResolutionLevel(pid, uid) < allowedResolutionLevel) {
- return false;
- }
-
- return true;
+ return getAllowedResolutionLevel(pid, uid) >= allowedResolutionLevel;
}
/**
@@ -1228,7 +1286,7 @@
public List<String> getAllProviders() {
ArrayList<String> out;
synchronized (mLock) {
- out = new ArrayList<String>(mProviders.size());
+ out = new ArrayList<>(mProviders.size());
for (LocationProviderInterface provider : mProviders) {
String name = provider.getName();
if (LocationManager.FUSED_PROVIDER.equals(name)) {
@@ -1251,11 +1309,11 @@
public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
ArrayList<String> out;
- int uid = Binder.getCallingUid();;
+ int uid = Binder.getCallingUid();
long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- out = new ArrayList<String>(mProviders.size());
+ out = new ArrayList<>(mProviders.size());
for (LocationProviderInterface provider : mProviders) {
String name = provider.getName();
if (LocationManager.FUSED_PROVIDER.equals(name)) {
@@ -1370,14 +1428,12 @@
ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
if (records != null) {
- final int N = records.size();
- for (int i = 0; i < N; i++) {
- UpdateRecord record = records.get(i);
+ for (UpdateRecord record : records) {
if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mUid))) {
// Sends a notification message to the receiver
if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) {
if (deadReceivers == null) {
- deadReceivers = new ArrayList<Receiver>();
+ deadReceivers = new ArrayList<>();
}
deadReceivers.add(record.mReceiver);
}
@@ -1410,6 +1466,12 @@
WorkSource worksource = new WorkSource();
ProviderRequest providerRequest = new ProviderRequest();
+ ContentResolver resolver = mContext.getContentResolver();
+ long backgroundThrottleInterval = Settings.Global.getLong(
+ resolver,
+ Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
+ DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS);
+
if (records != null) {
for (UpdateRecord record : records) {
if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mUid))) {
@@ -1419,10 +1481,22 @@
record.mReceiver.mPackageName,
record.mReceiver.mAllowedResolutionLevel)) {
LocationRequest locationRequest = record.mRequest;
+ long interval = locationRequest.getInterval();
+
+ if (!isThrottlingExemptLocked(record.mReceiver)) {
+ if (!record.mIsForegroundUid) {
+ interval = Math.max(interval, backgroundThrottleInterval);
+ }
+ if (interval != locationRequest.getInterval()) {
+ locationRequest = new LocationRequest(locationRequest);
+ locationRequest.setInterval(interval);
+ }
+ }
+
providerRequest.locationRequests.add(locationRequest);
- if (locationRequest.getInterval() < providerRequest.interval) {
+ if (interval < providerRequest.interval) {
providerRequest.reportLocation = true;
- providerRequest.interval = locationRequest.getInterval();
+ providerRequest.interval = interval;
}
}
}
@@ -1468,10 +1542,15 @@
p.setRequest(providerRequest, worksource);
}
+ private boolean isThrottlingExemptLocked(Receiver recevier) {
+ return isOverlayProviderPackageLocked(recevier.mPackageName);
+ }
+
private class UpdateRecord {
final String mProvider;
final LocationRequest mRequest;
final Receiver mReceiver;
+ boolean mIsForegroundUid;
Location mLastFixBroadcast;
long mLastStatusBroadcast;
@@ -1482,10 +1561,12 @@
mProvider = provider;
mRequest = request;
mReceiver = receiver;
+ mIsForegroundUid = isImportanceForeground(
+ mActivityManager.getPackageImportance(mReceiver.mPackageName));
ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
if (records == null) {
- records = new ArrayList<UpdateRecord>();
+ records = new ArrayList<>();
mRecordsByProvider.put(provider, records);
}
if (!records.contains(this)) {
@@ -1517,7 +1598,7 @@
receiverRecords.remove(this.mProvider);
// and also remove the Receiver if it has no more update records
- if (removeReceiver && receiverRecords.size() == 0) {
+ if (receiverRecords.size() == 0) {
removeUpdatesLocked(mReceiver);
}
}
@@ -1525,14 +1606,9 @@
@Override
public String toString() {
- StringBuilder s = new StringBuilder();
- s.append("UpdateRecord[");
- s.append(mProvider);
- s.append(' ').append(mReceiver.mPackageName).append('(');
- s.append(mReceiver.mUid).append(')');
- s.append(' ').append(mRequest);
- s.append(']');
- return s.toString();
+ return "UpdateRecord[" + mProvider + " " + mReceiver.mPackageName
+ + "(" + mReceiver.mUid + (mIsForegroundUid ? " foreground" : " background")
+ + ")" + " " + mRequest + "]";
}
}
@@ -1681,14 +1757,17 @@
throw new IllegalArgumentException("provider name must not be null");
}
- if (D) Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
- + " " + name + " " + request + " from " + packageName + "(" + uid + ")");
LocationProviderInterface provider = mProvidersByName.get(name);
if (provider == null) {
throw new IllegalArgumentException("provider doesn't exist: " + name);
}
UpdateRecord record = new UpdateRecord(name, request, receiver);
+ if (D) Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
+ + " " + name + " " + request + " from " + packageName + "(" + uid + " "
+ + (record.mIsForegroundUid ? "foreground" : "background")
+ + (isOverlayProviderPackageLocked(receiver.mPackageName) ? " [whitelisted]" : "") + ")");
+
UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record);
if (oldRecord != null) {
oldRecord.disposeLocked(false);
@@ -1743,7 +1822,7 @@
receiver.updateMonitoring(false);
// Record which providers were associated with this listener
- HashSet<String> providers = new HashSet<String>();
+ HashSet<String> providers = new HashSet<>();
HashMap<String, UpdateRecord> oldRecords = receiver.mUpdateRecords;
if (oldRecords != null) {
// Call dispose() on the obsolete update records.
@@ -2084,9 +2163,7 @@
try {
synchronized (mLock) {
LocationProviderInterface p = mProvidersByName.get(provider);
- if (p == null) return false;
-
- return isAllowedByUserSettingsLocked(provider, uid);
+ return p != null && isAllowedByUserSettingsLocked(provider, uid);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -2197,11 +2274,7 @@
}
// Check whether the expiry date has passed
- if (record.mRequest.getExpireAt() < now) {
- return false;
- }
-
- return true;
+ return record.mRequest.getExpireAt() >= now;
}
private void handleLocationChangedLocked(Location location, boolean passive) {
@@ -2216,7 +2289,7 @@
// Update last known locations
Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
- Location lastNoGPSLocation = null;
+ Location lastNoGPSLocation;
Location lastLocation = mLastLocation.get(provider);
if (lastLocation == null) {
lastLocation = new Location(provider);
@@ -2296,7 +2369,7 @@
continue;
}
- Location notifyLocation = null;
+ Location notifyLocation;
if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
notifyLocation = coarseLocation; // use coarse location
} else {
@@ -2333,14 +2406,14 @@
// track expired records
if (r.mRequest.getNumUpdates() <= 0 || r.mRequest.getExpireAt() < now) {
if (deadUpdateRecords == null) {
- deadUpdateRecords = new ArrayList<UpdateRecord>();
+ deadUpdateRecords = new ArrayList<>();
}
deadUpdateRecords.add(r);
}
// track dead receivers
if (receiverDead) {
if (deadReceivers == null) {
- deadReceivers = new ArrayList<Receiver>();
+ deadReceivers = new ArrayList<>();
}
if (!deadReceivers.contains(receiver)) {
deadReceivers.add(receiver);
@@ -2417,7 +2490,7 @@
for (Receiver receiver : mReceivers.values()) {
if (receiver.mPackageName.equals(packageName)) {
if (deadReceivers == null) {
- deadReceivers = new ArrayList<Receiver>();
+ deadReceivers = new ArrayList<>();
}
deadReceivers.add(receiver);
}
@@ -2694,6 +2767,13 @@
pw.println(" " + record);
}
}
+ pw.println(" Overlay Provider Packages:");
+ for (LocationProviderInterface provider : mProviders) {
+ if (provider instanceof LocationProviderProxy) {
+ pw.println(" " + provider.getName() + ": "
+ + ((LocationProviderProxy) provider).getConnectedPackageName());
+ }
+ }
pw.println(" Historical Records by Provider:");
for (Map.Entry<PackageProviderKey, PackageStatistics> entry
: mRequestStatistics.statistics.entrySet()) {
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 51503c0..2d40e8e 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -1196,9 +1196,11 @@
VerifyCredentialResponse response = verifyCredential(userId, storedHash, credentialToVerify,
hasChallenge, challenge, progressCallback);
- if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK
- && shouldReEnrollBaseZero) {
- setLockCredentialInternal(credential, storedHash.type, credentialToVerify, userId);
+ if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+ mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
+ if (shouldReEnrollBaseZero) {
+ setLockCredentialInternal(credential, storedHash.type, credentialToVerify, userId);
+ }
}
return response;
diff --git a/services/core/java/com/android/server/LockSettingsStrongAuth.java b/services/core/java/com/android/server/LockSettingsStrongAuth.java
index 551ceb8..1314110 100644
--- a/services/core/java/com/android/server/LockSettingsStrongAuth.java
+++ b/services/core/java/com/android/server/LockSettingsStrongAuth.java
@@ -16,23 +16,30 @@
package com.android.server;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
+
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
+import android.app.AlarmManager;
+import android.app.AlarmManager.OnAlarmListener;
+import android.app.admin.DevicePolicyManager;
import android.app.trust.IStrongAuthTracker;
import android.content.Context;
+import android.os.Binder;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
+import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseIntArray;
import java.util.ArrayList;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
-
/**
* Keeps track of requests for strong authentication.
*/
@@ -45,12 +52,22 @@
private static final int MSG_UNREGISTER_TRACKER = 3;
private static final int MSG_REMOVE_USER = 4;
+ private static final String STRONG_AUTH_TIMEOUT_ALARM_TAG =
+ "LockSettingsStrongAuth.timeoutForUser";
+
private final ArrayList<IStrongAuthTracker> mStrongAuthTrackers = new ArrayList<>();
private final SparseIntArray mStrongAuthForUser = new SparseIntArray();
+ private final ArrayMap<Integer, StrongAuthTimeoutAlarmListener>
+ mStrongAuthTimeoutAlarmListenerForUser = new ArrayMap<>();
private final int mDefaultStrongAuthFlags;
+ private final Context mContext;
+
+ private AlarmManager mAlarmManager;
public LockSettingsStrongAuth(Context context) {
+ mContext = context;
mDefaultStrongAuthFlags = StrongAuthTracker.getDefaultFlags(context);
+ mAlarmManager = context.getSystemService(AlarmManager.class);
}
private void handleAddStrongAuthTracker(IStrongAuthTracker tracker) {
@@ -151,6 +168,46 @@
requireStrongAuth(STRONG_AUTH_NOT_REQUIRED, userId);
}
+ public void reportSuccessfulStrongAuthUnlock(int userId) {
+ scheduleStrongAuthTimeout(userId);
+ }
+
+ private void scheduleStrongAuthTimeout(int userId) {
+ final DevicePolicyManager dpm =
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ long when = SystemClock.elapsedRealtime() + dpm.getRequiredStrongAuthTimeout(null, userId);
+ // cancel current alarm listener for the user (if there was one)
+ StrongAuthTimeoutAlarmListener alarm = mStrongAuthTimeoutAlarmListenerForUser.get(userId);
+ if (alarm != null) {
+ mAlarmManager.cancel(alarm);
+ } else {
+ alarm = new StrongAuthTimeoutAlarmListener(userId);
+ mStrongAuthTimeoutAlarmListenerForUser.put(userId, alarm);
+ }
+ // schedule a new alarm listener for the user
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when, STRONG_AUTH_TIMEOUT_ALARM_TAG,
+ alarm, mHandler);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private class StrongAuthTimeoutAlarmListener implements OnAlarmListener {
+
+ private final int mUserId;
+
+ public StrongAuthTimeoutAlarmListener(int userId) {
+ mUserId = userId;
+ }
+
+ @Override
+ public void onAlarm() {
+ requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, mUserId);
+ }
+ }
+
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 94acd75..e11dd1a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -33,6 +33,7 @@
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.IActivityManager;
+import android.app.usage.StorageStatsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -3270,6 +3271,29 @@
}
}
+ @Override
+ public long getCacheQuotaBytes(String volumeUuid, int uid) {
+ if (uid != Binder.getCallingUid()) {
+ mContext.enforceCallingPermission(android.Manifest.permission.STORAGE_INTERNAL, TAG);
+ }
+ // TODO: wire up to cache quota once merged
+ return 64 * TrafficStats.MB_IN_BYTES;
+ }
+
+ @Override
+ public long getCacheSizeBytes(String volumeUuid, int uid) {
+ if (uid != Binder.getCallingUid()) {
+ mContext.enforceCallingPermission(android.Manifest.permission.STORAGE_INTERNAL, TAG);
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mContext.getSystemService(StorageStatsManager.class)
+ .queryStatsForUid(volumeUuid, uid).getCacheBytes();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private void addObbStateLocked(ObbState obbState) throws RemoteException {
final IBinder binder = obbState.getBinder();
List<ObbState> obbStates = mObbMounts.get(binder);
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index d879919..3f97d4f 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -36,7 +36,6 @@
private final Context mContext;
private boolean mSafeMode;
private boolean mRuntimeRestarted;
- private boolean mFirstBoot;
// Services that should receive lifecycle events.
private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
@@ -260,16 +259,6 @@
mRuntimeRestarted = runtimeRestarted;
}
- /**
- * @return true if it's first boot after OTA
- */
- public boolean isFirstBoot() {
- return mFirstBoot;
- }
-
- void setFirstBoot(boolean firstBoot) {
- mFirstBoot = firstBoot;
- }
/**
* Outputs the state of this manager to the System log.
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4b89b40..5655dc5 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -349,8 +349,8 @@
try {
// Before going further -- if this app is not allowed to start services in the
// background, then at this point we aren't going to let it period.
- final int allowed = mAm.checkAllowBackgroundLocked(
- r.appInfo.uid, r.packageName, callingPid, false);
+ final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
+ r.appInfo.targetSdkVersion, callingPid, false, false);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
Slog.w(TAG, "Background start not allowed: service "
+ service + " to " + r.name.flattenToShortString()
@@ -607,8 +607,9 @@
for (int i=services.mServicesByName.size()-1; i>=0; i--) {
ServiceRecord service = services.mServicesByName.valueAt(i);
if (service.appInfo.uid == uid && service.startRequested) {
- if (mAm.checkAllowBackgroundLocked(service.appInfo.uid, service.packageName,
- -1, false) != ActivityManager.APP_START_MODE_NORMAL) {
+ if (mAm.getAppStartModeLocked(service.appInfo.uid, service.packageName,
+ service.appInfo.targetSdkVersion, -1, false, false)
+ != ActivityManager.APP_START_MODE_NORMAL) {
if (stopping == null) {
stopping = new ArrayList<>();
stopping.add(service);
@@ -707,7 +708,7 @@
try {
ServiceRecord r = findServiceLocked(className, token, userId);
if (r != null) {
- setServiceForegroundInnerLocked(r, userId, notification, flags);
+ setServiceForegroundInnerLocked(r, id, notification, flags);
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f99dfed..e7e07fe 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -22,6 +22,7 @@
import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
@@ -4936,7 +4937,7 @@
}
@Override
- public void crashApplication(int uid, int initialPid, String packageName,
+ public void crashApplication(int uid, int initialPid, String packageName, int userId,
String message) {
if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
!= PackageManager.PERMISSION_GRANTED) {
@@ -4949,7 +4950,7 @@
}
synchronized(this) {
- mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, message);
+ mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, userId, message);
}
}
@@ -7284,18 +7285,23 @@
Slog.d(TAG, "tempWhitelistAppForPowerSave(" + callerPid + ", " + callerUid + ", "
+ targetUid + ", " + duration + ")");
}
- synchronized (mPidsSelfLocked) {
- final ProcessRecord pr = mPidsSelfLocked.get(callerPid);
- if (pr == null) {
- Slog.w(TAG, "tempWhitelistAppForPowerSave() no ProcessRecord for pid " + callerPid);
- return;
- }
- if (!pr.whitelistManager) {
- if (DEBUG_WHITELISTS) {
- Slog.d(TAG, "tempWhitelistAppForPowerSave() for target " + targetUid + ": pid "
- + callerPid + " is not allowed");
+
+ if (checkPermission(CHANGE_DEVICE_IDLE_TEMP_WHITELIST, callerPid, callerUid)
+ != PackageManager.PERMISSION_GRANTED) {
+ synchronized (mPidsSelfLocked) {
+ final ProcessRecord pr = mPidsSelfLocked.get(callerPid);
+ if (pr == null) {
+ Slog.w(TAG, "tempWhitelistAppForPowerSave() no ProcessRecord for pid "
+ + callerPid);
+ return;
}
- return;
+ if (!pr.whitelistManager) {
+ if (DEBUG_WHITELISTS) {
+ Slog.d(TAG, "tempWhitelistAppForPowerSave() for target " + targetUid
+ + ": pid " + callerPid + " is not allowed");
+ }
+ return;
+ }
}
}
@@ -8020,65 +8026,42 @@
return readMet && writeMet;
}
- public int getAppStartMode(int uid, String packageName) {
+ public boolean isAppStartModeDisabled(int uid, String packageName) {
synchronized (this) {
- return checkAllowBackgroundLocked(uid, packageName, -1, false);
+ return getAppStartModeLocked(uid, packageName, 0, -1, false, true)
+ == ActivityManager.APP_START_MODE_DISABLED;
}
}
// Unified app-op and target sdk check
- int appRestrictedInBackgroundLocked(int uid, String packageName) {
- if (packageName == null) {
- packageName = mPackageManagerInt.getNameForUid(uid);
- if (packageName == null) {
- Slog.w(TAG, "No package known for uid " + uid);
- return ActivityManager.APP_START_MODE_NORMAL;
- }
- }
-
- // !!! TODO: cache the package/versionCode lookups to fast path this
- ApplicationInfo app = getPackageManagerInternalLocked().getApplicationInfo(packageName,
- UserHandle.getUserId(uid));
- if (app != null) {
- // Apps that target O+ are always subject to background check
- if (mEnforceBackgroundCheck && app.targetSdkVersion >= Build.VERSION_CODES.O) {
- if (DEBUG_BACKGROUND_CHECK) {
- Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");
- }
- return ActivityManager.APP_START_MODE_DELAYED_RIGID;
- }
- // ...and legacy apps get an AppOp check
- int appop = mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
- uid, packageName);
+ int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
+ // Apps that target O+ are always subject to background check
+ if (mEnforceBackgroundCheck && packageTargetSdk >= Build.VERSION_CODES.O) {
if (DEBUG_BACKGROUND_CHECK) {
- Slog.i(TAG, "Legacy app " + uid + "/" + packageName + " bg appop " + appop);
+ Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");
}
- switch (appop) {
- case AppOpsManager.MODE_ALLOWED:
- return ActivityManager.APP_START_MODE_NORMAL;
- case AppOpsManager.MODE_IGNORED:
- return ActivityManager.APP_START_MODE_DELAYED;
- default:
- return ActivityManager.APP_START_MODE_DELAYED_RIGID;
- }
- } else {
- Slog.w(TAG, "Unknown app " + packageName + " / " + uid);
+ return ActivityManager.APP_START_MODE_DELAYED_RIGID;
}
- return ActivityManager.APP_START_MODE_NORMAL;
+ // ...and legacy apps get an AppOp check
+ int appop = mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
+ uid, packageName);
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Legacy app " + uid + "/" + packageName + " bg appop " + appop);
+ }
+ switch (appop) {
+ case AppOpsManager.MODE_ALLOWED:
+ return ActivityManager.APP_START_MODE_NORMAL;
+ case AppOpsManager.MODE_IGNORED:
+ return ActivityManager.APP_START_MODE_DELAYED;
+ default:
+ return ActivityManager.APP_START_MODE_DELAYED_RIGID;
+ }
}
// Service launch is available to apps with run-in-background exemptions but
// some other background operations are not. If we're doing a check
// of service-launch policy, allow those callers to proceed unrestricted.
- int appServicesRestrictedInBackgroundLocked(int uid, String packageName) {
- if (packageName == null) {
- packageName = mPackageManagerInt.getNameForUid(uid);
- if (packageName == null) {
- Slog.w(TAG, "No package known for uid " + uid);
- return ActivityManager.APP_START_MODE_NORMAL;
- }
- }
-
+ int appServicesRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
// Persistent app? NB: expects that persistent uids are always active.
final UidRecord uidRec = mActiveUids.get(uid);
if (uidRec != null && uidRec.persistent) {
@@ -8108,11 +8091,11 @@
}
// None of the service-policy criteria apply, so we apply the common criteria
- return appRestrictedInBackgroundLocked(uid, packageName);
+ return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
}
- int checkAllowBackgroundLocked(int uid, String packageName, int callingPid,
- boolean alwaysRestrict) {
+ int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
+ int callingPid, boolean alwaysRestrict, boolean disabledOnly) {
UidRecord uidRec = mActiveUids.get(uid);
if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid + " pkg="
+ packageName + " rec=" + uidRec + " always=" + alwaysRestrict + " idle="
@@ -8130,27 +8113,37 @@
// We are hard-core about ephemeral apps not running in the background.
return ActivityManager.APP_START_MODE_DISABLED;
} else {
- /** Don't want to allow this exception in the final background check impl?
- if (callingPid >= 0) {
- ProcessRecord proc;
- synchronized (mPidsSelfLocked) {
- proc = mPidsSelfLocked.get(callingPid);
- }
- if (proc != null && proc.curProcState
- < ActivityManager.PROCESS_STATE_RECEIVER) {
- // Whoever is instigating this is in the foreground, so we will allow it
- // to go through.
- return ActivityManager.APP_START_MODE_NORMAL;
- }
+ if (disabledOnly) {
+ // The caller is only interested in whether app starts are completely
+ // disabled for the given package (that is, it is an instant app). So
+ // we don't need to go further, which is all just seeing if we should
+ // apply a "delayed" mode for a regular app.
+ return ActivityManager.APP_START_MODE_NORMAL;
}
- */
-
final int startMode = (alwaysRestrict)
- ? appRestrictedInBackgroundLocked(uid, packageName)
- : appServicesRestrictedInBackgroundLocked(uid, packageName);
+ ? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk)
+ : appServicesRestrictedInBackgroundLocked(uid, packageName,
+ packageTargetSdk);
if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid
+ " pkg=" + packageName + " startMode=" + startMode
+ " onwhitelist=" + isOnDeviceIdleWhitelistLocked(uid));
+ if (startMode == ActivityManager.APP_START_MODE_DELAYED) {
+ // This is an old app that has been forced into a "compatible as possible"
+ // mode of background check. To increase compatibility, we will allow other
+ // foreground apps to cause its services to start.
+ if (callingPid >= 0) {
+ ProcessRecord proc;
+ synchronized (mPidsSelfLocked) {
+ proc = mPidsSelfLocked.get(callingPid);
+ }
+ if (proc != null && proc.curProcState
+ < ActivityManager.PROCESS_STATE_RECEIVER) {
+ // Whoever is instigating this is in the foreground, so we will allow it
+ // to go through.
+ return ActivityManager.APP_START_MODE_NORMAL;
+ }
+ }
+ }
return startMode;
}
}
@@ -9765,15 +9758,17 @@
enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
final long ident = Binder.clearCallingIdentity();
try {
+ final TaskRecord task;
synchronized (this) {
- final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(
+ task = mStackSupervisor.anyTaskForIdLocked(
taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID);
if (task == null) {
Slog.w(TAG, "getTaskSnapshot: taskId=" + taskId + " not found");
return null;
}
- return task.getSnapshot();
}
+ // Don't call this while holding the lock as this operation might hit the disk.
+ return task.getSnapshot();
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -19620,7 +19615,8 @@
&& config.navigation == Configuration.NAVIGATION_NONAV);
int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK;
final boolean uiModeSupportsDialogs = (modeType != Configuration.UI_MODE_TYPE_CAR
- && !(modeType == Configuration.UI_MODE_TYPE_WATCH && "user".equals(Build.TYPE)));
+ && !(modeType == Configuration.UI_MODE_TYPE_WATCH && "user".equals(Build.TYPE))
+ && modeType != Configuration.UI_MODE_TYPE_TELEVISION);
return inputMethodExists && uiModeSupportsDialogs && !inVrMode;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 202868a..1a2a31b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -167,6 +167,8 @@
return runBugReport(pw);
case "force-stop":
return runForceStop(pw);
+ case "crash":
+ return runCrash(pw);
case "kill":
return runKill(pw);
case "kill-all":
@@ -851,6 +853,32 @@
return 0;
}
+ int runCrash(PrintWriter pw) throws RemoteException {
+ int userId = UserHandle.USER_ALL;
+
+ String opt;
+ while ((opt=getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+
+ int pid = -1;
+ String packageName = null;
+ final String arg = getNextArgRequired();
+ // The argument is either a pid or a package name
+ try {
+ pid = Integer.parseInt(arg);
+ } catch (NumberFormatException e) {
+ packageName = arg;
+ }
+ mInterface.crashApplication(-1, pid, packageName, userId, "shell-induced crash");
+ return 0;
+ }
+
int runKill(PrintWriter pw) throws RemoteException {
int userId = UserHandle.USER_ALL;
@@ -2480,6 +2508,8 @@
pw.println(" --telephony: will dump only telephony sections.");
pw.println(" force-stop [--user <USER_ID> | all | current] <PACKAGE>");
pw.println(" Completely stop the given application package.");
+ pw.println(" crash [--user <USER_ID>] <PACKAGE|PID>");
+ pw.println(" Induce a VM crash in the specified package or process");
pw.println(" kill [--user <USER_ID> | all | current] <PACKAGE>");
pw.println(" Kill all processes associated with the given application.");
pw.println(" kill-all");
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index e46d204..65b8554 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -3,9 +3,7 @@
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -17,7 +15,7 @@
import android.os.SystemClock;
import android.util.Slog;
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -172,7 +170,7 @@
MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS,
(int) (SystemClock.uptimeMillis() / 1000));
- LogBuilder builder = new LogBuilder(MetricsEvent.APP_TRANSITION);
+ LogMaker builder = new LogMaker(MetricsEvent.APP_TRANSITION);
builder.addTaggedData(MetricsEvent.APP_TRANSITION_COMPONENT_NAME, componentName);
builder.addTaggedData(MetricsEvent.APP_TRANSITION_PROCESS_RUNNING, processRunning ? 1 : 0);
builder.addTaggedData(MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS,
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index a968e0b..3bd44f5 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -22,6 +22,8 @@
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
@@ -64,6 +66,7 @@
import android.annotation.NonNull;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityOptions;
+import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.app.PictureInPictureArgs;
import android.app.ResultInfo;
@@ -75,6 +78,7 @@
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
@@ -911,13 +915,15 @@
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,6 +932,19 @@
}
}
+ /**
+ * @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 canGoInDockedStack() {
return !isHomeActivity() && isResizeableOrForced();
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index d7b3728e..963a9dc 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1795,6 +1795,12 @@
}
}
+ void addStartingWindowsForVisibleActivities(boolean taskSwitch) {
+ for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+ mTaskHistory.get(taskNdx).addStartingWindowsForVisibleActivities(taskSwitch);
+ }
+ }
+
/**
* @return true if the top visible activity wants to occlude the Keyguard, false otherwise
*/
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 14899b4..2c1c3a1 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3229,6 +3229,17 @@
}
}
+ void addStartingWindowsForVisibleActivities(boolean taskSwitch) {
+ for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+ final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
+ final int topStackNdx = stacks.size() - 1;
+ for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = stacks.get(stackNdx);
+ stack.addStartingWindowsForVisibleActivities(taskSwitch);
+ }
+ }
+ }
+
void invalidateTaskLayers() {
mTaskLayersChanged = true;
}
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 26d2ee2..873f2b7 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -460,6 +460,7 @@
final String splitName = rInfo.ephemeralResponse.splitName;
final boolean needsPhaseTwo = rInfo.ephemeralResponse.needsPhase2;
final String token = rInfo.ephemeralResponse.token;
+ final int versionCode = rInfo.ephemeralResponse.resolveInfo.getVersionCode();
if (needsPhaseTwo) {
// request phase two resolution
mService.getPackageManagerInternalLocked().requestEphemeralResolutionPhaseTwo(
@@ -467,8 +468,8 @@
callingPackage, userId);
}
intent = EphemeralResolver.buildEphemeralInstallerIntent(intent, ephemeralIntent,
- callingPackage, resolvedType, userId, packageName, splitName, token,
- needsPhaseTwo);
+ callingPackage, resolvedType, userId, packageName, splitName, versionCode,
+ token, needsPhaseTwo);
resolvedType = null;
callingUid = realCallingUid;
callingPid = realCallingPid;
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 739a8c4..384f2f8 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -259,7 +259,16 @@
}
}
- void scheduleAppCrashLocked(int uid, int initialPid, String packageName,
+ /**
+ * Induce a crash in the given app.
+ *
+ * @param uid if nonnegative, the required matching uid of the target to crash
+ * @param initialPid fast-path match for the target to crash
+ * @param packageName fallback match if the stated pid is not found or doesn't match uid
+ * @param userId If nonnegative, required to identify a match by package name
+ * @param message
+ */
+ void scheduleAppCrashLocked(int uid, int initialPid, String packageName, int userId,
String message) {
ProcessRecord proc = null;
@@ -270,14 +279,15 @@
synchronized (mService.mPidsSelfLocked) {
for (int i=0; i<mService.mPidsSelfLocked.size(); i++) {
ProcessRecord p = mService.mPidsSelfLocked.valueAt(i);
- if (p.uid != uid) {
+ if (uid >= 0 && p.uid != uid) {
continue;
}
if (p.pid == initialPid) {
proc = p;
break;
}
- if (p.pkgList.containsKey(packageName)) {
+ if (p.pkgList.containsKey(packageName)
+ && (userId < 0 || p.userId == userId)) {
proc = p;
}
}
@@ -286,7 +296,8 @@
if (proc == null) {
Slog.w(TAG, "crashApplication: nothing for uid=" + uid
+ " initialPid=" + initialPid
- + " packageName=" + packageName);
+ + " packageName=" + packageName
+ + " userId=" + userId);
return;
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 61e555b..ee2467a 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -592,22 +592,6 @@
+ " (uid " + r.callingUid + ")");
skip = true;
}
- if (!skip) {
- final int allowed = mService.checkAllowBackgroundLocked(filter.receiverList.uid,
- filter.packageName, -1, false);
- if (false && allowed == ActivityManager.APP_START_MODE_DISABLED) {
- // XXX should we really not allow this? It means that while we are
- // keeping an ephemeral app cached, its registered receivers will stop
- // receiving broadcasts after it goes idle... so if it comes back to
- // the foreground, it won't know what the current state of those broadcasts is.
- Slog.w(TAG, "Background execution not allowed: receiving "
- + r.intent
- + " to " + filter.receiverList.app
- + " (pid=" + filter.receiverList.pid
- + ", uid=" + filter.receiverList.uid + ")");
- skip = true;
- }
- }
if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
r.callingPid, r.resolvedType, filter.receiverList.uid)) {
@@ -1156,13 +1140,14 @@
info.activityInfo.applicationInfo.uid, false);
if (!skip) {
- final int allowed = mService.checkAllowBackgroundLocked(
- info.activityInfo.applicationInfo.uid, info.activityInfo.packageName, -1, true);
+ final int allowed = mService.getAppStartModeLocked(
+ info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
+ info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
// We won't allow this receiver to be launched if the app has been
// completely disabled from launches, or it was not explicitly sent
// to it and the app is in a state that should not receive it
- // (depending on how checkAllowBackgroundLocked has determined that).
+ // (depending on how getAppStartModeLocked has determined that).
if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
Slog.w(TAG, "Background execution disabled: receiving "
+ r.intent + " to "
diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java
index cfe2eb0..b0a4746 100644
--- a/services/core/java/com/android/server/am/KeyguardController.java
+++ b/services/core/java/com/android/server/am/KeyguardController.java
@@ -29,6 +29,7 @@
import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_GOING_AWAY;
import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_OCCLUDE;
import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static com.android.server.wm.AppTransition.TRANSIT_NONE;
import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
import android.os.IBinder;
@@ -120,6 +121,7 @@
// Some stack visibility might change (e.g. docked stack)
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+ mStackSupervisor.addStartingWindowsForVisibleActivities(true /* taskSwitch */);
mWindowManager.executeAppTransition();
} finally {
mWindowManager.continueSurfaceLayout();
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 71c7fd3..82b00da 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -534,7 +534,7 @@
// get to be foreground.
ams.setServiceForeground(name, ServiceRecord.this,
0, null, 0);
- ams.crashApplication(appUid, appPid, localPackageName,
+ ams.crashApplication(appUid, appPid, localPackageName, -1,
"Bad notification for startForeground: " + e);
}
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index e550ac88..7b4d289 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -416,7 +416,7 @@
final Configuration overrideConfig = getOverrideConfiguration();
mWindowContainerController = new TaskWindowContainerController(taskId, this, getStackId(),
userId, bounds, overrideConfig, mResizeMode, isHomeTask(), isOnTopLauncher(), onTop,
- showForAllUsers);
+ showForAllUsers, lastTaskDescription);
}
void removeWindowContainer() {
@@ -583,11 +583,14 @@
mWindowContainerController.cancelThumbnailTransition();
}
- public TaskSnapshot getSnapshot() {
- if (mWindowContainerController == null) {
- return null;
- }
- return mWindowContainerController.getSnapshot();
+ /**
+ * DO NOT HOLD THE ACTIVITY MANAGER LOCK WHEN CALLING THIS METHOD!
+ */
+ TaskSnapshot getSnapshot() {
+
+ // TODO: Move this to {@link TaskWindowContainerController} once recent tasks are more
+ // synchronized between AM and WM.
+ return mService.mWindowManager.getTaskSnapshot(taskId, userId);
}
void touchActiveTime() {
@@ -1399,6 +1402,9 @@
}
lastTaskDescription = new TaskDescription(label, null, iconFilename, colorPrimary,
colorBackground);
+ if (mWindowContainerController != null) {
+ mWindowContainerController.setTaskDescription(lastTaskDescription);
+ }
// Update the task affiliation color if we are the parent of the group
if (taskId == mAffiliatedTaskId) {
mAffiliatedTaskColor = lastTaskDescription.getPrimaryColor();
@@ -1978,6 +1984,15 @@
return rootAffinity != null && getStackId() != PINNED_STACK_ID;
}
+ void addStartingWindowsForVisibleActivities(boolean taskSwitch) {
+ for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = mActivities.get(activityNdx);
+ if (r.visible) {
+ r.showStartingWindow(null /* prev */, false /* newTask */, taskSwitch);
+ }
+ }
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("userId="); pw.print(userId);
pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 5bf92d7..728476a 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -47,6 +47,7 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.Dialog;
import android.app.IStopUserCallback;
@@ -55,6 +56,7 @@
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.Intent;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.BatteryStats;
@@ -255,7 +257,9 @@
// storage is already unlocked.
if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
mInjector.getUserManagerInternal().setUserState(userId, uss.state);
- if (!mInjector.isRuntimeRestarted() && !mInjector.isFirstBoot()) {
+ // Do not report secondary users, runtime restarts or first boot/upgrade
+ if (userId == UserHandle.USER_SYSTEM
+ && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
MetricsLogger.histogram(mInjector.getContext(),
"framework_locked_boot_completed", uptimeSeconds);
@@ -436,7 +440,9 @@
}
Slog.d(TAG, "Sending BOOT_COMPLETE user #" + userId);
- if (!mInjector.isRuntimeRestarted() && !mInjector.isFirstBoot()) {
+ // Do not report secondary users, runtime restarts or first boot/upgrade
+ if (userId == UserHandle.USER_SYSTEM
+ && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
int uptimeSeconds = (int) (SystemClock.elapsedRealtime() / 1000);
MetricsLogger.histogram(mInjector.getContext(), "framework_boot_completed",
uptimeSeconds);
@@ -1709,8 +1715,13 @@
return mService.mSystemServiceManager.isRuntimeRestarted();
}
- boolean isFirstBoot() {
- return mService.mSystemServiceManager.isFirstBoot();
+ boolean isFirstBootOrUpgrade() {
+ IPackageManager pm = AppGlobals.getPackageManager();
+ try {
+ return pm.isFirstBoot() || pm.isUpgrade();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
void sendPreBootBroadcast(int userId, boolean quiet, final Runnable onFinish) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index df5f01d..31ef94f 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 @@
* 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 @@
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 b82dd60..b0e4509 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -76,6 +76,7 @@
import com.android.server.connectivity.tethering.IControlsTethering;
import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
import com.android.server.connectivity.tethering.IPv6TetheringInterfaceServices;
+import com.android.server.connectivity.tethering.TetheringConfiguration;
import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
import com.android.server.connectivity.tethering.UpstreamNetworkMonitor;
import com.android.server.net.BaseNetworkObserver;
@@ -118,10 +119,6 @@
// used to synchronize public access to members
private final Object mPublicSync;
- private static final Integer MOBILE_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE);
- private static final Integer HIPRI_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE_HIPRI);
- private static final Integer DUN_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE_DUN);
-
private final INetworkManagementService mNMService;
private final INetworkStatsService mStatsService;
private final INetworkPolicyManager mPolicyManager;
@@ -978,8 +975,6 @@
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) {
@@ -1013,43 +1008,14 @@
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() {
@@ -1131,11 +1097,10 @@
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);
@@ -1690,119 +1655,4 @@
private static String[] copy(String[] strarray) {
return Arrays.copyOf(strarray, strarray.length);
}
-
- private static class TetheringConfiguration {
- private static final int DUN_NOT_REQUIRED = 0;
- private static final int DUN_REQUIRED = 1;
- private static final int DUN_UNSPECIFIED = 2;
-
- // USB is 192.168.42.1 and 255.255.255.0
- // Wifi is 192.168.43.1 and 255.255.255.0
- // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
- // with 255.255.255.0
- // P2P is 192.168.49.1 and 255.255.255.0
- private static final String[] DHCP_DEFAULT_RANGE = {
- "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
- "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
- "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
- "192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254",
- };
-
- private final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
-
- public final String[] tetherableUsbRegexs;
- public final String[] tetherableWifiRegexs;
- public final String[] tetherableBluetoothRegexs;
- public final boolean isDunRequired;
- public final Collection<Integer> preferredUpstreamIfaceTypes;
- public final String[] dhcpRanges;
- public final String[] defaultIPv4DNS;
-
- public TetheringConfiguration(Context ctx) {
- tetherableUsbRegexs = ctx.getResources().getStringArray(
- com.android.internal.R.array.config_tether_usb_regexs);
- tetherableWifiRegexs = ctx.getResources().getStringArray(
- com.android.internal.R.array.config_tether_wifi_regexs);
- tetherableBluetoothRegexs = ctx.getResources().getStringArray(
- com.android.internal.R.array.config_tether_bluetooth_regexs);
-
- isDunRequired = checkDunRequired(ctx);
- preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(ctx, isDunRequired);
-
- dhcpRanges = getDhcpRanges(ctx);
- defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
- }
-
- public boolean isUsb(String iface) {
- for (String regex : tetherableUsbRegexs) {
- if (iface.matches(regex)) return true;
- }
- return false;
- }
-
- public boolean isWifi(String iface) {
- for (String regex : tetherableWifiRegexs) {
- if (iface.matches(regex)) return true;
- }
- return false;
- }
-
- public boolean isBluetooth(String iface) {
- for (String regex : tetherableBluetoothRegexs) {
- if (iface.matches(regex)) return true;
- }
- return false;
- }
-
- private static boolean checkDunRequired(Context ctx) {
- final TelephonyManager tm = ctx.getSystemService(TelephonyManager.class);
- final int secureSetting =
- (tm != null) ? tm.getTetherApnRequired() : DUN_UNSPECIFIED;
- return (secureSetting == DUN_REQUIRED);
- }
-
- private static Collection<Integer> getUpstreamIfaceTypes(Context ctx, boolean requiresDun) {
- final int ifaceTypes[] = ctx.getResources().getIntArray(
- com.android.internal.R.array.config_tether_upstream_types);
- final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
- for (int i : ifaceTypes) {
- upstreamIfaceTypes.add(i);
- }
-
- // Fix up upstream interface types for DUN or mobile.
- // TODO: Perform this filtering step in the above for loop.
- if (requiresDun) {
- while (upstreamIfaceTypes.contains(MOBILE_TYPE)) {
- upstreamIfaceTypes.remove(MOBILE_TYPE);
- }
- while (upstreamIfaceTypes.contains(HIPRI_TYPE)) {
- upstreamIfaceTypes.remove(HIPRI_TYPE);
- }
- if (!upstreamIfaceTypes.contains(DUN_TYPE)) {
- upstreamIfaceTypes.add(DUN_TYPE);
- }
- } else {
- while (upstreamIfaceTypes.contains(DUN_TYPE)) {
- upstreamIfaceTypes.remove(DUN_TYPE);
- }
- if (!upstreamIfaceTypes.contains(MOBILE_TYPE)) {
- upstreamIfaceTypes.add(MOBILE_TYPE);
- }
- if (!upstreamIfaceTypes.contains(HIPRI_TYPE)) {
- upstreamIfaceTypes.add(HIPRI_TYPE);
- }
- }
-
- return upstreamIfaceTypes;
- }
-
- private static String[] getDhcpRanges(Context ctx) {
- final String[] fromResource = ctx.getResources().getStringArray(
- com.android.internal.R.array.config_tether_dhcp_range);
- if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
- return fromResource;
- }
- return copy(DHCP_DEFAULT_RANGE);
- }
- }
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
new file mode 100644
index 0000000..14d06cc
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
+import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+
+/**
+ * A utility class to encapsulate the various tethering configuration elements.
+ *
+ * This configuration data includes elements describing upstream properties
+ * (preferred and required types of upstream connectivity as well as default
+ * DNS servers to use if none are available) and downstream properties (such
+ * as regular expressions use to match suitable downstream interfaces and the
+ * DHCPv4 ranges to use).
+ *
+ * @hide
+ */
+public class TetheringConfiguration {
+ private static final String TAG = TetheringConfiguration.class.getSimpleName();
+
+ private static final int DUN_NOT_REQUIRED = 0;
+ private static final int DUN_REQUIRED = 1;
+ private static final int DUN_UNSPECIFIED = 2;
+
+ // USB is 192.168.42.1 and 255.255.255.0
+ // Wifi is 192.168.43.1 and 255.255.255.0
+ // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
+ // with 255.255.255.0
+ // P2P is 192.168.49.1 and 255.255.255.0
+ private static final String[] DHCP_DEFAULT_RANGE = {
+ "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
+ "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
+ "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
+ "192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254",
+ };
+
+ private final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
+
+ public final String[] tetherableUsbRegexs;
+ public final String[] tetherableWifiRegexs;
+ public final String[] tetherableBluetoothRegexs;
+ public final boolean isDunRequired;
+ public final Collection<Integer> preferredUpstreamIfaceTypes;
+ public final String[] dhcpRanges;
+ public final String[] defaultIPv4DNS;
+
+ public TetheringConfiguration(Context ctx) {
+ tetherableUsbRegexs = ctx.getResources().getStringArray(
+ com.android.internal.R.array.config_tether_usb_regexs);
+ tetherableWifiRegexs = ctx.getResources().getStringArray(
+ com.android.internal.R.array.config_tether_wifi_regexs);
+ tetherableBluetoothRegexs = ctx.getResources().getStringArray(
+ com.android.internal.R.array.config_tether_bluetooth_regexs);
+
+ isDunRequired = checkDunRequired(ctx);
+ preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(ctx, isDunRequired);
+
+ dhcpRanges = getDhcpRanges(ctx);
+ defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
+ }
+
+ public boolean isUsb(String iface) {
+ return matchesDownstreamRegexs(iface, tetherableUsbRegexs);
+ }
+
+ public boolean isWifi(String iface) {
+ return matchesDownstreamRegexs(iface, tetherableWifiRegexs);
+ }
+
+ public boolean isBluetooth(String iface) {
+ return matchesDownstreamRegexs(iface, tetherableBluetoothRegexs);
+ }
+
+ private static boolean checkDunRequired(Context ctx) {
+ final TelephonyManager tm = ctx.getSystemService(TelephonyManager.class);
+ final int secureSetting =
+ (tm != null) ? tm.getTetherApnRequired() : DUN_UNSPECIFIED;
+ return (secureSetting == DUN_REQUIRED);
+ }
+
+ private static Collection<Integer> getUpstreamIfaceTypes(Context ctx, boolean requiresDun) {
+ final int ifaceTypes[] = ctx.getResources().getIntArray(
+ com.android.internal.R.array.config_tether_upstream_types);
+ final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
+ for (int i : ifaceTypes) {
+ switch (i) {
+ case TYPE_MOBILE:
+ case TYPE_MOBILE_HIPRI:
+ if (requiresDun) continue;
+ break;
+ case TYPE_MOBILE_DUN:
+ if (!requiresDun) continue;
+ break;
+ }
+ upstreamIfaceTypes.add(i);
+ }
+
+ // Fix up upstream interface types for DUN or mobile. NOTE: independent
+ // of the value of |requiresDun|, cell data of one form or another is
+ // *always* an upstream, regardless of the upstream interface types
+ // specified by configuration resources.
+ if (requiresDun) {
+ if (!upstreamIfaceTypes.contains(TYPE_MOBILE_DUN)) {
+ upstreamIfaceTypes.add(TYPE_MOBILE_DUN);
+ }
+ } else {
+ if (!upstreamIfaceTypes.contains(TYPE_MOBILE)) {
+ upstreamIfaceTypes.add(TYPE_MOBILE);
+ }
+ if (!upstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI)) {
+ upstreamIfaceTypes.add(TYPE_MOBILE_HIPRI);
+ }
+ }
+
+ return upstreamIfaceTypes;
+ }
+
+ private static boolean matchesDownstreamRegexs(String iface, String[] regexs) {
+ for (String regex : regexs) {
+ if (iface.matches(regex)) return true;
+ }
+ return false;
+ }
+
+ private static String[] getDhcpRanges(Context ctx) {
+ final String[] fromResource = ctx.getResources().getStringArray(
+ com.android.internal.R.array.config_tether_dhcp_range);
+ if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
+ return fromResource;
+ }
+ return copy(DHCP_DEFAULT_RANGE);
+ }
+
+ private static String[] copy(String[] strarray) {
+ return Arrays.copyOf(strarray, strarray.length);
+ }
+}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 11a3f11..5b539ff 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1019,8 +1019,7 @@
final int owningUid = syncAdapterInfo.uid;
final String owningPackage = syncAdapterInfo.componentName.getPackageName();
try {
- if (ActivityManager.getService().getAppStartMode(owningUid,
- owningPackage) == ActivityManager.APP_START_MODE_DISABLED) {
+ if (ActivityManager.getService().isAppStartModeDisabled(owningUid, owningPackage)) {
Slog.w(TAG, "Not scheduling job " + syncAdapterInfo.uid + ":"
+ syncAdapterInfo.componentName
+ " -- package not allowed to start");
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 8673225..841a951 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -285,6 +285,7 @@
int activeColorMode) {
List<Integer> pendingColorModes = new ArrayList<>();
+ if (colorModes == null) return false;
// Build an updated list of all existing color modes.
boolean colorModesAdded = false;
for (int colorMode: colorModes) {
diff --git a/services/core/java/com/android/server/display/NightDisplayService.java b/services/core/java/com/android/server/display/NightDisplayService.java
index 0ec48b9..0357b1b 100644
--- a/services/core/java/com/android/server/display/NightDisplayService.java
+++ b/services/core/java/com/android/server/display/NightDisplayService.java
@@ -75,6 +75,11 @@
};
/**
+ * The transition time, in milliseconds, for Night Display to turn on/off.
+ */
+ private static final long TRANSITION_DURATION = 3000L;
+
+ /**
* The identity matrix, used if one of the given matrices is {@code null}.
*/
private static final float[] MATRIX_IDENTITY = new float[16];
@@ -285,8 +290,7 @@
mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
from == null ? MATRIX_IDENTITY : from, to == null ? MATRIX_IDENTITY : to);
- mColorMatrixAnimator.setDuration(getContext().getResources()
- .getInteger(android.R.integer.config_longAnimTime));
+ mColorMatrixAnimator.setDuration(TRANSITION_DURATION);
mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
getContext(), android.R.interpolator.fast_out_slow_in));
mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index f42c5be..a748013 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -566,8 +566,8 @@
String tag) {
JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
try {
- if (ActivityManager.getService().getAppStartMode(uId,
- job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
+ if (ActivityManager.getService().isAppStartModeDisabled(uId,
+ job.getService().getPackageName())) {
Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+ " -- package not allowed to start");
return JobScheduler.RESULT_FAILURE;
@@ -1201,9 +1201,8 @@
public void process(JobStatus job) {
if (isReadyToBeExecutedLocked(job)) {
try {
- if (ActivityManager.getService().getAppStartMode(job.getUid(),
- job.getJob().getService().getPackageName())
- == ActivityManager.APP_START_MODE_DISABLED) {
+ if (ActivityManager.getService().isAppStartModeDisabled(job.getUid(),
+ job.getJob().getService().getPackageName())) {
Slog.w(TAG, "Aborting job " + job.getUid() + ":"
+ job.getJob().toString() + " -- package not allowed to start");
mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
diff --git a/services/core/java/com/android/server/notification/BadgeExtractor.java b/services/core/java/com/android/server/notification/BadgeExtractor.java
new file mode 100644
index 0000000..4795fbf
--- /dev/null
+++ b/services/core/java/com/android/server/notification/BadgeExtractor.java
@@ -0,0 +1,59 @@
+/**
+* Copyright (C) 2017 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.android.server.notification;
+
+import android.content.Context;
+import android.util.Slog;
+
+/**
+ * Determines whether a badge should be shown for this notification
+ */
+public class BadgeExtractor implements NotificationSignalExtractor {
+ private static final String TAG = "BadgeExtractor";
+ private static final boolean DBG = false;
+
+ private RankingConfig mConfig;
+
+ public void initialize(Context ctx, NotificationUsageStats usageStats) {
+ if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
+ }
+
+ public RankingReconsideration process(NotificationRecord record) {
+ if (record == null || record.getNotification() == null) {
+ if (DBG) Slog.d(TAG, "skipping empty notification");
+ return null;
+ }
+
+ if (mConfig == null) {
+ if (DBG) Slog.d(TAG, "missing config");
+ return null;
+ }
+ boolean appCanShowBadge =
+ mConfig.canShowBadge(record.sbn.getPackageName(), record.sbn.getUid());
+ if (!appCanShowBadge) {
+ record.setShowBadge(false);
+ } else {
+ record.setShowBadge(record.getChannel().canShowBadge() && appCanShowBadge);
+ }
+
+ return null;
+ }
+
+ @Override
+ public void setConfig(RankingConfig config) {
+ mConfig = config;
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6c66a60..96459be 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -198,6 +198,8 @@
static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
+ static final long SNOOZE_UNTIL_UNSPECIFIED = -1;
+
static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
@@ -229,6 +231,7 @@
private IActivityManager mAm;
private IPackageManager mPackageManager;
+ private PackageManager mPackageManagerClient;
AudioManager mAudioManager;
AudioManagerInternal mAudioManagerInternal;
@Nullable StatusBarManagerInternal mStatusBar;
@@ -268,6 +271,7 @@
private boolean mNotificationPulseEnabled;
// used as a mutex for access to all active notifications & listeners
+ final Object mNotificationLock = new Object();
final ArrayList<NotificationRecord> mNotificationList =
new ArrayList<NotificationRecord>();
final ArrayMap<String, NotificationRecord> mNotificationsByKey =
@@ -372,7 +376,7 @@
private void loadPolicyFile() {
if (DBG) Slog.d(TAG, "loadPolicyFile");
- synchronized(mPolicyFile) {
+ synchronized (mPolicyFile) {
FileInputStream infile = null;
try {
@@ -491,7 +495,7 @@
@Override
public void onSetDisabled(int status) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mDisableNotificationEffects =
(status & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
if (disableNotificationEffects(null) != null) {
@@ -519,7 +523,7 @@
@Override
public void onClearAll(int callingUid, int callingPid, int userId) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
cancelAllLocked(callingUid, callingPid, userId, REASON_DELEGATE_CANCEL_ALL, null,
/*includeCurrentProfiles*/ true);
}
@@ -527,7 +531,7 @@
@Override
public void onNotificationClick(int callingUid, int callingPid, String key) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r == null) {
Log.w(TAG, "No notification with key: " + key);
@@ -548,7 +552,7 @@
@Override
public void onNotificationActionClick(int callingUid, int callingPid, String key,
int actionIndex) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r == null) {
Log.w(TAG, "No notification with key: " + key);
@@ -584,7 +588,7 @@
@Override
public void clearEffects() {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
if (DBG) Slog.d(TAG, "clearEffects");
clearSoundLocked();
clearVibrateLocked();
@@ -601,7 +605,7 @@
REASON_DELEGATE_ERROR, null);
long ident = Binder.clearCallingIdentity();
try {
- ActivityManager.getService().crashApplication(uid, initialPid, pkg,
+ ActivityManager.getService().crashApplication(uid, initialPid, pkg, -1,
"Bad notification posted from package " + pkg
+ ": " + message);
} catch (RemoteException e) {
@@ -612,7 +616,7 @@
@Override
public void onNotificationVisibilityChanged(NotificationVisibility[] newlyVisibleKeys,
NotificationVisibility[] noLongerVisibleKeys) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
for (NotificationVisibility nv : newlyVisibleKeys) {
NotificationRecord r = mNotificationsByKey.get(nv.key);
if (r == null) continue;
@@ -635,7 +639,7 @@
@Override
public void onNotificationExpansionChanged(String key,
boolean userAction, boolean expanded) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r != null) {
r.stats.onExpansionChanged(userAction, expanded);
@@ -949,7 +953,8 @@
// TODO: Tests should call onStart instead once the methods above are removed.
@VisibleForTesting
- void init(IPackageManager packageManager, LightsManager lightsManager) {
+ void init(Looper looper, IPackageManager packageManager, PackageManager packageManagerClient,
+ LightsManager lightsManager, NotificationListeners notificationListeners) {
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
@@ -957,11 +962,12 @@
mAm = ActivityManager.getService();
mPackageManager = packageManager;
+ mPackageManagerClient = packageManagerClient;
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
- mHandler = new WorkerHandler();
+ mHandler = new WorkerHandler(looper);
mRankingThread.start();
String[] extractorNames;
try {
@@ -991,7 +997,7 @@
new Intent(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED_INTERNAL)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT),
UserHandle.ALL, android.Manifest.permission.MANAGE_NOTIFICATIONS);
- synchronized(mNotificationList) {
+ synchronized (mNotificationLock) {
updateInterruptionFilterLocked();
}
}
@@ -1019,7 +1025,7 @@
mGroupHelper = new GroupHelper(new GroupHelper.Callback() {
@Override
public void addAutoGroup(String key) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
addAutogroupKeyLocked(key);
}
mRankingHandler.requestSort(false);
@@ -1027,7 +1033,7 @@
@Override
public void removeAutoGroup(String key) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
removeAutogroupKeyLocked(key);
}
mRankingHandler.requestSort(false);
@@ -1040,7 +1046,7 @@
@Override
public void removeAutoGroupSummary(int userId, String pkg) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
clearAutogroupSummaryLocked(userId, pkg);
}
}
@@ -1051,8 +1057,8 @@
syncBlockDb();
- // This is a MangedServices object that keeps track of the listeners.
- mListeners = new NotificationListeners();
+ // This is a ManagedServices object that keeps track of the listeners.
+ mListeners = notificationListeners;
// This is a MangedServices object that keeps track of the assistant.
mNotificationAssistants = new NotificationAssistants();
@@ -1134,7 +1140,8 @@
@Override
public void onStart() {
- init(AppGlobals.getPackageManager(), getLocalService(LightsManager.class));
+ init(Looper.myLooper(), AppGlobals.getPackageManager(), getContext().getPackageManager(),
+ getLocalService(LightsManager.class), new NotificationListeners());
publishBinderService(Context.NOTIFICATION_SERVICE, mService);
publishLocalService(NotificationManagerInternal.class, mInternalService);
}
@@ -1236,6 +1243,35 @@
sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
}
+ private void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel,
+ boolean fromAssistant) {
+ if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
+ // cancel
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
+ UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED,
+ null);
+ }
+ if (fromAssistant) {
+ mRankingHelper.updateNotificationChannelFromAssistant(pkg, uid, channel);
+ } else {
+ mRankingHelper.updateNotificationChannel(pkg, uid, channel);
+ }
+
+ synchronized (mNotificationList) {
+ final int N = mNotificationList.size();
+ for (int i = N - 1; i >= 0; --i) {
+ NotificationRecord r = mNotificationList.get(i);
+ if (channel.getId() != null && channel.getId().equals(r.getChannel().getId())) {
+ r.updateNotificationChannel(mRankingHelper.getNotificationChannel(
+ r.sbn.getPackageName(), r.getUser().getIdentifier(),
+ channel.getId(), false));
+ }
+ }
+ }
+ mRankingHandler.requestSort(true);
+ savePolicyFile();
+ }
+
private ArrayList<ComponentName> getSuppressors() {
ArrayList<ComponentName> names = new ArrayList<ComponentName>();
for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) {
@@ -1514,6 +1550,19 @@
}
@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);
@@ -1559,15 +1608,8 @@
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
@@ -1627,7 +1669,7 @@
// noteOp will check to make sure the callingPkg matches the uid
if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
== AppOpsManager.MODE_ALLOWED) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
tmp = new StatusBarNotification[mNotificationList.size()];
final int N = mNotificationList.size();
for (int i=0; i<N; i++) {
@@ -1653,7 +1695,7 @@
final ArrayMap<String, StatusBarNotification> map
= new ArrayMap<>(mNotificationList.size() + mEnqueuedNotifications.size());
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final int N = mNotificationList.size();
for (int i = 0; i < N; i++) {
StatusBarNotification sbn = sanitizeSbn(pkg, userId,
@@ -1668,10 +1710,8 @@
map.put(sbn.getKey(), sbn);
}
}
- }
- synchronized (mEnqueuedNotifications) {
- final int N = mEnqueuedNotifications.size();
- for (int i = 0; i < N; i++) {
+ final int M = mEnqueuedNotifications.size();
+ for (int i = 0; i < M; i++) {
StatusBarNotification sbn = sanitizeSbn(pkg, userId,
mEnqueuedNotifications.get(i).sbn);
if (sbn != null) {
@@ -1696,7 +1736,6 @@
return new StatusBarNotification(
sbn.getPackageName(),
sbn.getOpPkg(),
- sbn.getNotificationChannel(),
sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
sbn.getNotification().clone(),
sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
@@ -1763,7 +1802,7 @@
final int callingPid = Binder.getCallingPid();
long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
if (keys != null) {
final int N = keys.length;
@@ -1826,7 +1865,7 @@
public void setNotificationsShownFromListener(INotificationListener token, String[] keys) {
long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
if (keys != null) {
final int N = keys.length;
@@ -1881,7 +1920,7 @@
long identity = Binder.clearCallingIdentity();
try {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- snoozeNotificationInt(key, snoozeCriterionId, info);
+ snoozeNotificationInt(key, SNOOZE_UNTIL_UNSPECIFIED, snoozeCriterionId, info);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1898,7 +1937,7 @@
long identity = Binder.clearCallingIdentity();
try {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- snoozeNotificationInt(key, snoozeUntil, info);
+ snoozeNotificationInt(key, snoozeUntil, null, info);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1914,7 +1953,7 @@
long identity = Binder.clearCallingIdentity();
try {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- snoozeNotificationInt(key, info);
+ snoozeNotificationInt(key, SNOOZE_UNTIL_UNSPECIFIED, null, info);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1950,7 +1989,7 @@
final int callingPid = Binder.getCallingPid();
long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
if (info.supportsProfiles()) {
Log.e(TAG, "Ignoring deprecated cancelNotification(pkg, tag, id) "
@@ -1979,7 +2018,7 @@
@Override
public ParceledListSlice<StatusBarNotification> getActiveNotificationsFromListener(
INotificationListener token, String[] keys, int trim) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
final boolean getKeys = keys != null;
final int N = getKeys ? keys.length : mNotificationList.size();
@@ -2004,7 +2043,7 @@
public void requestHintsFromListener(INotificationListener token, int hints) {
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
final int disableEffectsMask = HINT_HOST_DISABLE_EFFECTS
| HINT_HOST_DISABLE_NOTIFICATION_EFFECTS
@@ -2025,7 +2064,7 @@
@Override
public int getHintsFromListener(INotificationListener token) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
return mListenerHints;
}
}
@@ -2035,7 +2074,7 @@
int interruptionFilter) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
mZenModeHelper.requestFromListener(info.component, interruptionFilter);
updateInterruptionFilterLocked();
@@ -2056,7 +2095,7 @@
@Override
public void setOnNotificationPostedTrimFromListener(INotificationListener token, int trim)
throws RemoteException {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
if (info == null) return;
mListeners.setOnNotificationPostedTrimLocked(info, trim);
@@ -2375,7 +2414,7 @@
enforceSystemOrSystemUI("grant notification policy access");
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mPolicyAccess.put(pkg, granted);
}
} finally {
@@ -2410,7 +2449,7 @@
Adjustment adjustment) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mEnqueuedNotifications) {
+ synchronized (mNotificationLock) {
mNotificationAssistants.checkServiceTokenLocked(token);
int N = mEnqueuedNotifications.size();
for (int i = 0; i < N; i++) {
@@ -2432,7 +2471,7 @@
Adjustment adjustment) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mNotificationAssistants.checkServiceTokenLocked(token);
NotificationRecord n = mNotificationsByKey.get(adjustment.getKey());
applyAdjustment(n, adjustment);
@@ -2449,7 +2488,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mNotificationAssistants.checkServiceTokenLocked(token);
for (Adjustment adjustment : adjustments) {
NotificationRecord n = mNotificationsByKey.get(adjustment.getKey());
@@ -2490,15 +2529,9 @@
public void updateNotificationChannelFromAssistant(INotificationListener token, String pkg,
NotificationChannel channel) throws RemoteException {
ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token);
- if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
- // cancel
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
- info.userid, REASON_CHANNEL_BANNED, null);
- }
+ Preconditions.checkNotNull(channel);
int uid = mPackageManager.getPackageUid(pkg, 0, info.userid);
- mRankingHelper.updateNotificationChannelFromAssistant(pkg, uid, channel);
- mRankingHandler.requestSort(true);
- savePolicyFile();
+ updateNotificationChannelInt(pkg, uid, channel, true);
}
@Override
@@ -2523,7 +2556,7 @@
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 */));
}
@@ -2555,9 +2588,8 @@
ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
if (summaries != null && summaries.containsKey(pkg)) {
// Clear summary.
- final NotificationRecord removed = mNotificationsByKey.get(summaries.remove(pkg));
+ final NotificationRecord removed = findNotificationByKeyLocked(summaries.remove(pkg));
if (removed != null) {
- mNotificationList.remove(removed);
cancelNotificationLocked(removed, false, REASON_UNAUTOBUNDLED);
}
}
@@ -2566,7 +2598,7 @@
// Posts a 'fake' summary for a package that has exceeded the solo-notification limit.
private void createAutoGroupSummary(int userId, String pkg, String triggeringKey) {
NotificationRecord summaryRecord = null;
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
NotificationRecord notificationRecord = mNotificationsByKey.get(triggeringKey);
if (notificationRecord == null) {
// The notification could have been cancelled again already. A successive
@@ -2606,18 +2638,18 @@
final StatusBarNotification summarySbn =
new StatusBarNotification(adjustedSbn.getPackageName(),
adjustedSbn.getOpPkg(),
- adjustedSbn.getNotificationChannel(),
Integer.MAX_VALUE,
GroupHelper.AUTOGROUP_KEY, adjustedSbn.getUid(),
adjustedSbn.getInitialPid(), summaryNotification,
adjustedSbn.getUser(), GroupHelper.AUTOGROUP_KEY,
System.currentTimeMillis());
- summaryRecord = new NotificationRecord(getContext(), summarySbn);
+ summaryRecord = new NotificationRecord(getContext(), summarySbn,
+ notificationRecord.getChannel());
summaries.put(pkg, summarySbn.getKey());
}
}
if (summaryRecord != null) {
- synchronized (mEnqueuedNotifications) {
+ synchronized (mNotificationLock) {
mEnqueuedNotifications.add(summaryRecord);
}
mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord));
@@ -2672,7 +2704,7 @@
}
}
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
if (!zenOnly) {
N = mNotificationList.size();
if (N > 0) {
@@ -2710,19 +2742,17 @@
}
pw.println(" mArchive=" + mArchive.toString());
Iterator<StatusBarNotification> iter = mArchive.descendingIterator();
- int i=0;
+ int j=0;
while (iter.hasNext()) {
final StatusBarNotification sbn = iter.next();
if (filter != null && !filter.matches(sbn)) continue;
pw.println(" " + sbn);
- if (++i >= 5) {
+ if (++j >= 5) {
if (iter.hasNext()) pw.println(" ...");
break;
}
}
- }
- synchronized (mEnqueuedNotifications) {
if (!zenOnly) {
N = mEnqueuedNotifications.size();
if (N > 0) {
@@ -2817,14 +2847,14 @@
public void removeForegroundServiceFlagFromNotification(String pkg, int notificationId,
int userId) {
checkCallerIsSystem();
- synchronized (mNotificationList) {
- int i = indexOfNotificationLocked(pkg, null, notificationId, userId);
- if (i < 0) {
+ synchronized (mNotificationLock) {
+ NotificationRecord r = findNotificationByListLocked(mNotificationList, pkg, null,
+ notificationId, userId);
+ if (r == null) {
Log.d(TAG, "stripForegroundServiceFlag: Could not find notification with "
+ "pkg=" + pkg + " / id=" + notificationId + " / userId=" + userId);
return;
}
- NotificationRecord r = mNotificationList.get(i);
StatusBarNotification sbn = r.sbn;
// NoMan adds flags FLAG_NO_CLEAR and FLAG_ONGOING_EVENT when it sees
// FLAG_FOREGROUND_SERVICE. Hence it's not enough to remove FLAG_FOREGROUND_SERVICE,
@@ -2861,7 +2891,7 @@
// Fix the notification as best we can.
try {
- final ApplicationInfo ai = getContext().getPackageManager().getApplicationInfoAsUser(
+ final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
(userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
Notification.addFieldsFromContext(ai, userId, notification);
@@ -2880,14 +2910,14 @@
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
// package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks.
if (!isSystemNotification && !isNotificationFromListener) {
- synchronized (mNotificationList) {
- if(mNotificationsByKey.get(n.getKey()) != null) {
+ synchronized (mNotificationLock) {
+ if (mNotificationsByKey.get(n.getKey()) != null) {
// this is an update, rate limit updates only
final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
if (appEnqueueRate > mMaxPackageEnqueueRate) {
@@ -2944,8 +2974,8 @@
Notification.PRIORITY_MAX);
// setup local book-keeping
- final NotificationRecord r = new NotificationRecord(getContext(), n);
- synchronized (mEnqueuedNotifications) {
+ final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
+ synchronized (mNotificationLock) {
mEnqueuedNotifications.add(r);
}
mHandler.post(new EnqueueNotificationRunnable(userId, r));
@@ -2964,7 +2994,7 @@
@Override
public void run() {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
if (mSnoozeHelper.isSnoozed(userId, r.sbn.getPackageName(), r.getKey())) {
// TODO: log to event log
if (DBG) {
@@ -3059,9 +3089,9 @@
@Override
public void run() {
- try {
- NotificationRecord r = null;
- synchronized (mEnqueuedNotifications) {
+ synchronized (mNotificationLock) {
+ try {
+ NotificationRecord r = null;
int N = mEnqueuedNotifications.size();
for (int i = 0; i < N; i++) {
final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
@@ -3070,12 +3100,10 @@
break;
}
}
- }
- if (r == null) {
- Slog.e(TAG, "Cannot find enqueued record for key: " + key);
- return;
- }
- synchronized (mNotificationList) {
+ if (r == null) {
+ Slog.i(TAG, "Cannot find enqueued record for key: " + key);
+ return;
+ }
NotificationRecord old = mNotificationsByKey.get(key);
final StatusBarNotification n = r.sbn;
final Notification notification = n.getNotification();
@@ -3134,9 +3162,7 @@
}
buzzBeepBlinkLocked(r);
- }
- } finally {
- synchronized (mEnqueuedNotifications) {
+ } finally {
int N = mEnqueuedNotifications.size();
for (int i = 0; i < N; i++) {
final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
@@ -3193,8 +3219,7 @@
// notification was a summary and the new one isn't, or when the old
// notification was a summary and its group key changed.
if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) {
- cancelGroupChildrenLocked(old, callingUid, callingPid, null,
- REASON_GROUP_SUMMARY_CANCELED, false /* sendDelete */);
+ cancelGroupChildrenLocked(old, callingUid, callingPid, null, false /* sendDelete */);
}
}
@@ -3463,7 +3488,7 @@
RankingReconsideration recon = (RankingReconsideration) message.obj;
recon.run();
boolean changed;
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final NotificationRecord record = mNotificationsByKey.get(recon.getKey());
if (record == null) {
return;
@@ -3491,16 +3516,20 @@
private void handleRankingSort(Message msg) {
if (!(msg.obj instanceof Boolean)) return;
boolean forceUpdate = ((Boolean) msg.obj == null) ? false : (boolean) msg.obj;
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
final int N = mNotificationList.size();
+ // Any field that can change via one of the extractors or by the assistant
+ // needs to be added here.
ArrayList<String> orderBefore = new ArrayList<String>(N);
ArrayList<String> groupOverrideBefore = new ArrayList<>(N);
int[] visibilities = new int[N];
+ boolean[] showBadges = new boolean[N];
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
orderBefore.add(r.getKey());
groupOverrideBefore.add(r.sbn.getGroupKey());
visibilities[i] = r.getPackageVisibilityOverride();
+ showBadges[i] = r.canShowBadge();
mRankingHelper.extractSignals(r);
}
mRankingHelper.sort(mNotificationList);
@@ -3509,7 +3538,8 @@
if (forceUpdate
|| !orderBefore.get(i).equals(r.getKey())
|| visibilities[i] != r.getPackageVisibilityOverride()
- || !groupOverrideBefore.get(i).equals(r.sbn.getGroupKey())) {
+ || !groupOverrideBefore.get(i).equals(r.sbn.getGroupKey())
+ || showBadges[i] != r.canShowBadge()) {
scheduleSendRankingUpdate();
return;
}
@@ -3548,7 +3578,7 @@
}
private void handleSendRankingUpdate() {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mListeners.notifyRankingUpdateLocked();
}
}
@@ -3567,19 +3597,23 @@
}
private void handleListenerHintsChanged(int hints) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mListeners.notifyListenerHintsChangedLocked(hints);
}
}
private void handleListenerInterruptionFilterChanged(int interruptionFilter) {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
mListeners.notifyInterruptionFilterChanged(interruptionFilter);
}
}
private final class WorkerHandler extends Handler
{
+ public WorkerHandler(Looper looper) {
+ super(looper);
+ }
+
@Override
public void handleMessage(Message msg)
{
@@ -3665,6 +3699,17 @@
}
private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason) {
+ final String canceledKey = r.getKey();
+
+ // Remove from either list
+ boolean wasPosted;
+ if (mNotificationList.remove(r)) {
+ mNotificationsByKey.remove(r.sbn.getKey());
+ wasPosted = true;
+ } else {
+ mEnqueuedNotifications.remove(r);
+ wasPosted = false;
+ }
// Record caller.
recordCallerLocked(r);
@@ -3682,50 +3727,51 @@
}
}
- // status bar
- if (r.getNotification().getSmallIcon() != null) {
- r.isCanceled = true;
- mListeners.notifyRemovedLocked(r.sbn, reason);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mGroupHelper.onNotificationRemoved(r.sbn);
+ // Only cancel these if this notification actually got to be posted.
+ if (wasPosted) {
+ // status bar
+ if (r.getNotification().getSmallIcon() != null) {
+ r.isCanceled = true;
+ mListeners.notifyRemovedLocked(r.sbn, reason);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mGroupHelper.onNotificationRemoved(r.sbn);
+ }
+ });
+ }
+
+ // sound
+ if (canceledKey.equals(mSoundNotificationKey)) {
+ mSoundNotificationKey = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
+ if (player != null) {
+ player.stopAsync();
+ }
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
- });
- }
+ }
- final String canceledKey = r.getKey();
-
- // sound
- if (canceledKey.equals(mSoundNotificationKey)) {
- mSoundNotificationKey = null;
- final long identity = Binder.clearCallingIdentity();
- try {
- final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
- if (player != null) {
- player.stopAsync();
+ // vibrate
+ if (canceledKey.equals(mVibrateNotificationKey)) {
+ mVibrateNotificationKey = null;
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mVibrator.cancel();
}
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(identity);
+ finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
- }
- // vibrate
- if (canceledKey.equals(mVibrateNotificationKey)) {
- mVibrateNotificationKey = null;
- long identity = Binder.clearCallingIdentity();
- try {
- mVibrator.cancel();
- }
- finally {
- Binder.restoreCallingIdentity(identity);
- }
+ // light
+ mLights.remove(canceledKey);
}
- // light
- mLights.remove(canceledKey);
-
// Record usage stats
// TODO: add unbundling stats?
switch (reason) {
@@ -3741,10 +3787,9 @@
break;
}
- mNotificationsByKey.remove(r.sbn.getKey());
String groupKey = r.getGroupKey();
NotificationRecord groupSummary = mSummaryByGroupKey.get(groupKey);
- if (groupSummary != null && groupSummary.getKey().equals(r.getKey())) {
+ if (groupSummary != null && groupSummary.getKey().equals(canceledKey)) {
mSummaryByGroupKey.remove(groupKey);
}
final ArrayMap<String, String> summaries = mAutobundledSummaries.get(r.sbn.getUserId());
@@ -3779,10 +3824,11 @@
if (DBG) EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, id, tag,
userId, mustHaveFlags, mustNotHaveFlags, reason, listenerName);
- synchronized (mNotificationList) {
- int index = indexOfNotificationLocked(pkg, tag, id, userId);
- if (index >= 0) {
- NotificationRecord r = mNotificationList.get(index);
+ synchronized (mNotificationLock) {
+ // Look for the notification, searching both the posted and enqueued lists.
+ NotificationRecord r = findNotificationLocked(pkg, tag, id, userId);
+ if (r != null) {
+ // The notification was found, check if it should be removed.
// Ideally we'd do this in the caller of this method. However, that would
// require the caller to also find the notification.
@@ -3797,13 +3843,13 @@
return;
}
- mNotificationList.remove(index);
-
+ // Cancel the notification.
cancelNotificationLocked(r, sendDelete, reason);
cancelGroupChildrenLocked(r, callingUid, callingPid, listenerName,
- REASON_GROUP_SUMMARY_CANCELED, sendDelete);
+ sendDelete);
updateLightsLocked();
} else {
+ // No notification was found, assume that it is snoozed and cancel it.
final boolean wasSnoozed = mSnoozeHelper.cancel(userId, pkg, tag, id);
if (wasSnoozed) {
savePolicyFile();
@@ -3842,120 +3888,131 @@
* Cancels all notifications from a given package that have all of the
* {@code mustHaveFlags}.
*/
- boolean cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, String channelId,
+ void cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, String channelId,
int mustHaveFlags, int mustNotHaveFlags, boolean doit, int userId, int reason,
ManagedServiceInfo listener) {
- String listenerName = listener == null ? null : listener.component.toShortString();
- EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
- pkg, userId, mustHaveFlags, mustNotHaveFlags, reason,
- listenerName);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ String listenerName = listener == null ? null : listener.component.toShortString();
+ EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
+ pkg, userId, mustHaveFlags, mustNotHaveFlags, reason,
+ listenerName);
- synchronized (mNotificationList) {
- final int N = mNotificationList.size();
- ArrayList<NotificationRecord> canceledNotifications = null;
- for (int i = N-1; i >= 0; --i) {
- NotificationRecord r = mNotificationList.get(i);
- if (!notificationMatchesUserId(r, userId)) {
- continue;
- }
- // Don't remove notifications to all, if there's no package name specified
- if (r.getUserId() == UserHandle.USER_ALL && pkg == null) {
- continue;
- }
- if ((r.getFlags() & mustHaveFlags) != mustHaveFlags) {
- continue;
- }
- if ((r.getFlags() & mustNotHaveFlags) != 0) {
- continue;
- }
- if (pkg != null && !r.sbn.getPackageName().equals(pkg)) {
- continue;
- }
- if (channelId != null && !channelId.equals(r.getChannel().getId())) {
- continue;
- }
- if (canceledNotifications == null) {
- canceledNotifications = new ArrayList<>();
- }
- canceledNotifications.add(r);
+ // Why does this parameter exist? Do we actually want to execute the above if doit
+ // is false?
if (!doit) {
- return true;
+ return;
}
- mNotificationList.remove(i);
- cancelNotificationLocked(r, false, reason);
- }
- mSnoozeHelper.cancel(userId, pkg);
- if (doit && canceledNotifications != null) {
- final int M = canceledNotifications.size();
- for (int i = 0; i < M; i++) {
- cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
- listenerName, REASON_GROUP_SUMMARY_CANCELED, false /* sendDelete */);
+
+ synchronized (mNotificationLock) {
+ FlagChecker flagChecker = (int flags) -> {
+ if ((flags & mustHaveFlags) != mustHaveFlags) {
+ return false;
+ }
+ if ((flags & mustNotHaveFlags) != 0) {
+ return false;
+ }
+ return true;
+ };
+
+ cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
+ pkg, true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
+ false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
+ listenerName);
+ cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
+ callingPid, pkg, true /*nullPkgIndicatesUserSwitch*/, channelId,
+ flagChecker, false /*includeCurrentProfiles*/, userId,
+ false /*sendDelete*/, reason, listenerName);
+ mSnoozeHelper.cancel(userId, pkg);
}
}
- if (canceledNotifications != null) {
- updateLightsLocked();
+ });
+ }
+
+ private interface FlagChecker {
+ // Returns false if these flags do not pass the defined flag test.
+ public boolean apply(int flags);
+ }
+
+ private void cancelAllNotificationsByListLocked(ArrayList<NotificationRecord> notificationList,
+ int callingUid, int callingPid, String pkg, boolean nullPkgIndicatesUserSwitch,
+ String channelId, FlagChecker flagChecker, boolean includeCurrentProfiles, int userId,
+ boolean sendDelete, int reason, String listenerName) {
+ ArrayList<NotificationRecord> canceledNotifications = null;
+ for (int i = notificationList.size() - 1; i >= 0; --i) {
+ NotificationRecord r = notificationList.get(i);
+ if (includeCurrentProfiles) {
+ if (!notificationMatchesCurrentProfiles(r, userId)) {
+ continue;
+ }
+ } else if (!notificationMatchesUserId(r, userId)) {
+ continue;
}
- return canceledNotifications != null;
+ // Don't remove notifications to all, if there's no package name specified
+ if (nullPkgIndicatesUserSwitch && pkg == null && r.getUserId() == UserHandle.USER_ALL) {
+ continue;
+ }
+ if (!flagChecker.apply(r.getFlags())) {
+ continue;
+ }
+ if (pkg != null && !r.sbn.getPackageName().equals(pkg)) {
+ continue;
+ }
+ if (channelId != null && !channelId.equals(r.getChannel().getId())) {
+ continue;
+ }
+
+ if (canceledNotifications == null) {
+ canceledNotifications = new ArrayList<>();
+ }
+ canceledNotifications.add(r);
+ cancelNotificationLocked(r, sendDelete, reason);
+ }
+ if (canceledNotifications != null) {
+ final int M = canceledNotifications.size();
+ for (int i = 0; i < M; i++) {
+ cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
+ listenerName, false /* sendDelete */);
+ }
+ updateLightsLocked();
}
}
- void snoozeNotificationInt(String key, String snoozeCriterionId, ManagedServiceInfo listener) {
+ void snoozeNotificationInt(String key, long until, String snoozeCriterionId,
+ ManagedServiceInfo listener) {
String listenerName = listener == null ? null : listener.component.toShortString();
// TODO: write to event log
if (DBG) {
- Slog.d(TAG, String.format("snooze event(%s, %s, %s)",
- key, snoozeCriterionId, listenerName));
+ Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, until, snoozeCriterionId,
+ listenerName));
}
- synchronized (mNotificationList) {
- final NotificationRecord r = mNotificationsByKey.get(key);
- if (r != null) {
- mNotificationList.remove(r);
- cancelNotificationLocked(r, false, REASON_SNOOZED);
- mNotificationAssistants.notifyAssistantSnoozedLocked(r.sbn, snoozeCriterionId);
- updateLightsLocked();
- mSnoozeHelper.snooze(r);
- savePolicyFile();
- }
- }
- }
-
- void snoozeNotificationInt(String key, long until, ManagedServiceInfo listener) {
- String listenerName = listener == null ? null : listener.component.toShortString();
- // TODO: write to event log
- if (DBG) {
- Slog.d(TAG, String.format("snooze event(%s, %d, %s)", key, until, listenerName));
- }
- if (until < System.currentTimeMillis()) {
+ if (until != SNOOZE_UNTIL_UNSPECIFIED && until < System.currentTimeMillis()) {
return;
}
- synchronized (mNotificationList) {
- final NotificationRecord r = mNotificationsByKey.get(key);
- if (r != null) {
- mNotificationList.remove(r);
- cancelNotificationLocked(r, false, REASON_SNOOZED);
- updateLightsLocked();
- mSnoozeHelper.snooze(r, until);
- savePolicyFile();
+ // Needs to post so that it can cancel notifications not yet enqueued.
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mNotificationLock) {
+ final NotificationRecord r = findNotificationByKeyLocked(key);
+ if (r != null) {
+ cancelNotificationLocked(r, false, REASON_SNOOZED);
+ updateLightsLocked();
+ if (snoozeCriterionId != null) {
+ mNotificationAssistants.notifyAssistantSnoozedLocked(r.sbn,
+ snoozeCriterionId);
+ }
+ if (until == SNOOZE_UNTIL_UNSPECIFIED) {
+ mSnoozeHelper.snooze(r);
+ } else {
+ mSnoozeHelper.snooze(r, until);
+ }
+ savePolicyFile();
+ }
+ }
}
- }
- }
-
- void snoozeNotificationInt(String key, ManagedServiceInfo listener) {
- String listenerName = listener == null ? null : listener.component.toShortString();
- // TODO: write to event log
- if (DBG) {
- Slog.d(TAG, String.format("snooze event(%s, %s)", key, listenerName));
- }
- synchronized (mNotificationList) {
- final NotificationRecord r = mNotificationsByKey.get(key);
- if (r != null) {
- mNotificationList.remove(r);
- cancelNotificationLocked(r, false, REASON_SNOOZED);
- updateLightsLocked();
- mSnoozeHelper.snooze(r);
- savePolicyFile();
- }
- }
+ });
}
void unsnoozeNotificationInt(String key, ManagedServiceInfo listener) {
@@ -3970,47 +4027,40 @@
void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
ManagedServiceInfo listener, boolean includeCurrentProfiles) {
- String listenerName = listener == null ? null : listener.component.toShortString();
- EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
- null, userId, 0, 0, reason, listenerName);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mNotificationLock) {
+ String listenerName =
+ listener == null ? null : listener.component.toShortString();
+ EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
+ null, userId, 0, 0, reason, listenerName);
- ArrayList<NotificationRecord> canceledNotifications = null;
- final int N = mNotificationList.size();
- for (int i=N-1; i>=0; i--) {
- NotificationRecord r = mNotificationList.get(i);
- if (includeCurrentProfiles) {
- if (!notificationMatchesCurrentProfiles(r, userId)) {
- continue;
- }
- } else {
- if (!notificationMatchesUserId(r, userId)) {
- continue;
+ FlagChecker flagChecker = (int flags) -> {
+ if ((flags & (Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR))
+ != 0) {
+ return false;
+ }
+ return true;
+ };
+
+ cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
+ null, false /*nullPkgIndicatesUserSwitch*/, null, flagChecker,
+ includeCurrentProfiles, userId, true /*sendDelete*/, reason,
+ listenerName);
+ cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
+ callingPid, null, false /*nullPkgIndicatesUserSwitch*/, null,
+ flagChecker, includeCurrentProfiles, userId, true /*sendDelete*/,
+ reason, listenerName);
+ mSnoozeHelper.cancel(userId, includeCurrentProfiles);
}
}
-
- if ((r.getFlags() & (Notification.FLAG_ONGOING_EVENT
- | Notification.FLAG_NO_CLEAR)) == 0) {
- mNotificationList.remove(i);
- cancelNotificationLocked(r, true, reason);
- // Make a note so we can cancel children later.
- if (canceledNotifications == null) {
- canceledNotifications = new ArrayList<>();
- }
- canceledNotifications.add(r);
- }
- }
- mSnoozeHelper.cancel(userId, includeCurrentProfiles);
- int M = canceledNotifications != null ? canceledNotifications.size() : 0;
- for (int i = 0; i < M; i++) {
- cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
- listenerName, REASON_GROUP_SUMMARY_CANCELED, false /* sendDelete */);
- }
- updateLightsLocked();
+ });
}
// Warning: The caller is responsible for invoking updateLightsLocked().
private void cancelGroupChildrenLocked(NotificationRecord r, int callingUid, int callingPid,
- String listenerName, int reason, boolean sendDelete) {
+ String listenerName, boolean sendDelete) {
Notification n = r.getNotification();
if (!n.isGroupSummary()) {
return;
@@ -4024,16 +4074,26 @@
return;
}
- final int N = mNotificationList.size();
- for (int i = N - 1; i >= 0; i--) {
- NotificationRecord childR = mNotificationList.get(i);
- StatusBarNotification childSbn = childR.sbn;
+ cancelGroupChildrenByListLocked(mNotificationList, r, callingUid, callingPid, listenerName,
+ sendDelete);
+ cancelGroupChildrenByListLocked(mEnqueuedNotifications, r, callingUid, callingPid,
+ listenerName, sendDelete);
+ }
+
+ private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList,
+ NotificationRecord parentNotification, int callingUid, int callingPid,
+ String listenerName, boolean sendDelete) {
+ final String pkg = parentNotification.sbn.getPackageName();
+ final int userId = parentNotification.getUserId();
+ final int reason = REASON_GROUP_SUMMARY_CANCELED;
+ for (int i = notificationList.size() - 1; i >= 0; i--) {
+ final NotificationRecord childR = notificationList.get(i);
+ final StatusBarNotification childSbn = childR.sbn;
if ((childSbn.isGroup() && !childSbn.getNotification().isGroupSummary()) &&
- childR.getGroupKey().equals(r.getGroupKey())
+ childR.getGroupKey().equals(parentNotification.getGroupKey())
&& (childR.getFlags() & Notification.FLAG_FOREGROUND_SERVICE) == 0) {
EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
childSbn.getTag(), userId, 0, 0, reason, listenerName);
- mNotificationList.remove(i);
cancelNotificationLocked(childR, sendDelete, reason);
}
}
@@ -4082,19 +4142,49 @@
}
}
- // lock on mNotificationList
- int indexOfNotificationLocked(String pkg, String tag, int id, int userId)
+ // Searches both enqueued and posted notifications by key.
+ // TODO: need to combine a bunch of these getters with slightly different behavior.
+ // TODO: Should enqueuing just add to mNotificationsByKey instead?
+ private NotificationRecord findNotificationByKeyLocked(String key) {
+ final int N = mNotificationList.size();
+ for (int i = 0; i < N; i++) {
+ if (key.equals(mNotificationList.get(i).getKey())) {
+ return mNotificationList.get(i);
+ }
+ }
+ final int M = mEnqueuedNotifications.size();
+ for (int i = 0; i < M; i++) {
+ if (key.equals(mEnqueuedNotifications.get(i).getKey())) {
+ return mEnqueuedNotifications.get(i);
+ }
+ }
+ return null;
+ }
+
+ private NotificationRecord findNotificationLocked(String pkg, String tag, int id, int userId) {
+ NotificationRecord r;
+ if ((r = findNotificationByListLocked(mNotificationList, pkg, tag, id, userId)) != null) {
+ return r;
+ }
+ if ((r = findNotificationByListLocked(mEnqueuedNotifications, pkg, tag, id, userId))
+ != null) {
+ return r;
+ }
+ return null;
+ }
+
+ private NotificationRecord findNotificationByListLocked(ArrayList<NotificationRecord> list,
+ String pkg, String tag, int id, int userId)
{
- ArrayList<NotificationRecord> list = mNotificationList;
final int len = list.size();
- for (int i=0; i<len; i++) {
+ for (int i = 0; i < len; i++) {
NotificationRecord r = list.get(i);
if (notificationMatchesUserId(r, userId) && r.sbn.getId() == id &&
TextUtils.equals(r.sbn.getTag(), tag) && r.sbn.getPackageName().equals(pkg)) {
- return i;
+ return r;
}
}
- return -1;
+ return null;
}
// lock on mNotificationList
@@ -4109,7 +4199,7 @@
}
private void updateNotificationPulse() {
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
updateLightsLocked();
}
}
@@ -4189,9 +4279,10 @@
Bundle visibilityOverrides = new Bundle();
Bundle suppressedVisualEffects = new Bundle();
Bundle explanation = new Bundle();
- Bundle overrideChannels = new Bundle();
+ Bundle channels = new Bundle();
Bundle overridePeople = new Bundle();
Bundle snoozeCriteria = new Bundle();
+ Bundle showBadge = new Bundle();
for (int i = 0; i < N; i++) {
NotificationRecord record = mNotificationList.get(i);
if (!isVisibleToListener(record.sbn, info)) {
@@ -4213,9 +4304,10 @@
visibilityOverrides.putInt(key, record.getPackageVisibilityOverride());
}
overrideGroupKeys.putString(key, record.sbn.getOverrideGroupKey());
- overrideChannels.putParcelable(key, record.getChannel());
+ channels.putParcelable(key, record.getChannel());
overridePeople.putStringArrayList(key, record.getPeopleOverride());
snoozeCriteria.putParcelableArrayList(key, record.getSnoozeCriteria());
+ showBadge.putBoolean(key, record.canShowBadge());
}
final int M = keys.size();
String[] keysAr = keys.toArray(new String[M]);
@@ -4226,7 +4318,7 @@
}
return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
- overrideChannels, overridePeople, snoozeCriteria);
+ channels, overridePeople, snoozeCriteria, showBadge);
}
private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
@@ -4413,7 +4505,7 @@
public void onServiceAdded(ManagedServiceInfo info) {
final INotificationListener listener = (INotificationListener) info.service;
final NotificationRankingUpdate update;
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
update = makeRankingUpdateLocked(info);
}
try {
@@ -4608,12 +4700,12 @@
}
}
- private boolean isListenerPackage(String packageName) {
+ public boolean isListenerPackage(String packageName) {
if (packageName == null) {
return false;
}
// TODO: clean up locking object later
- synchronized (mNotificationList) {
+ synchronized (mNotificationLock) {
for (final ManagedServiceInfo serviceInfo : mServices) {
if (packageName.equals(serviceInfo.component.getPackageName())) {
return true;
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index e8c3d97..2a5a25f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -25,7 +25,6 @@
import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -114,12 +113,14 @@
private Uri mSound;
private long[] mVibration;
private AudioAttributes mAttributes;
- private NotificationChannel mOverrideChannel;
+ private NotificationChannel mChannel;
private ArrayList<String> mPeopleOverride;
private ArrayList<SnoozeCriterion> mSnoozeCriteria;
+ private boolean mShowBadge;
@VisibleForTesting
- public NotificationRecord(Context context, StatusBarNotification sbn)
+ public NotificationRecord(Context context, StatusBarNotification sbn,
+ NotificationChannel channel)
{
this.sbn = sbn;
mOriginalFlags = sbn.getNotification().flags;
@@ -128,6 +129,7 @@
mUpdateTimeMs = mCreationTimeMs;
mContext = context;
stats = new NotificationUsageStats.SingleNotificationStats();
+ mChannel = channel;
mPreChannelsNotification = isPreChannelsNotification();
mSound = calculateSound();
mVibration = calculateVibration();
@@ -154,7 +156,7 @@
private Uri calculateSound() {
final Notification n = sbn.getNotification();
- Uri sound = sbn.getNotificationChannel().getSound();
+ Uri sound = mChannel.getSound();
if (mPreChannelsNotification && (getChannel().getUserLockedFields()
& NotificationChannel.USER_LOCKED_SOUND) == 0) {
@@ -386,7 +388,8 @@
pw.println(prefix + " mSound= " + mSound);
pw.println(prefix + " mVibration= " + mVibration);
pw.println(prefix + " mAttributes= " + mAttributes);
- pw.println(prefix + " overrideChannel=" + getChannel());
+ pw.println(prefix + " mShowBadge=" + mShowBadge);
+ pw.println(prefix + " channel=" + getChannel());
if (getPeopleOverride() != null) {
pw.println(prefix + " overridePeople= " + TextUtils.join(",", getPeopleOverride()));
}
@@ -640,16 +643,24 @@
}
public NotificationChannel getChannel() {
- return mOverrideChannel == null ? sbn.getNotificationChannel() : mOverrideChannel;
+ return mChannel;
}
- protected void setNotificationChannelOverride(NotificationChannel channel) {
- mOverrideChannel = channel;
- if (mOverrideChannel != null) {
+ protected void updateNotificationChannel(NotificationChannel channel) {
+ if (channel != null) {
+ mChannel = channel;
calculateImportance();
}
}
+ public void setShowBadge(boolean showBadge) {
+ mShowBadge = showBadge;
+ }
+
+ public boolean canShowBadge() {
+ return mShowBadge;
+ }
+
public Uri getSound() {
return mSound;
}
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index c2cef09..492d5c6 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 @@
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 e44fb7f..1861bcb 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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
}
@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 @@
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 @@
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 @@
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 @@
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 @@
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 d735e72..96a0d18 100644
--- a/services/core/java/com/android/server/pm/EphemeralResolver.java
+++ b/services/core/java/com/android/server/pm/EphemeralResolver.java
@@ -78,6 +78,7 @@
int sequence) {
final String packageName;
final String splitName;
+ final int versionCode;
if (ephemeralResolveInfo != null) {
final ArrayList<EphemeralResolveInfo> ephemeralResolveInfoList =
new ArrayList<EphemeralResolveInfo>(1);
@@ -91,13 +92,16 @@
&& ephemeralIntentInfo.resolveInfo != null) {
packageName = ephemeralIntentInfo.resolveInfo.getPackageName();
splitName = ephemeralIntentInfo.splitName;
+ versionCode = ephemeralIntentInfo.resolveInfo.getVersionCode();
} else {
packageName = null;
splitName = null;
+ versionCode = -1;
}
} else {
packageName = null;
splitName = null;
+ versionCode = -1;
}
final Intent installerIntent = buildEphemeralInstallerIntent(
requestObj.launchIntent,
@@ -107,6 +111,7 @@
requestObj.userId,
packageName,
splitName,
+ versionCode,
requestObj.responseObj.token,
false /*needsPhaseTwo*/);
installerIntent.setComponent(new ComponentName(
@@ -123,7 +128,7 @@
*/
public static Intent buildEphemeralInstallerIntent(Intent launchIntent, Intent origIntent,
String callingPackage, String resolvedType, int userId, String ephemeralPackageName,
- String ephemeralSplitName, String token, boolean needsPhaseTwo) {
+ String ephemeralSplitName, int versionCode, String token, boolean needsPhaseTwo) {
// Construct the intent that launches the ephemeral installer
int flags = launchIntent.getFlags();
final Intent intent = new Intent();
@@ -181,6 +186,7 @@
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, ephemeralPackageName);
intent.putExtra(Intent.EXTRA_SPLIT_NAME, ephemeralSplitName);
+ intent.putExtra(Intent.EXTRA_VERSION_CODE, versionCode);
}
return intent;
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index a74e141..50309be 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 @@
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 e40b30f..63a5d14 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3449,7 +3449,7 @@
}
- private boolean filterSharedLibPackageLPr(PackageSetting ps, int uid, int userId) {
+ private boolean filterSharedLibPackageLPr(@Nullable PackageSetting ps, int uid, int userId) {
// System/shell/root get to see all static libs
final int appId = UserHandle.getAppId(uid);
if (appId == Process.SYSTEM_UID || appId == Process.SHELL_UID
@@ -3458,7 +3458,7 @@
}
// No package means no static lib as it is always on internal storage
- if (ps.pkg == null || !ps.pkg.applicationInfo.isStaticSharedLibrary()) {
+ if (ps == null || ps.pkg == null || !ps.pkg.applicationInfo.isStaticSharedLibrary()) {
return false;
}
@@ -9391,8 +9391,7 @@
}
// A package name must be unique; don't allow duplicates
- if (mPackages.containsKey(pkg.packageName)
- || mSharedLibraries.containsKey(pkg.packageName)) {
+ if (mPackages.containsKey(pkg.packageName)) {
throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
"Application package " + pkg.packageName
+ " already installed. Skipping duplicate.");
@@ -17704,6 +17703,13 @@
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 aa421b1..49b96b0 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 @@
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 @@
case "-3":
listThirdParty = true;
break;
+ case "--show-versioncode":
+ showVersionCode = true;
+ break;
case "--user":
userId = UserHandle.parseUserArg(getNextArgRequired());
break;
@@ -664,8 +668,11 @@
pw.print(info.applicationInfo.sourceDir);
pw.print("=");
}
- pw.print(info.packageName); pw.print( " versionCode:"
- + info.applicationInfo.versionCode);
+ pw.print(info.packageName);
+ if (showVersionCode) {
+ pw.print(" versionCode:");
+ pw.print(info.applicationInfo.versionCode);
+ }
if (listInstaller) {
pw.print(" installer=");
pw.print(mInterface.getInstallerPackageName(info.packageName));
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index ae709fe..56d679ef 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1529,10 +1529,11 @@
if (UserHandle.getUserId(callingUid) != userId) {
throw new SecurityException("Invalid user-ID");
}
- if (injectGetPackageUid(packageName, userId) == callingUid) {
- return; // Caller is valid.
+ if (injectGetPackageUid(packageName, userId) != callingUid) {
+ throw new SecurityException("Calling package name mismatch");
}
- throw new SecurityException("Calling package name mismatch");
+ Preconditions.checkState(!isEphemeralApp(packageName, userId),
+ "Ephemeral apps can't use ShortcutManager");
}
// Overridden in unit tests to execute r synchronously.
@@ -3073,6 +3074,10 @@
return (ai != null) && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
}
+ private static boolean isEphemeralApp(@Nullable ApplicationInfo ai) {
+ return (ai != null) && ai.isEphemeralApp();
+ }
+
private static boolean isInstalled(@Nullable PackageInfo pi) {
return (pi != null) && isInstalled(pi.applicationInfo);
}
@@ -3097,6 +3102,10 @@
return getApplicationInfo(packageName, userId) != null;
}
+ boolean isEphemeralApp(String packageName, int userId) {
+ return isEphemeralApp(getApplicationInfo(packageName, userId));
+ }
+
@Nullable
XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
return activityInfo.loadXmlMetaData(mContext.getPackageManager(), key);
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 02b46ec..4fd51b2 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -35,6 +35,7 @@
import android.provider.Settings;
import android.util.AndroidRuntimeException;
import android.util.Log;
+import android.webkit.UserPackage;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewZygote;
@@ -271,6 +272,12 @@
}
@Override
+ public List<UserPackage> getPackageInfoForProviderAllUsers(Context context,
+ WebViewProviderInfo configInfo) {
+ return UserPackage.getPackageInfosAllUsers(context, configInfo.packageName, PACKAGE_FLAGS);
+ }
+
+ @Override
public int getMultiProcessSetting(Context context) {
return Settings.Global.getInt(context.getContentResolver(),
Settings.Global.WEBVIEW_MULTIPROCESS, 0);
diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java
index fd137eb..b06f829 100644
--- a/services/core/java/com/android/server/webkit/SystemInterface.java
+++ b/services/core/java/com/android/server/webkit/SystemInterface.java
@@ -20,8 +20,11 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.ContentObserver;
+import android.webkit.UserPackage;
import android.webkit.WebViewProviderInfo;
+import java.util.List;
+
/**
* System interface for the WebViewUpdateService.
* This interface provides a way to test the WebView preparation mechanism - during normal use this
@@ -49,6 +52,14 @@
public boolean systemIsDebuggable();
public PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
throws NameNotFoundException;
+ /**
+ * Get the PackageInfos of all users for the package represented by {@param configInfo}.
+ * @return an array of UserPackages for a certain package, each UserPackage being belonging to a
+ * certain user. The returned array can contain null PackageInfos if the given package
+ * is uninstalled for some user.
+ */
+ public List<UserPackage> getPackageInfoForProviderAllUsers(Context context,
+ WebViewProviderInfo configInfo);
public int getMultiProcessSetting(Context context);
public void setMultiProcessSetting(Context context, int value);
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 311570e..4a105e1 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -94,6 +94,9 @@
case Intent.ACTION_USER_ADDED:
mImpl.handleNewUser(userId);
break;
+ case Intent.ACTION_USER_REMOVED:
+ mImpl.handleUserRemoved(userId);
+ break;
}
}
};
@@ -112,6 +115,7 @@
IntentFilter userAddedFilter = new IntentFilter();
userAddedFilter.addAction(Intent.ACTION_USER_ADDED);
+ userAddedFilter.addAction(Intent.ACTION_USER_REMOVED);
getContext().registerReceiverAsUser(mWebViewUpdatedReceiver, UserHandle.ALL,
userAddedFilter, null /* broadcast permission */, null /* handler */);
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index edfb11c..f016830 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -24,6 +24,7 @@
import android.os.UserHandle;
import android.util.Base64;
import android.util.Slog;
+import android.webkit.UserPackage;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
@@ -100,32 +101,41 @@
private boolean existsValidNonFallbackProvider(WebViewProviderInfo[] providers) {
for (WebViewProviderInfo provider : providers) {
if (provider.availableByDefault && !provider.isFallback) {
- try {
- PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(provider);
- if (isInstalledPackage(packageInfo) && isEnabledPackage(packageInfo)
- && mWebViewUpdater.isValidProvider(provider, packageInfo)) {
- return true;
- }
- } catch (NameNotFoundException e) {
- // A non-existent provider is neither valid nor enabled
+ // userPackages can contain null objects.
+ List<UserPackage> userPackages =
+ mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider);
+ if (isInstalledAndEnabledForAllUsers(userPackages) &&
+ // Checking validity of the package for the system user (rather than all
+ // users) since package validity depends not on the user but on the package
+ // itself.
+ mWebViewUpdater.isValidProvider(provider,
+ userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo())) {
+ return true;
}
}
}
return false;
}
- /**
- * Called when a new user has been added to update the state of its fallback package.
- */
void handleNewUser(int userId) {
- if (!mSystemInterface.isFallbackLogicEnabled()) return;
+ handleUserChange();
+ }
- WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
- WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
- if (fallbackProvider == null) return;
+ void handleUserRemoved(int userId) {
+ handleUserChange();
+ }
- mSystemInterface.enablePackageForUser(fallbackProvider.packageName,
- !existsValidNonFallbackProvider(webviewProviders), userId);
+ /**
+ * Called when a user was added or removed to ensure fallback logic and WebView preparation are
+ * triggered. This has to be done since the WebView package we use depends on the enabled-state
+ * of packages for all users (so adding or removing a user might cause us to change package).
+ */
+ private void handleUserChange() {
+ if (mSystemInterface.isFallbackLogicEnabled()) {
+ updateFallbackState(mSystemInterface.getWebViewPackages());
+ }
+ // Potentially trigger package-changing logic.
+ mWebViewUpdater.updateCurrentWebViewPackage(null);
}
void notifyRelroCreationCompleted() {
@@ -141,7 +151,7 @@
}
WebViewProviderInfo[] getValidWebViewPackages() {
- return mWebViewUpdater.getValidAndInstalledWebViewPackages();
+ return mWebViewUpdater.getValidWebViewPackages();
}
WebViewProviderInfo[] getWebViewPackages() {
@@ -160,7 +170,7 @@
if (!mSystemInterface.isFallbackLogicEnabled()) return;
WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
- updateFallbackState(webviewProviders, true);
+ updateFallbackState(webviewProviders);
}
/**
@@ -185,35 +195,23 @@
}
}
if (!changedPackageAvailableByDefault) return;
- updateFallbackState(webviewProviders, false);
+ updateFallbackState(webviewProviders);
}
- private void updateFallbackState(WebViewProviderInfo[] webviewProviders, boolean isBoot) {
+ private void updateFallbackState(WebViewProviderInfo[] webviewProviders) {
// If there exists a valid and enabled non-fallback package - disable the fallback
// package, otherwise, enable it.
WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
if (fallbackProvider == null) return;
boolean existsValidNonFallbackProvider = existsValidNonFallbackProvider(webviewProviders);
- boolean isFallbackEnabled = false;
- try {
- isFallbackEnabled = isEnabledPackage(
- mSystemInterface.getPackageInfoForProvider(fallbackProvider));
- } catch (NameNotFoundException e) {
- // No fallback package installed -> early out.
- return;
- }
-
- if (existsValidNonFallbackProvider
- // During an OTA the primary user's WebView state might differ from other users', so
- // ignore the state of that user during boot.
- && (isFallbackEnabled || isBoot)) {
+ List<UserPackage> userPackages =
+ mSystemInterface.getPackageInfoForProviderAllUsers(mContext, fallbackProvider);
+ if (existsValidNonFallbackProvider && !isDisabledForAllUsers(userPackages)) {
mSystemInterface.uninstallAndDisablePackageForAllUsers(mContext,
fallbackProvider.packageName);
} else if (!existsValidNonFallbackProvider
- // During an OTA the primary user's WebView state might differ from other users', so
- // ignore the state of that user during boot.
- && (!isFallbackEnabled || isBoot)) {
+ && !isInstalledAndEnabledForAllUsers(userPackages)) {
// Enable the fallback package for all users.
mSystemInterface.enablePackageForAllUsers(mContext,
fallbackProvider.packageName, true);
@@ -376,38 +374,8 @@
* or the replacement are done).
*/
public String changeProviderAndSetting(String newProviderName) {
- PackageInfo oldPackage = null;
- PackageInfo newPackage = null;
- boolean providerChanged = false;
- synchronized(mLock) {
- oldPackage = mCurrentWebViewPackage;
- mSystemInterface.updateUserSetting(mContext, newProviderName);
-
- try {
- newPackage = findPreferredWebViewPackage();
- providerChanged = (oldPackage == null)
- || !newPackage.packageName.equals(oldPackage.packageName);
- } catch (WebViewPackageMissingException e) {
- mCurrentWebViewPackage = null;
- Slog.e(TAG, "Tried to change WebView provider but failed to fetch WebView " +
- "package " + e);
- // If we don't perform the user change but don't have an installed WebView
- // package, we will have changed the setting and it will be used when a package
- // is available.
- return "";
- }
- // Perform the provider change if we chose a new provider
- if (providerChanged) {
- onWebViewProviderChanged(newPackage);
- }
- }
- // Kill apps using the old provider only if we changed provider
- if (providerChanged && oldPackage != null) {
- mSystemInterface.killPackageDependents(oldPackage.packageName);
- }
- // Return the new provider, this is not necessarily the one we were asked to switch to
- // But the persistent setting will now be pointing to the provider we were asked to
- // switch to anyway
+ PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
+ if (newPackage == null) return "";
return newPackage.packageName;
}
@@ -437,15 +405,14 @@
}
}
- private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos(boolean onlyInstalled) {
+ private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
List<ProviderAndPackageInfo> providers = new ArrayList<>();
for(int n = 0; n < allProviders.length; n++) {
try {
PackageInfo packageInfo =
mSystemInterface.getPackageInfoForProvider(allProviders[n]);
- if ((!onlyInstalled || isInstalledPackage(packageInfo))
- && isValidProvider(allProviders[n], packageInfo)) {
+ if (isValidProvider(allProviders[n], packageInfo)) {
providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
}
} catch (NameNotFoundException e) {
@@ -458,9 +425,8 @@
/**
* Fetch only the currently valid WebView packages.
**/
- public WebViewProviderInfo[] getValidAndInstalledWebViewPackages() {
- ProviderAndPackageInfo[] providersAndPackageInfos =
- getValidWebViewPackagesAndInfos(true /* only fetch installed packages */);
+ public WebViewProviderInfo[] getValidWebViewPackages() {
+ ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
WebViewProviderInfo[] providers =
new WebViewProviderInfo[providersAndPackageInfos.length];
for(int n = 0; n < providersAndPackageInfos.length; n++) {
@@ -487,39 +453,49 @@
*
*/
private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
- ProviderAndPackageInfo[] providers =
- getValidWebViewPackagesAndInfos(false /* onlyInstalled */);
+ ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
- // If the user has chosen provider, use that
+ // If the user has chosen provider, use that (if it's installed and enabled for all
+ // users).
for (ProviderAndPackageInfo providerAndPackage : providers) {
- if (providerAndPackage.provider.packageName.equals(userChosenProvider)
- && isInstalledPackage(providerAndPackage.packageInfo)
- && isEnabledPackage(providerAndPackage.packageInfo)) {
- return providerAndPackage.packageInfo;
+ if (providerAndPackage.provider.packageName.equals(userChosenProvider)) {
+ // userPackages can contain null objects.
+ List<UserPackage> userPackages =
+ mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+ providerAndPackage.provider);
+ if (isInstalledAndEnabledForAllUsers(userPackages)) {
+ return providerAndPackage.packageInfo;
+ }
}
}
// User did not choose, or the choice failed; use the most stable provider that is
- // installed and enabled for the device owner, and available by default (not through
+ // installed and enabled for all users, and available by default (not through
// user choice).
for (ProviderAndPackageInfo providerAndPackage : providers) {
- if (providerAndPackage.provider.availableByDefault
- && isInstalledPackage(providerAndPackage.packageInfo)
- && isEnabledPackage(providerAndPackage.packageInfo)) {
- return providerAndPackage.packageInfo;
+ if (providerAndPackage.provider.availableByDefault) {
+ // userPackages can contain null objects.
+ List<UserPackage> userPackages =
+ mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+ providerAndPackage.provider);
+ if (isInstalledAndEnabledForAllUsers(userPackages)) {
+ return providerAndPackage.packageInfo;
+ }
}
}
// Could not find any installed and enabled package either, use the most stable and
// default-available provider.
+ // TODO(gsennton) remove this when we have a functional WebView stub.
for (ProviderAndPackageInfo providerAndPackage : providers) {
if (providerAndPackage.provider.availableByDefault) {
return providerAndPackage.packageInfo;
}
}
+ // This should never happen during normal operation (only with modified system images).
mAnyWebViewInstalled = false;
throw new WebViewPackageMissingException("Could not find a loadable WebView package");
}
@@ -702,6 +678,48 @@
mAnyWebViewInstalled));
}
}
+
+ /**
+ * Update the current WebView package.
+ * @param newProviderName the package to switch to, null if no package has been explicitly
+ * chosen.
+ */
+ public PackageInfo updateCurrentWebViewPackage(String newProviderName) {
+ PackageInfo oldPackage = null;
+ PackageInfo newPackage = null;
+ boolean providerChanged = false;
+ synchronized(mLock) {
+ oldPackage = mCurrentWebViewPackage;
+
+ if (newProviderName != null) {
+ mSystemInterface.updateUserSetting(mContext, newProviderName);
+ }
+
+ try {
+ newPackage = findPreferredWebViewPackage();
+ providerChanged = (oldPackage == null)
+ || !newPackage.packageName.equals(oldPackage.packageName);
+ } catch (WebViewPackageMissingException e) {
+ // If updated the Setting but don't have an installed WebView package, the
+ // Setting will be used when a package is available.
+ mCurrentWebViewPackage = null;
+ Slog.e(TAG, "Couldn't find WebView package to use " + e);
+ return null;
+ }
+ // Perform the provider change if we chose a new provider
+ if (providerChanged) {
+ onWebViewProviderChanged(newPackage);
+ }
+ }
+ // Kill apps using the old provider only if we changed provider
+ if (providerChanged && oldPackage != null) {
+ mSystemInterface.killPackageDependents(oldPackage.packageName);
+ }
+ // Return the new provider, this is not necessarily the one we were asked to switch to,
+ // but the persistent setting will now be pointing to the provider we were asked to
+ // switch to anyway.
+ return newPackage;
+ }
}
private static boolean providerHasValidSignature(WebViewProviderInfo provider,
@@ -730,20 +748,27 @@
}
/**
- * Returns whether the given package is enabled.
- * This state can be changed by the user from Settings->Apps
+ * Return true iff {@param packageInfos} point to only installed and enabled packages.
+ * The given packages {@param packageInfos} should all be pointing to the same package, but each
+ * PackageInfo representing a different user's package.
*/
- private static boolean isEnabledPackage(PackageInfo packageInfo) {
- return packageInfo.applicationInfo.enabled;
+ private static boolean isInstalledAndEnabledForAllUsers(
+ List<UserPackage> userPackages) {
+ for (UserPackage userPackage : userPackages) {
+ if (!userPackage.isInstalledPackage() || !userPackage.isEnabledPackage()) {
+ return false;
+ }
+ }
+ return true;
}
- /**
- * Return true if the package is installed and not hidden
- */
- private static boolean isInstalledPackage(PackageInfo packageInfo) {
- return (((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0)
- && ((packageInfo.applicationInfo.privateFlags
- & ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0));
+ private static boolean isDisabledForAllUsers(List<UserPackage> userPackages) {
+ for (UserPackage userPackage : userPackages) {
+ if (userPackage.getPackageInfo() != null && userPackage.isEnabledPackage()) {
+ return false;
+ }
+ }
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 34f6752..27e0f29 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -56,7 +56,7 @@
private static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 2;
private final IApplicationToken mToken;
- private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Handler mHandler;
private final Runnable mOnWindowsDrawn = () -> {
if (mListener == null) {
@@ -186,6 +186,7 @@
int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
WindowManagerService service) {
super(listener, service);
+ mHandler = new Handler(service.mH.getLooper());
mToken = token;
synchronized(mWindowMap) {
AppWindowToken atoken = mRoot.getAppWindowToken(mToken.asBinder());
@@ -492,7 +493,7 @@
private boolean createSnapshot() {
final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
- mContainer.mTask);
+ mContainer.mTask.mTaskId, mContainer.mTask.mUserId, false /* restoreFromDisk */);
if (snapshot == null) {
return false;
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index ac9859d..079dc8f 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -375,6 +375,7 @@
// affected.
mService.getDefaultDisplayContentLocked().getDockedDividerController()
.notifyAppVisibilityChanged();
+ mService.mTaskSnapshotController.notifyAppVisibilityChanged(this, visible);
}
}
@@ -674,7 +675,7 @@
// well there is no point now.
if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Nulling last startingData");
startingData = null;
- } else if (mChildren.size() == 1 && startingSurface != null) {
+ } else if (mChildren.size() == 1 && startingSurface != null && !isRelaunching()) {
// If this is the last window except for a starting transition window,
// we need to get rid of the starting transition.
if (getController() != null) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 505051f..914cc8d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2259,10 +2259,11 @@
}
// Don't include wallpaper in bounds calculation
- if (includeDecor && !stackBounds.isEmpty()) {
- frame.set(stackBounds);
- } else if (includeDecor) {
- mutableIncludeFullDisplay.value = true;
+ if (!mutableIncludeFullDisplay.value && includeDecor) {
+ final TaskStack stack = w.getStack();
+ if (stack != null) {
+ stack.getBounds(frame);
+ }
} else if (!mutableIncludeFullDisplay.value && !w.mIsWallpaper) {
final Rect wf = w.mFrame;
final Rect cr = w.mContentInsets;
@@ -2281,7 +2282,7 @@
final boolean foundTargetWs =
(w.mAppToken != null && w.mAppToken.token == appToken)
|| (mScreenshotApplicationState.appWin != null && wallpaperOnly);
- if (foundTargetWs && w.isDisplayedLw() && winAnim.getShown()) {
+ if (foundTargetWs && winAnim.getShown()) {
mScreenshotApplicationState.screenshotReady = true;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 680d0f2..3a3ec71 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.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;
@@ -90,9 +91,11 @@
// Whether this task is an on-top launcher task, which is determined by the root activity.
private boolean mIsOnTopLauncher;
+ private TaskDescription mTaskDescription;
+
Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode, boolean homeTask,
- TaskWindowContainerController controller) {
+ TaskDescription taskDescription, TaskWindowContainerController controller) {
mTaskId = taskId;
mStack = stack;
mUserId = userId;
@@ -102,6 +105,7 @@
mHomeTask = homeTask;
setController(controller);
setBounds(bounds, overrideConfig);
+ mTaskDescription = taskDescription;
}
DisplayContent getDisplayContent() {
@@ -647,6 +651,14 @@
}
}
+ void setTaskDescription(TaskDescription taskDescription) {
+ mTaskDescription = taskDescription;
+ }
+
+ TaskDescription getTaskDescription() {
+ return mTaskDescription;
+ }
+
@Override
boolean fillsParent() {
return mFillsParent || !StackId.isTaskResizeAllowed(mStack.mStackId);
@@ -688,6 +700,5 @@
pw.println(triplePrefix + "Activity #" + i + " " + wtoken);
wtoken.dump(pw, triplePrefix);
}
-
}
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index c86229b..601bf28 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
@@ -19,8 +19,11 @@
import android.annotation.Nullable;
import android.app.ActivityManager.TaskSnapshot;
import android.util.ArrayMap;
+import android.util.LruCache;
import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Map.Entry;
/**
* Caches snapshots. See {@link TaskSnapshotController}.
@@ -29,51 +32,125 @@
*/
class TaskSnapshotCache {
- private final ArrayMap<AppWindowToken, Task> mAppTaskMap = new ArrayMap<>();
- private final ArrayMap<Task, CacheEntry> mCache = new ArrayMap<>();
+ // TODO: Make this more dynamic to accomodate for different clients.
+ private static final int RETRIEVAL_CACHE_SIZE = 4;
+
+ private final WindowManagerService mService;
+ private final TaskSnapshotLoader mLoader;
+ private final ArrayMap<AppWindowToken, Integer> mAppTaskMap = new ArrayMap<>();
+ private final ArrayMap<Integer, CacheEntry> mRunningCache = new ArrayMap<>();
+ private final LruCache<Integer, TaskSnapshot> mRetrievalCache =
+ new LruCache<>(RETRIEVAL_CACHE_SIZE);
+
+ TaskSnapshotCache(WindowManagerService service, TaskSnapshotLoader loader) {
+ mService = service;
+ mLoader = loader;
+ }
void putSnapshot(Task task, TaskSnapshot snapshot) {
- final CacheEntry entry = mCache.get(task);
+ final CacheEntry entry = mRunningCache.get(task.mTaskId);
if (entry != null) {
mAppTaskMap.remove(entry.topApp);
}
final AppWindowToken top = task.getTopChild();
- mAppTaskMap.put(top, task);
- mCache.put(task, new CacheEntry(snapshot, task.getTopChild()));
- }
-
- @Nullable TaskSnapshot getSnapshot(Task task) {
- final CacheEntry entry = mCache.get(task);
- return entry != null ? entry.snapshot : null;
+ mAppTaskMap.put(top, task.mTaskId);
+ mRunningCache.put(task.mTaskId, new CacheEntry(snapshot, task.getTopChild()));
+ mRetrievalCache.put(task.mTaskId, snapshot);
}
/**
- * Cleans the cache after an app window token's process died.
+ * If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW MANAGER LOCK!
*/
- void cleanCache(AppWindowToken wtoken) {
- final Task task = mAppTaskMap.get(wtoken);
- if (task != null) {
- removeEntry(task);
+ @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {
+
+ synchronized (mService.mWindowMap) {
+ // Try the running cache.
+ final CacheEntry entry = mRunningCache.get(taskId);
+ if (entry != null) {
+ return entry.snapshot;
+ }
+
+ // Try the retrieval cache.
+ final TaskSnapshot snapshot = mRetrievalCache.get(taskId);
+ if (snapshot != null) {
+ return snapshot;
+ }
+ }
+
+ // Try to restore from disk if asked.
+ if (!restoreFromDisk) {
+ return null;
+ }
+ return tryRestoreFromDisk(taskId, userId);
+ }
+
+ /**
+ * DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING THIS METHOD!
+ */
+ private TaskSnapshot tryRestoreFromDisk(int taskId, int userId) {
+ final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId);
+ if (snapshot == null) {
+ return null;
+ }
+ synchronized (mService.mWindowMap) {
+ mRetrievalCache.put(taskId, snapshot);
+ }
+ return snapshot;
+ }
+
+ /**
+ * Called when an app token has been removed
+ */
+ void onAppRemoved(AppWindowToken wtoken) {
+ final Integer taskId = mAppTaskMap.get(wtoken);
+ if (taskId != null) {
+ removeRunningEntry(taskId);
+ }
+ if (wtoken.mTask != null) {
+ mRetrievalCache.remove(wtoken.mTask.mTaskId);
}
}
- private void removeEntry(Task task) {
- final CacheEntry entry = mCache.get(task);
+ /**
+ * Callend when an app window token's process died.
+ */
+ void onAppDied(AppWindowToken wtoken) {
+ final Integer taskId = mAppTaskMap.get(wtoken);
+ if (taskId != null) {
+ removeRunningEntry(taskId);
+ }
+ }
+
+ void onTaskRemoved(int taskId) {
+ removeRunningEntry(taskId);
+ mRetrievalCache.remove(taskId);
+ }
+
+ private void removeRunningEntry(int taskId) {
+ final CacheEntry entry = mRunningCache.get(taskId);
if (entry != null) {
mAppTaskMap.remove(entry.topApp);
- mCache.remove(task);
+ mRunningCache.remove(taskId);
}
}
void dump(PrintWriter pw, String prefix) {
final String doublePrefix = prefix + " ";
final String triplePrefix = doublePrefix + " ";
+ final String quadruplePrefix = triplePrefix + " ";
pw.println(prefix + "SnapshotCache");
- for (int i = mCache.size() - 1; i >= 0; i--) {
- final CacheEntry entry = mCache.valueAt(i);
- pw.println(doublePrefix + "Entry taskId=" + mCache.keyAt(i).mTaskId);
- pw.println(triplePrefix + "topApp=" + entry.topApp);
- pw.println(triplePrefix + "snapshot=" + entry.snapshot);
+ pw.println(doublePrefix + "RunningCache");
+ for (int i = mRunningCache.size() - 1; i >= 0; i--) {
+ final CacheEntry entry = mRunningCache.valueAt(i);
+ pw.println(triplePrefix + "Entry taskId=" + mRunningCache.keyAt(i));
+ pw.println(quadruplePrefix + "topApp=" + entry.topApp);
+ pw.println(quadruplePrefix + "snapshot=" + entry.snapshot);
+ }
+ pw.println(doublePrefix + "RetrievalCache");
+ final Map<Integer, TaskSnapshot> retrievalSnapshot = mRetrievalCache.snapshot();
+ for (Entry<Integer, TaskSnapshot> entry : retrievalSnapshot.entrySet()) {
+ pw.println(triplePrefix + "Entry taskId=" + entry.getKey());
+ pw.println(quadruplePrefix + "snapshot=" + entry.getValue());
}
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 10ecf3b..2b74f84 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -26,9 +26,10 @@
import android.util.ArraySet;
import android.view.WindowManagerPolicy.StartingSurface;
+import com.google.android.collect.Sets;
+
import com.android.internal.annotations.VisibleForTesting;
-import java.io.File;
import java.io.PrintWriter;
/**
@@ -48,7 +49,7 @@
private final WindowManagerService mService;
- private final TaskSnapshotCache mCache = new TaskSnapshotCache();
+ private final TaskSnapshotCache mCache;
private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
Environment::getDataSystemCeDirectory);
private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
@@ -56,6 +57,7 @@
TaskSnapshotController(WindowManagerService service) {
mService = service;
+ mCache = new TaskSnapshotCache(mService, mLoader);
}
void systemReady() {
@@ -66,10 +68,27 @@
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)) {
@@ -86,8 +105,12 @@
}
}
- @Nullable TaskSnapshot getSnapshot(Task task) {
- return mCache.getSnapshot(task);
+ /**
+ * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
+ * MANAGER LOCK WHEN CALLING THIS METHOD!
+ */
+ @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {
+ return mCache.getSnapshot(taskId, userId, restoreFromDisk);
}
/**
@@ -138,20 +161,18 @@
* Called when an {@link AppWindowToken} has been removed.
*/
void onAppRemoved(AppWindowToken wtoken) {
- // TODO: Clean from both recents and running cache.
- mCache.cleanCache(wtoken);
+ mCache.onAppRemoved(wtoken);
}
/**
* Called when the process of an {@link AppWindowToken} has died.
*/
void onAppDied(AppWindowToken wtoken) {
-
- // TODO: Only clean from running cache.
- mCache.cleanCache(wtoken);
+ mCache.onAppDied(wtoken);
}
void notifyTaskRemovedFromRecents(int taskId, int userId) {
+ mCache.onTaskRemoved(taskId);
mPersister.onTaskRemovedFromRecents(taskId, userId);
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 4a09423..cfcbbd0 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -26,12 +26,16 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import android.app.ActivityManager.TaskDescription;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.GraphicBuffer;
+import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.Slog;
@@ -43,6 +47,7 @@
import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicy.StartingSurface;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.view.BaseIWindow;
/**
@@ -61,6 +66,7 @@
private final WindowManagerService mService;
private boolean mHasDrawn;
private boolean mReportNextDraw;
+ private Paint mFillBackgroundPaint = new Paint();
static TaskSnapshotSurface create(WindowManagerService service, AppWindowToken token,
GraphicBuffer snapshot) {
@@ -73,6 +79,7 @@
final Rect tmpRect = new Rect();
final Rect tmpFrame = new Rect();
final Configuration tmpConfiguration = new Configuration();
+ int fillBackgroundColor = Color.WHITE;
synchronized (service.mWindowMap) {
layoutParams.type = TYPE_APPLICATION_STARTING;
layoutParams.format = snapshot.getFormat();
@@ -90,6 +97,12 @@
layoutParams.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
layoutParams.setTitle(String.format(TITLE_FORMAT, token.mTask.mTaskId));
+ if (token.mTask != null) {
+ final TaskDescription taskDescription = token.mTask.getTaskDescription();
+ if (taskDescription != null) {
+ fillBackgroundColor = taskDescription.getBackgroundColor();
+ }
+ }
}
try {
final int res = session.addToDisplay(window, window.mSeq, layoutParams,
@@ -103,7 +116,7 @@
// Local call.
}
final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
- surface);
+ surface, fillBackgroundColor);
window.setOuter(snapshotSurface);
try {
session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, tmpFrame,
@@ -116,11 +129,14 @@
return snapshotSurface;
}
- private TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface) {
+ @VisibleForTesting
+ TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface,
+ int fillBackgroundColor) {
mService = service;
mSession = WindowManagerGlobal.getWindowSession();
mWindow = window;
mSurface = surface;
+ mFillBackgroundPaint.setColor(fillBackgroundColor);
}
@Override
@@ -136,7 +152,9 @@
// TODO: Just wrap the buffer here without any copying.
final Canvas c = mSurface.lockHardwareCanvas();
- c.drawBitmap(Bitmap.createHardwareBitmap(snapshot), 0, 0, null);
+ final Bitmap b = Bitmap.createHardwareBitmap(snapshot);
+ fillEmptyBackground(c, b);
+ c.drawBitmap(b, 0, 0, null);
mSurface.unlockCanvasAndPost(c);
final boolean reportNextDraw;
synchronized (mService.mWindowMap) {
@@ -149,6 +167,21 @@
mSurface.release();
}
+ @VisibleForTesting
+ void fillEmptyBackground(Canvas c, Bitmap b) {
+ final boolean fillHorizontally = c.getWidth() > b.getWidth();
+ final boolean fillVertically = c.getHeight() > b.getHeight();
+ if (fillHorizontally) {
+ c.drawRect(b.getWidth(), 0, c.getWidth(), fillVertically
+ ? b.getHeight()
+ : c.getHeight(),
+ mFillBackgroundPaint);
+ }
+ if (fillVertically) {
+ c.drawRect(0, b.getHeight(), c.getWidth(), c.getHeight(), mFillBackgroundPaint);
+ }
+ }
+
private void reportDrawn() {
synchronized (mService.mWindowMap) {
mReportNextDraw = false;
@@ -160,7 +193,7 @@
}
}
- private static Handler sHandler = new Handler() {
+ private static Handler sHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
index 96b79a6..61a2cd9 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 TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
int stackId, int userId, Rect bounds, Configuration overrideConfig, int resizeMode,
- boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers) {
+ boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers,
+ TaskDescription taskDescription) {
super(listener, WindowManagerService.getInstance());
mTaskId = taskId;
@@ -79,7 +81,7 @@
}
EventLog.writeEvent(WM_TASK_CREATED, taskId, stackId);
final Task task = createTask(taskId, stack, userId, bounds, overrideConfig, resizeMode,
- homeTask, isOnTopLauncher);
+ homeTask, isOnTopLauncher, taskDescription);
final int position = toTop ? POSITION_TOP : POSITION_BOTTOM;
stack.addTask(task, position, showForAllUsers, true /* moveParents */);
}
@@ -88,9 +90,9 @@
@VisibleForTesting
Task createTask(int taskId, TaskStack stack, int userId, Rect bounds,
Configuration overrideConfig, int resizeMode, boolean homeTask,
- boolean isOnTopLauncher) {
+ boolean isOnTopLauncher, TaskDescription taskDescription) {
return new Task(taskId, stack, userId, mService, bounds, overrideConfig, isOnTopLauncher,
- resizeMode, homeTask, this);
+ resizeMode, homeTask, taskDescription, this);
}
@Override
@@ -263,16 +265,13 @@
}
}
- /**
- * @return a graphic buffer representing a screenshot of a task
- */
- public TaskSnapshot getSnapshot() {
+ public void setTaskDescription(TaskDescription taskDescription) {
synchronized (mWindowMap) {
if (mContainer == null) {
- Slog.w(TAG_WM, "getSnapshot: taskId " + mTaskId + " not found.");
- return null;
+ Slog.w(TAG_WM, "setTaskDescription: taskId " + mTaskId + " not found.");
+ return;
}
- return mService.mTaskSnapshotController.getSnapshot(mContainer);
+ mContainer.setTaskDescription(taskDescription);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9a6cd2c..c8f4bd2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -97,6 +97,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.IActivityManager;
@@ -3902,6 +3903,10 @@
return true;
}
+ public TaskSnapshot getTaskSnapshot(int taskId, int userId) {
+ return mTaskSnapshotController.getSnapshot(taskId, userId, true /* restoreFromDisk */);
+ }
+
/**
* In case a task write/delete operation was lost because the system crashed, this makes sure to
* clean up the directory to remove obsolete files.
diff --git a/services/core/jni/com_android_server_lights_LightsService.cpp b/services/core/jni/com_android_server_lights_LightsService.cpp
index 2fd0603..74af639 100644
--- a/services/core/jni/com_android_server_lights_LightsService.cpp
+++ b/services/core/jni/com_android_server_lights_LightsService.cpp
@@ -81,7 +81,7 @@
// TODO(b/31632518)
if (gLight == nullptr) {
- gLight = ILight::getService("light");
+ gLight = ILight::getService();
}
if (gLight == nullptr) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8835ab2..f3b0131 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 @@
}
/**
- * 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 @@
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 @@
}
/**
- * 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 @@
}
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;
}
}
@@ -8814,7 +8827,7 @@
}
@Override
- public void notifyPendingSystemUpdate(long updateReceivedTime) {
+ public void notifyPendingSystemUpdate(@Nullable SystemUpdateInfo info) {
mContext.enforceCallingOrSelfPermission(permission.NOTIFY_PENDING_SYSTEM_UPDATE,
"Only the system update service can broadcast update information");
@@ -8824,13 +8837,14 @@
return;
}
- if (!mOwners.saveSystemUpdateInfo(updateReceivedTime)) {
- // Received time hasn't changed, don't send duplicate notification.
+ if (!mOwners.saveSystemUpdateInfo(info)) {
+ // Pending system update hasn't changed, don't send duplicate notification.
return;
}
- final Intent intent = new Intent(DeviceAdminReceiver.ACTION_NOTIFY_PENDING_SYSTEM_UPDATE);
- intent.putExtra(DeviceAdminReceiver.EXTRA_SYSTEM_UPDATE_RECEIVED_TIME, updateReceivedTime);
+ final Intent intent = new Intent(DeviceAdminReceiver.ACTION_NOTIFY_PENDING_SYSTEM_UPDATE)
+ .putExtra(DeviceAdminReceiver.EXTRA_SYSTEM_UPDATE_RECEIVED_TIME,
+ info == null ? -1 : info.getReceivedTime());
final long ident = mInjector.binderClearCallingIdentity();
try {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 99c76b1..a5500dd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -476,19 +476,20 @@
}
/**
- * @return Whether update received time has changed.
+ * Saves the given {@link SystemUpdateInfo} if it is different from the existing one, or if
+ * none exists.
+ *
+ * @return Whether the saved system update information has changed.
*/
- boolean saveSystemUpdateInfo(long receivedTime) {
- final SystemUpdateInfo newSystemUpdateInfo = SystemUpdateInfo.of(receivedTime);
+ boolean saveSystemUpdateInfo(@Nullable SystemUpdateInfo newInfo) {
synchronized (mLock) {
// Check if we already have the same update information.
- if (Objects.equals(newSystemUpdateInfo, mSystemUpdateInfo)) {
+ if (Objects.equals(newInfo, mSystemUpdateInfo)) {
return false;
}
- mSystemUpdateInfo = newSystemUpdateInfo;
+ mSystemUpdateInfo = newInfo;
new DeviceOwnerReadWriter().writeToFileLocked();
-
return true;
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index bedc6e1..c3ef23b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -280,7 +280,7 @@
Slog.i(TAG, "Entered the Android system server!");
int uptimeMillis = (int) SystemClock.elapsedRealtime();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, uptimeMillis);
- if (!mRuntimeRestart && !mFirstBoot) {
+ if (!mRuntimeRestart) {
MetricsLogger.histogram(null, "boot_system_server_init", uptimeMillis);
}
@@ -349,7 +349,6 @@
// Create the system service manager.
mSystemServiceManager = new SystemServiceManager(mSystemContext);
mSystemServiceManager.setRuntimeRestarted(mRuntimeRestart);
- mSystemServiceManager.setFirstBoot(mFirstBoot);
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
// Prepare the thread pool for init tasks that can be parallelized
SystemServerInitThreadPool.get();
@@ -376,7 +375,7 @@
if (StrictMode.conditionallyEnableDebugLogging()) {
Slog.i(TAG, "Enabled StrictMode for system server main thread.");
}
- if (!mRuntimeRestart && !mFirstBoot) {
+ if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
int uptimeMillis = (int) SystemClock.elapsedRealtime();
MetricsLogger.histogram(null, "boot_system_server_ready", uptimeMillis);
final int MAX_UPTIME_MILLIS = 60 * 1000;
@@ -391,6 +390,10 @@
throw new RuntimeException("Main thread loop unexpectedly exited");
}
+ private boolean isFirstBootOrUpgrade() {
+ return mPackageManagerService.isFirstBoot() || mPackageManagerService.isUpgrade();
+ }
+
private void reportWtf(String msg, Throwable e) {
Slog.w(TAG, "***********************************************");
Slog.wtf(TAG, "BOOT FAILURE " + msg, e);
@@ -535,7 +538,7 @@
mFirstBoot = mPackageManagerService.isFirstBoot();
mPackageManager = mSystemContext.getPackageManager();
traceEnd();
- if (!mRuntimeRestart && !mFirstBoot) {
+ if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
MetricsLogger.histogram(null, "boot_package_manager_init_ready",
(int) SystemClock.elapsedRealtime());
}
@@ -936,6 +939,12 @@
traceEnd();
}
+ if (!disableNonCoreServices) {
+ traceBeginAndSlog("StartFontServiceManager");
+ mSystemServiceManager.startService(FontManagerService.Lifecycle.class);
+ traceEnd();
+ }
+
if (!disableNonCoreServices && !disableTextServices) {
traceBeginAndSlog("StartTextServicesManager");
mSystemServiceManager.startService(TextServicesManagerService.Lifecycle.class);
diff --git a/services/tests/notification/AndroidManifest.xml b/services/tests/notification/AndroidManifest.xml
index 1ed8ed0..92f155f 100644
--- a/services/tests/notification/AndroidManifest.xml
+++ b/services/tests/notification/AndroidManifest.xml
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/services/tests/notification/src/com/android/server/notification/BadgeExtractorTest.java b/services/tests/notification/src/com/android/server/notification/BadgeExtractorTest.java
new file mode 100644
index 0000000..b26bac3
--- /dev/null
+++ b/services/tests/notification/src/com/android/server/notification/BadgeExtractorTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.notification;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.Notification.Builder;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BadgeExtractorTest {
+
+ @Mock RankingConfig mConfig;
+
+ private String mPkg = "com.android.server.notification";
+ private int mId = 1001;
+ private String mTag = null;
+ private int mUid = 1000;
+ private int mPid = 2000;
+ private UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ private NotificationRecord getNotificationRecord(NotificationChannel channel) {
+ final Builder builder = new Builder(getContext())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setPriority(Notification.PRIORITY_HIGH)
+ .setDefaults(Notification.DEFAULT_SOUND);
+
+ Notification n = builder.build();
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, mId, mTag, mUid,
+ mPid, n, mUser, null, System.currentTimeMillis());
+ NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
+ return r;
+ }
+
+ private Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ //
+ // Tests
+ //
+
+ @Test
+ public void testAppYesChannelNo() throws Exception {
+ BadgeExtractor extractor = new BadgeExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(true);
+ NotificationChannel channel =
+ new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED);
+ channel.setShowBadge(false);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertFalse(r.canShowBadge());
+ }
+
+ @Test
+ public void testAppNoChannelYes() throws Exception {
+ BadgeExtractor extractor = new BadgeExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(false);
+ NotificationChannel channel =
+ new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_HIGH);
+ channel.setShowBadge(true);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertFalse(r.canShowBadge());
+ }
+
+ @Test
+ public void testAppYesChannelYes() throws Exception {
+ BadgeExtractor extractor = new BadgeExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(true);
+ NotificationChannel channel =
+ new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED);
+ channel.setShowBadge(true);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertTrue(r.canShowBadge());
+ }
+
+ @Test
+ public void testAppNoChannelNo() throws Exception {
+ BadgeExtractor extractor = new BadgeExtractor();
+ extractor.setConfig(mConfig);
+
+ when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(false);
+ NotificationChannel channel =
+ new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED);
+ channel.setShowBadge(false);
+
+ NotificationRecord r = getNotificationRecord(channel);
+
+ extractor.process(r);
+
+ assertFalse(r.canShowBadge());
+ }
+}
diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
index ad436724a..468a26b 100644
--- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -230,9 +230,9 @@
n.flags |= Notification.FLAG_INSISTENT;
}
- StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, channel, id, mTag, mUid,
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
mPid, n, mUser, null, System.currentTimeMillis());
- NotificationRecord r = new NotificationRecord(getContext(), sbn);
+ NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
mService.addNotification(r);
return r;
}
diff --git a/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java b/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java
index f48d785..936531b 100644
--- a/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java
@@ -70,9 +70,7 @@
if (groupKey != null) {
nb.setGroup(groupKey);
}
- NotificationChannel channel =
- new NotificationChannel("test", "test", NotificationManager.IMPORTANCE_LOW);
- return new StatusBarNotification(pkg, pkg, channel, id, tag, 0, 0, nb.build(), user, null,
+ return new StatusBarNotification(pkg, pkg, id, tag, 0, 0, nb.build(), user, null,
System.currentTimeMillis());
}
diff --git a/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java b/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
index eee9cf1..f8a32bb 100644
--- a/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
+++ b/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
@@ -69,9 +69,9 @@
.setDefaults(Notification.DEFAULT_SOUND);
Notification n = builder.build();
- StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, channel, mId, mTag, mUid,
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, mId, mTag, mUid,
mPid, n, mUser, null, System.currentTimeMillis());
- NotificationRecord r = new NotificationRecord(getContext(), sbn);
+ NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
return r;
}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java
index 403b65c..aa08b41 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java
@@ -105,8 +105,8 @@
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
mRecordMinCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg,
- callPkg, getDefaultChannel(), 1, "minCall", callUid, callUid, n1,
- new UserHandle(userId), "", 2000));
+ callPkg, 1, "minCall", callUid, callUid, n1,
+ new UserHandle(userId), "", 2000), getDefaultChannel());
mRecordMinCall.setUserImportance(NotificationManager.IMPORTANCE_MIN);
Notification n2 = new Notification.Builder(mContext)
@@ -114,8 +114,8 @@
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
mRecordHighCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg,
- callPkg, getDefaultChannel(), 1, "highcall", callUid, callUid, n2,
- new UserHandle(userId), "", 1999));
+ callPkg, 1, "highcall", callUid, callUid, n2,
+ new UserHandle(userId), "", 1999), getDefaultChannel());
mRecordHighCall.setUserImportance(NotificationManager.IMPORTANCE_HIGH);
Notification n3 = new Notification.Builder(mContext)
@@ -124,43 +124,43 @@
.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
mRecordDefaultMedia = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "media", uid2, uid2, n3, new UserHandle(userId),
- "", 1499));
+ pkg2, 1, "media", uid2, uid2, n3, new UserHandle(userId),
+ "", 1499), getDefaultChannel());
mRecordDefaultMedia.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT);
Notification n4 = new Notification.Builder(mContext)
.setStyle(new Notification.MessagingStyle("sender!")).build();
mRecordInlineReply = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "inlinereply", uid2, uid2, n4, new UserHandle(userId),
- "", 1599));
+ pkg2, 1, "inlinereply", uid2, uid2, n4, new UserHandle(userId),
+ "", 1599), getDefaultChannel());
mRecordInlineReply.setUserImportance(NotificationManager.IMPORTANCE_HIGH);
mRecordInlineReply.setPackagePriority(Notification.PRIORITY_MAX);
Notification n5 = new Notification.Builder(mContext)
.setCategory(Notification.CATEGORY_MESSAGE).build();
mRecordSms = new NotificationRecord(mContext, new StatusBarNotification(smsPkg,
- smsPkg, getDefaultChannel(), 1, "sms", smsUid, smsUid, n5, new UserHandle(userId),
- "", 1299));
+ smsPkg, 1, "sms", smsUid, smsUid, n5, new UserHandle(userId),
+ "", 1299), getDefaultChannel());
mRecordSms.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT);
Notification n6 = new Notification.Builder(mContext).build();
mRecordStarredContact = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "starred", uid2, uid2, n6, new UserHandle(userId),
- "", 1259));
+ pkg2, 1, "starred", uid2, uid2, n6, new UserHandle(userId),
+ "", 1259), getDefaultChannel());
mRecordStarredContact.setContactAffinity(ValidateNotificationPeople.STARRED_CONTACT);
mRecordStarredContact.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT);
Notification n7 = new Notification.Builder(mContext).build();
mRecordContact = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "contact", uid2, uid2, n7, new UserHandle(userId),
- "", 1259));
+ pkg2, 1, "contact", uid2, uid2, n7, new UserHandle(userId),
+ "", 1259), getDefaultChannel());
mRecordContact.setContactAffinity(ValidateNotificationPeople.VALID_CONTACT);
mRecordContact.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT);
Notification n8 = new Notification.Builder(mContext).build();
mRecordUrgent = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "urgent", uid2, uid2, n8, new UserHandle(userId),
- "", 1258));
+ pkg2, 1, "urgent", uid2, uid2, n8, new UserHandle(userId),
+ "", 1258), getDefaultChannel());
mRecordUrgent.setUserImportance(NotificationManager.IMPORTANCE_HIGH);
Notification n9 = new Notification.Builder(mContext)
@@ -169,15 +169,15 @@
|Notification.FLAG_FOREGROUND_SERVICE, true)
.build();
mRecordCheater = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "cheater", uid2, uid2, n9, new UserHandle(userId),
- "", 9258));
+ pkg2, 1, "cheater", uid2, uid2, n9, new UserHandle(userId),
+ "", 9258), getDefaultChannel());
mRecordCheater.setUserImportance(NotificationManager.IMPORTANCE_LOW);
Notification n10 = new Notification.Builder(mContext)
.setStyle(new Notification.InboxStyle().setSummaryText("message!")).build();
mRecordEmail = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, getDefaultChannel(), 1, "email", uid2, uid2, n10, new UserHandle(userId),
- "", 1599));
+ pkg2, 1, "email", uid2, uid2, n10, new UserHandle(userId),
+ "", 1599), getDefaultChannel());
mRecordEmail.setUserImportance(NotificationManager.IMPORTANCE_HIGH);
}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java
index b6166f6..f0f4c4d 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -59,6 +59,7 @@
assertEquals(getChannel(key, i), ranking.getChannel());
assertEquals(getPeople(key, i), ranking.getAdditionalPeople());
assertEquals(getSnoozeCriteria(key, i), ranking.getSnoozeCriteria());
+ assertEquals(getShowBadge(i), ranking.canShowBadge());
}
}
@@ -68,9 +69,10 @@
Bundle overrideGroupKeys = new Bundle();
Bundle suppressedVisualEffects = new Bundle();
Bundle explanation = new Bundle();
- Bundle overrideChannels = new Bundle();
+ Bundle channels = new Bundle();
Bundle overridePeople = new Bundle();
Bundle snoozeCriteria = new Bundle();
+ Bundle showBadge = new Bundle();
int[] importance = new int[mKeys.length];
for (int i = 0; i < mKeys.length; i++) {
@@ -83,14 +85,15 @@
suppressedVisualEffects.putInt(key, getSuppressedVisualEffects(i));
importance[i] = getImportance(i);
explanation.putString(key, getExplanation(key));
- overrideChannels.putParcelable(key, getChannel(key, i));
+ channels.putParcelable(key, getChannel(key, i));
overridePeople.putStringArrayList(key, getPeople(key, i));
snoozeCriteria.putParcelableArrayList(key, getSnoozeCriteria(key, i));
+ showBadge.putBoolean(key, getShowBadge(i));
}
NotificationRankingUpdate update = new NotificationRankingUpdate(mKeys,
interceptedKeys.toArray(new String[0]), visibilityOverrides,
suppressedVisualEffects, importance, explanation, overrideGroupKeys,
- overrideChannels, overridePeople, snoozeCriteria);
+ channels, overridePeople, snoozeCriteria, showBadge);
return update;
}
@@ -122,6 +125,10 @@
return new NotificationChannel(key, key, getImportance(index));
}
+ private boolean getShowBadge(int index) {
+ return index % 3 == 0;
+ }
+
private ArrayList<String> getPeople(String key, int index) {
ArrayList<String> people = new ArrayList<>();
for (int i = 0; i < index; i++) {
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index 40938fd..92d9810 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -24,7 +24,9 @@
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -33,18 +35,25 @@
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.MessageQueue;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.support.test.annotation.UiThreadTest;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -58,30 +67,76 @@
@RunWith(AndroidJUnit4.class)
public class NotificationManagerServiceTest {
private final String pkg = "com.android.server.notification";
- private final int uid = 0;
+ private final int uid = Binder.getCallingUid();
private NotificationManagerService mNotificationManagerService;
private INotificationManager mBinderService;
private IPackageManager mPackageManager = mock(IPackageManager.class);
+ private Context mContext;
+ private HandlerThread mThread;
@Before
@UiThreadTest
public void setUp() throws Exception {
- final Context context = InstrumentationRegistry.getTargetContext();
- mNotificationManagerService = new NotificationManagerService(context);
+ mContext = InstrumentationRegistry.getTargetContext();
+ mNotificationManagerService = new NotificationManagerService(mContext);
// MockPackageManager - default returns ApplicationInfo with matching calling UID
final ApplicationInfo applicationInfo = new ApplicationInfo();
- applicationInfo.uid = Binder.getCallingUid();
+ applicationInfo.uid = uid;
when(mPackageManager.getApplicationInfo(any(), anyInt(), anyInt()))
.thenReturn(applicationInfo);
+ final PackageManager mockPackageManagerClient = mock(PackageManager.class);
+ when(mockPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
final LightsManager mockLightsManager = mock(LightsManager.class);
when(mockLightsManager.getLight(anyInt())).thenReturn(mock(Light.class));
- mNotificationManagerService.init(mPackageManager, mockLightsManager);
+ // Use a separate thread for service looper.
+ mThread = new HandlerThread("TestThread");
+ mThread.start();
+ // Mock NotificationListeners to bypass security checks.
+ final NotificationManagerService.NotificationListeners mockNotificationListeners =
+ mock(NotificationManagerService.NotificationListeners.class);
+ when(mockNotificationListeners.checkServiceTokenLocked(any())).thenReturn(
+ mockNotificationListeners.new ManagedServiceInfo(null,
+ new ComponentName(pkg, "test_class"), uid, true, null, 0));
+
+ mNotificationManagerService.init(mThread.getLooper(), mPackageManager,
+ mockPackageManagerClient, mockLightsManager, mockNotificationListeners);
// Tests call directly into the Binder.
mBinderService = mNotificationManagerService.getBinderService();
}
+ public void waitForIdle() throws Exception {
+ MessageQueue queue = mThread.getLooper().getQueue();
+ CountDownLatch latch = new CountDownLatch(1);
+ queue.addIdleHandler(new MessageQueue.IdleHandler() {
+ @Override public boolean queueIdle() {
+ latch.countDown();
+ return false;
+ }
+ });
+ latch.await();
+ if (!queue.isIdle()) {
+ waitForIdle();
+ }
+ }
+
+ private NotificationRecord generateNotificationRecord(NotificationChannel channel) {
+ if (channel == null) {
+ channel = new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT);
+ }
+ Notification n = new Notification.Builder(mContext)
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setPriority(Notification.PRIORITY_HIGH)
+ .build();
+ StatusBarNotification sbn = new StatusBarNotification(mContext.getPackageName(),
+ mContext.getPackageName(), 1, "tag", uid, 0,
+ n, new UserHandle(uid), null, 0);
+ return new NotificationRecord(mContext, sbn, channel);
+ }
+
@Test
@UiThreadTest
public void testCreateNotificationChannels_SingleChannel() throws Exception {
@@ -203,15 +258,120 @@
verify(usageStats, times(1)).registerBlocked(eq(r));
}
- private NotificationRecord generateNotificationRecord(NotificationChannel channel) {
- final Context context = InstrumentationRegistry.getTargetContext();
- Notification n = new Notification.Builder(context)
- .setContentTitle("foo")
- .setSmallIcon(android.R.drawable.sym_def_app_icon)
- .setPriority(Notification.PRIORITY_HIGH)
- .build();
- StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, channel, 1, "tag", uid, uid,
- n, UserHandle.SYSTEM, null, uid);
- return new NotificationRecord(context, sbn);
+ @Test
+ @UiThreadTest
+ public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
+ mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ generateNotificationRecord(null).getNotification(), new int[1], 0);
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(mContext.getPackageName());
+ assertEquals(1, notifs.length);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
+ mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ generateNotificationRecord(null).getNotification(), new int[1], 0);
+ mBinderService.cancelNotificationWithTag(mContext.getPackageName(), "tag", 0, 0);
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(mContext.getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
+ final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+ mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
+ mBinderService.cancelNotificationsFromListener(null, null);
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCancelAllNotificationsImmediatelyAfterEnqueue() throws Exception {
+ final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+ mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
+ mBinderService.cancelAllNotifications(sbn.getPackageName(), sbn.getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
+ final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+ sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
+ mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
+ mBinderService.cancelAllNotifications(sbn.getPackageName(), sbn.getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(1, notifs.length);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception {
+ final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+ sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
+ mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
+ mBinderService.cancelAllNotifications("other_pkg_name", sbn.getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(1, notifs.length);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCancelAllNotifications_NullPkgRemovesAll() throws Exception {
+ final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+ mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
+ mBinderService.cancelAllNotifications(null, sbn.getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCancelAllNotifications_NullPkgIgnoresUserAllNotifications() throws Exception {
+ final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+ mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ sbn.getId(), sbn.getNotification(), new int[1], UserHandle.USER_ALL);
+ // Null pkg is how we signal a user switch.
+ mBinderService.cancelAllNotifications(null, sbn.getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(1, notifs.length);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testSnoozeNotificationImmediatelyAfterEnqueue() throws Exception {
+ final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+ mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
+ mBinderService.snoozeNotificationFromListener(null, sbn.getKey());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(0, notifs.length);
}
}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
index fc94271f..15dcc26 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
@@ -136,10 +136,10 @@
Notification n = builder.build();
if (preO) {
- return new StatusBarNotification(pkg, pkg, defaultChannel, id1, tag1, uid, uid, n,
+ return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n,
mUser, null, uid);
} else {
- return new StatusBarNotification(pkg2, pkg2, channel, id2, tag2, uid2, uid2, n,
+ return new StatusBarNotification(pkg2, pkg2, id2, tag2, uid2, uid2, n,
mUser, null, uid2);
}
}
@@ -155,7 +155,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, record.getSound());
}
@@ -166,7 +166,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_SOUND, record.getSound());
}
@@ -178,7 +178,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_SOUND, record.getSound());
}
@@ -189,7 +189,7 @@
StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
assertEquals(CUSTOM_SOUND, record.getSound());
}
@@ -200,7 +200,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, false /* noisy */,
false /* defaultSound */, true /* buzzy */, true /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertNotNull(record.getVibration());
}
@@ -211,7 +211,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, false /* noisy */,
false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_VIBRATION, record.getVibration());
}
@@ -223,7 +223,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, false /* noisy */,
false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertTrue(!Objects.equals(CUSTOM_VIBRATION, record.getVibration()));
}
@@ -234,7 +234,7 @@
StatusBarNotification sbn = getNotification(false /*preO */, false /* noisy */,
false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
assertEquals(CUSTOM_CHANNEL_VIBRATION, record.getVibration());
}
@@ -245,7 +245,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_ATTRIBUTES, record.getAudioAttributes());
}
@@ -256,7 +256,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_ATTRIBUTES, record.getAudioAttributes());
}
@@ -264,7 +264,7 @@
public void testImportance_preUpgrade() throws Exception {
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(NotificationManager.IMPORTANCE_HIGH, record.getImportance());
}
@@ -275,7 +275,7 @@
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(NotificationManager.IMPORTANCE_LOW, record.getImportance());
}
@@ -283,7 +283,7 @@
public void testImportance_upgrade() throws Exception {
StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
assertEquals(NotificationManager.IMPORTANCE_DEFAULT, record.getImportance());
}
}
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index 59ac427..0320d8a 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 @@
.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 @@
.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 @@
.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 @@
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 @@
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 @@
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 460fcdf..b7931d4 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 @@
.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/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index ee49a00..a600e69 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -76,6 +76,7 @@
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -94,6 +95,7 @@
@Mock private DevicePolicyManager mMockDevicePolicyManager;
@Mock private IAccountManagerResponse mMockAccountManagerResponse;
@Mock private IBinder mMockBinder;
+ @Mock private INotificationManager mMockNotificationManager;
@Captor private ArgumentCaptor<Intent> mIntentCaptor;
@Captor private ArgumentCaptor<Bundle> mBundleCaptor;
@@ -129,7 +131,7 @@
Context realTestContext = getContext();
MyMockContext mockContext = new MyMockContext(realTestContext, mMockContext);
setContext(mockContext);
- mTestInjector = new TestInjector(realTestContext, mockContext);
+ mTestInjector = new TestInjector(realTestContext, mockContext, mMockNotificationManager);
mAms = new AccountManagerService(mTestInjector);
}
@@ -500,7 +502,7 @@
}
@SmallTest
- public void testStartAddAccountSessionUserSuccessWithoutPasswordForwarding() throws Exception {
+ public void testStartAddAccountSessionSuccessWithoutPasswordForwarding() throws Exception {
unlockSystemUser();
when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn(
PackageManager.PERMISSION_DENIED);
@@ -531,7 +533,7 @@
}
@SmallTest
- public void testStartAddAccountSessionUserSuccessWithPasswordForwarding() throws Exception {
+ public void testStartAddAccountSessionSuccessWithPasswordForwarding() throws Exception {
unlockSystemUser();
when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn(
PackageManager.PERMISSION_GRANTED);
@@ -564,7 +566,7 @@
}
@SmallTest
- public void testStartAddAccountSessionUserReturnWithInvalidIntent() throws Exception {
+ public void testStartAddAccountSessionReturnWithInvalidIntent() throws Exception {
unlockSystemUser();
ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = new ActivityInfo();
@@ -593,7 +595,7 @@
}
@SmallTest
- public void testStartAddAccountSessionUserReturnWithValidIntent() throws Exception {
+ public void testStartAddAccountSessionReturnWithValidIntent() throws Exception {
unlockSystemUser();
ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = new ActivityInfo();
@@ -626,7 +628,7 @@
}
@SmallTest
- public void testStartAddAccountSessionUserError() throws Exception {
+ public void testStartAddAccountSessionError() throws Exception {
unlockSystemUser();
Bundle options = createOptionsWithAccountName(
AccountManagerServiceTestFixtures.ACCOUNT_NAME_ERROR);
@@ -650,14 +652,629 @@
verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
}
+ @SmallTest
+ public void testStartUpdateCredentialsSessionWithNullResponse() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.startUpdateCredentialsSession(
+ null, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "authTokenType",
+ true, // expectActivityLaunch
+ null); // optionsIn
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testStartUpdateCredentialsSessionWithNullAccount() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.startUpdateCredentialsSession(
+ mMockAccountManagerResponse, // response
+ null,
+ "authTokenType",
+ true, // expectActivityLaunch
+ null); // optionsIn
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testStartUpdateCredentialsSessionSuccessWithoutPasswordForwarding()
+ throws Exception {
+ unlockSystemUser();
+ when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ Bundle options = createOptionsWithAccountName(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS);
+ mAms.startUpdateCredentialsSession(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "authTokenType",
+ false, // expectActivityLaunch
+ options); // optionsIn
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
+ assertNotNull(sessionBundle);
+ // Assert that session bundle is encrypted and hence data not visible.
+ assertNull(sessionBundle.getString(AccountManagerServiceTestFixtures.SESSION_DATA_NAME_1));
+ // Assert password is not returned
+ assertNull(result.getString(AccountManager.KEY_PASSWORD));
+ assertNull(result.getString(AccountManager.KEY_AUTHTOKEN, null));
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_STATUS_TOKEN,
+ result.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
+ }
+
+ @SmallTest
+ public void testStartUpdateCredentialsSessionSuccessWithPasswordForwarding() throws Exception {
+ unlockSystemUser();
+ when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn(
+ PackageManager.PERMISSION_GRANTED);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ Bundle options = createOptionsWithAccountName(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS);
+ mAms.startUpdateCredentialsSession(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "authTokenType",
+ false, // expectActivityLaunch
+ options); // optionsIn
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
+ assertNotNull(sessionBundle);
+ // Assert that session bundle is encrypted and hence data not visible.
+ assertNull(sessionBundle.getString(AccountManagerServiceTestFixtures.SESSION_DATA_NAME_1));
+ // Assert password is returned
+ assertEquals(result.getString(AccountManager.KEY_PASSWORD),
+ AccountManagerServiceTestFixtures.ACCOUNT_PASSWORD);
+ assertNull(result.getString(AccountManager.KEY_AUTHTOKEN));
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_STATUS_TOKEN,
+ result.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
+ }
+
+ @SmallTest
+ public void testStartUpdateCredentialsSessionReturnWithInvalidIntent() throws Exception {
+ unlockSystemUser();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ Bundle options = createOptionsWithAccountName(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE);
+
+ mAms.startUpdateCredentialsSession(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+ "authTokenType",
+ true, // expectActivityLaunch
+ options); // optionsIn
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_REMOTE_EXCEPTION), anyString());
+ }
+
+ @SmallTest
+ public void testStartUpdateCredentialsSessionReturnWithValidIntent() throws Exception {
+ unlockSystemUser();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ Bundle options = createOptionsWithAccountName(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE);
+
+ mAms.startUpdateCredentialsSession(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+ "authTokenType",
+ true, // expectActivityLaunch
+ options); // optionsIn
+
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+ assertNotNull(intent);
+ assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_RESULT));
+ assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK));
+ }
+
+ @SmallTest
+ public void testStartUpdateCredentialsSessionError() throws Exception {
+ unlockSystemUser();
+ Bundle options = createOptionsWithAccountName(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_ERROR);
+ options.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_INVALID_RESPONSE);
+ options.putString(AccountManager.KEY_ERROR_MESSAGE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.startUpdateCredentialsSession(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_ERROR,
+ "authTokenType",
+ true, // expectActivityLaunch
+ options); // optionsIn
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserWithNullResponse() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.finishSessionAsUser(
+ null, // response
+ createEncryptedSessionBundle(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS),
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ UserHandle.USER_SYSTEM);
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserWithNullSessionBundle() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.finishSessionAsUser(
+ mMockAccountManagerResponse, // response
+ null, // sessionBundle
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ UserHandle.USER_SYSTEM);
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserUserCannotModifyAccountNoDPM() throws Exception {
+ unlockSystemUser();
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+ when(mMockUserManager.getUserRestrictions(any(UserHandle.class))).thenReturn(bundle);
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+
+ mAms.finishSessionAsUser(
+ mMockAccountManagerResponse, // response
+ createEncryptedSessionBundle(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS),
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ 2); // fake user id
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_USER_RESTRICTED), anyString());
+ verify(mMockContext).startActivityAsUser(mIntentCaptor.capture(), eq(UserHandle.of(2)));
+
+ // verify the intent for default CantAddAccountActivity is sent.
+ Intent intent = mIntentCaptor.getValue();
+ assertEquals(intent.getComponent().getClassName(), CantAddAccountActivity.class.getName());
+ assertEquals(intent.getIntExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, 0),
+ AccountManager.ERROR_CODE_USER_RESTRICTED);
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserUserCannotModifyAccountWithDPM() throws Exception {
+ unlockSystemUser();
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+ when(mMockUserManager.getUserRestrictions(any(UserHandle.class))).thenReturn(bundle);
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+ LocalServices.addService(
+ DevicePolicyManagerInternal.class, mMockDevicePolicyManagerInternal);
+ when(mMockDevicePolicyManagerInternal.createUserRestrictionSupportIntent(
+ anyInt(), anyString())).thenReturn(new Intent());
+ when(mMockDevicePolicyManagerInternal.createShowAdminSupportIntent(
+ anyInt(), anyBoolean())).thenReturn(new Intent());
+
+ mAms.finishSessionAsUser(
+ mMockAccountManagerResponse, // response
+ createEncryptedSessionBundle(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS),
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ 2); // fake user id
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_USER_RESTRICTED), anyString());
+ verify(mMockContext).startActivityAsUser(any(Intent.class), eq(UserHandle.of(2)));
+ verify(mMockDevicePolicyManagerInternal).createUserRestrictionSupportIntent(
+ anyInt(), anyString());
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserWithBadSessionBundle() throws Exception {
+ unlockSystemUser();
+
+ Bundle badSessionBundle = new Bundle();
+ badSessionBundle.putString("any", "any");
+ mAms.finishSessionAsUser(
+ mMockAccountManagerResponse, // response
+ badSessionBundle, // sessionBundle
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ 2); // fake user id
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_BAD_REQUEST), anyString());
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserWithBadAccountType() throws Exception {
+ unlockSystemUser();
+
+ mAms.finishSessionAsUser(
+ mMockAccountManagerResponse, // response
+ createEncryptedSessionBundleWithNoAccountType(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS),
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ 2); // fake user id
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_BAD_ARGUMENTS), anyString());
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserUserCannotModifyAccountForTypeNoDPM() throws Exception {
+ unlockSystemUser();
+ when(mMockDevicePolicyManager.getAccountTypesWithManagementDisabledAsUser(anyInt()))
+ .thenReturn(new String[]{AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, "BBB"});
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+
+ mAms.finishSessionAsUser(
+ mMockAccountManagerResponse, // response
+ createEncryptedSessionBundle(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS),
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ 2); // fake user id
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE), anyString());
+ verify(mMockContext).startActivityAsUser(mIntentCaptor.capture(), eq(UserHandle.of(2)));
+
+ // verify the intent for default CantAddAccountActivity is sent.
+ Intent intent = mIntentCaptor.getValue();
+ assertEquals(intent.getComponent().getClassName(), CantAddAccountActivity.class.getName());
+ assertEquals(intent.getIntExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, 0),
+ AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE);
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserUserCannotModifyAccountForTypeWithDPM() throws Exception {
+ unlockSystemUser();
+ when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
+ mMockDevicePolicyManager);
+ when(mMockDevicePolicyManager.getAccountTypesWithManagementDisabledAsUser(anyInt()))
+ .thenReturn(new String[]{AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, "BBB"});
+
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+ LocalServices.addService(
+ DevicePolicyManagerInternal.class, mMockDevicePolicyManagerInternal);
+ when(mMockDevicePolicyManagerInternal.createUserRestrictionSupportIntent(
+ anyInt(), anyString())).thenReturn(new Intent());
+ when(mMockDevicePolicyManagerInternal.createShowAdminSupportIntent(
+ anyInt(), anyBoolean())).thenReturn(new Intent());
+
+ mAms.finishSessionAsUser(
+ mMockAccountManagerResponse, // response
+ createEncryptedSessionBundle(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS),
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ 2); // fake user id
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE), anyString());
+ verify(mMockContext).startActivityAsUser(any(Intent.class), eq(UserHandle.of(2)));
+ verify(mMockDevicePolicyManagerInternal).createShowAdminSupportIntent(
+ anyInt(), anyBoolean());
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserSuccess() throws Exception {
+ unlockSystemUser();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ mAms.finishSessionAsUser(
+ response, // response
+ createEncryptedSessionBundle(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS),
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ UserHandle.USER_SYSTEM);
+
+ waitForLatch(latch);
+ // Verify notification is cancelled
+ verify(mMockNotificationManager).cancelNotificationWithTag(
+ anyString(), anyString(), anyInt(), anyInt());
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
+ assertNotNull(sessionBundle);
+ // Assert that session bundle is decrypted and hence data is visible.
+ assertEquals(AccountManagerServiceTestFixtures.SESSION_DATA_VALUE_1,
+ sessionBundle.getString(AccountManagerServiceTestFixtures.SESSION_DATA_NAME_1));
+ // Assert finishSessionAsUser added calling uid and pid into the sessionBundle
+ assertTrue(sessionBundle.containsKey(AccountManager.KEY_CALLER_UID));
+ assertTrue(sessionBundle.containsKey(AccountManager.KEY_CALLER_PID));
+ // Assert App bundle data overrides sessionBundle data
+ assertEquals(sessionBundle.getString(
+ AccountManager.KEY_ANDROID_PACKAGE_NAME), "APCT.package");
+
+ // Verify response data
+ assertNull(result.getString(AccountManager.KEY_AUTHTOKEN, null));
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_NAME,
+ result.getString(AccountManager.KEY_ACCOUNT_NAME));
+ assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+ result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserReturnWithInvalidIntent() throws Exception {
+ unlockSystemUser();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.finishSessionAsUser(
+ response, // response
+ createEncryptedSessionBundle(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE),
+ true, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ UserHandle.USER_SYSTEM);
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_REMOTE_EXCEPTION), anyString());
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserReturnWithValidIntent() throws Exception {
+ unlockSystemUser();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.finishSessionAsUser(
+ response, // response
+ createEncryptedSessionBundle(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE),
+ true, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ UserHandle.USER_SYSTEM);
+
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+ assertNotNull(intent);
+ assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_RESULT));
+ assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK));
+ }
+
+ @SmallTest
+ public void testFinishSessionAsUserError() throws Exception {
+ unlockSystemUser();
+ Bundle sessionBundle = createEncryptedSessionBundleWithError(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_ERROR);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.finishSessionAsUser(
+ response, // response
+ sessionBundle,
+ false, // expectActivityLaunch
+ createAppBundle(), // appInfo
+ UserHandle.USER_SYSTEM);
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ }
+
+ @SmallTest
+ public void testIsCredentialsUpdatedSuggestedWithNullResponse() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.isCredentialsUpdateSuggested(
+ null, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ AccountManagerServiceTestFixtures.ACCOUNT_STATUS_TOKEN);
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testIsCredentialsUpdatedSuggestedWithNullAccount() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.isCredentialsUpdateSuggested(
+ mMockAccountManagerResponse,
+ null, // account
+ AccountManagerServiceTestFixtures.ACCOUNT_STATUS_TOKEN);
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testIsCredentialsUpdatedSuggestedWithEmptyStatusToken() throws Exception {
+ unlockSystemUser();
+ try {
+ mAms.isCredentialsUpdateSuggested(
+ mMockAccountManagerResponse,
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ null);
+ fail("IllegalArgumentException expected. But no exception was thrown.");
+ } catch (IllegalArgumentException e) {
+ } catch(Exception e){
+ fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+ }
+ }
+
+ @SmallTest
+ public void testIsCredentialsUpdatedSuggestedError() throws Exception {
+ unlockSystemUser();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.isCredentialsUpdateSuggested(
+ response,
+ AccountManagerServiceTestFixtures.ACCOUNT_ERROR,
+ AccountManagerServiceTestFixtures.ACCOUNT_STATUS_TOKEN);
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+ verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+ }
+
+ @SmallTest
+ public void testIsCredentialsUpdatedSuggestedSuccess() throws Exception {
+ unlockSystemUser();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+
+ mAms.isCredentialsUpdateSuggested(
+ response,
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ AccountManagerServiceTestFixtures.ACCOUNT_STATUS_TOKEN);
+
+ waitForLatch(latch);
+ verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+ Bundle result = mBundleCaptor.getValue();
+ boolean needUpdate = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+ assertTrue(needUpdate);
+ }
+
private void waitForLatch(CountDownLatch latch) {
try {
latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
- fail("should not throw an InterruptedException");
+ throw new IllegalStateException("Should not throw an InterruptedException", e);
}
}
+ private Bundle encryptBundleWithCryptoHelper(Bundle sessionBundle) {
+ Bundle encryptedBundle = null;
+ try {
+ CryptoHelper cryptoHelper = CryptoHelper.getInstance();
+ encryptedBundle = cryptoHelper.encryptBundle(sessionBundle);
+ } catch (GeneralSecurityException e) {
+ throw new IllegalStateException("Failed to encrypt session bundle.", e);
+ }
+ return encryptedBundle;
+ }
+
+ private Bundle createEncryptedSessionBundle(final String accountName) {
+ Bundle sessionBundle = new Bundle();
+ sessionBundle.putString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME, accountName);
+ sessionBundle.putString(
+ AccountManagerServiceTestFixtures.SESSION_DATA_NAME_1,
+ AccountManagerServiceTestFixtures.SESSION_DATA_VALUE_1);
+ sessionBundle.putString(AccountManager.KEY_ACCOUNT_TYPE,
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1);
+ sessionBundle.putString(AccountManager.KEY_ANDROID_PACKAGE_NAME, "APCT.session.package");
+ return encryptBundleWithCryptoHelper(sessionBundle);
+ }
+
+ private Bundle createEncryptedSessionBundleWithError(final String accountName) {
+ Bundle sessionBundle = new Bundle();
+ sessionBundle.putString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME, accountName);
+ sessionBundle.putString(
+ AccountManagerServiceTestFixtures.SESSION_DATA_NAME_1,
+ AccountManagerServiceTestFixtures.SESSION_DATA_VALUE_1);
+ sessionBundle.putString(AccountManager.KEY_ACCOUNT_TYPE,
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1);
+ sessionBundle.putString(AccountManager.KEY_ANDROID_PACKAGE_NAME, "APCT.session.package");
+ sessionBundle.putInt(
+ AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_INVALID_RESPONSE);
+ sessionBundle.putString(AccountManager.KEY_ERROR_MESSAGE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+ return encryptBundleWithCryptoHelper(sessionBundle);
+ }
+
+ private Bundle createEncryptedSessionBundleWithNoAccountType(final String accountName) {
+ Bundle sessionBundle = new Bundle();
+ sessionBundle.putString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME, accountName);
+ sessionBundle.putString(
+ AccountManagerServiceTestFixtures.SESSION_DATA_NAME_1,
+ AccountManagerServiceTestFixtures.SESSION_DATA_VALUE_1);
+ sessionBundle.putString(AccountManager.KEY_ANDROID_PACKAGE_NAME, "APCT.session.package");
+ return encryptBundleWithCryptoHelper(sessionBundle);
+ }
+
+ private Bundle createAppBundle() {
+ Bundle appBundle = new Bundle();
+ appBundle.putString(AccountManager.KEY_ANDROID_PACKAGE_NAME, "APCT.package");
+ return appBundle;
+ }
+
private Bundle createOptionsWithAccountName(final String accountName) {
Bundle sessionBundle = new Bundle();
sessionBundle.putString(
@@ -784,9 +1401,13 @@
static class TestInjector extends AccountManagerService.Injector {
private Context mRealContext;
- TestInjector(Context realContext, Context mockContext) {
+ private INotificationManager mMockNotificationManager;
+ TestInjector(Context realContext,
+ Context mockContext,
+ INotificationManager mockNotificationManager) {
super(mockContext);
mRealContext = realContext;
+ mMockNotificationManager = mockNotificationManager;
}
@Override
@@ -820,7 +1441,7 @@
@Override
INotificationManager getNotificationManager() {
- return mock(INotificationManager.class);
+ return mMockNotificationManager;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
index 0db11e0c..8ec6176 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
@@ -197,7 +197,8 @@
result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
result.putString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN,
AccountManagerServiceTestFixtures.ACCOUNT_STATUS_TOKEN);
- result.putString(AccountManager.KEY_PASSWORD, "doesn't matter");
+ result.putString(AccountManager.KEY_PASSWORD,
+ AccountManagerServiceTestFixtures.ACCOUNT_PASSWORD);
result.putString(AccountManager.KEY_AUTHTOKEN,
Integer.toString(mTokenCounter.incrementAndGet()));
} else if (accountName.equals(
@@ -243,6 +244,8 @@
Bundle result = new Bundle();
if (accountName.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) {
+ // add sessionBundle into result for verification purpose
+ result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
// fill bundle with a success result.
result.putString(AccountManager.KEY_ACCOUNT_NAME,
AccountManagerServiceTestFixtures.ACCOUNT_NAME);
@@ -288,7 +291,9 @@
} else {
// fill with error
fillResultWithError(
- result, AccountManager.ERROR_CODE_INVALID_RESPONSE, "Default Error Message");
+ result,
+ AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ AccountManagerServiceTestFixtures.ERROR_MESSAGE);
}
response.onResult(result);
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 167b33a..9835c88 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -566,6 +566,7 @@
protected Map<String, PackageInfo> mInjectedPackages;
protected Set<PackageWithUser> mUninstalledPackages;
+ protected Set<PackageWithUser> mEphemeralPackages;
protected Set<String> mSystemPackages;
protected PackageManager mMockPackageManager;
@@ -731,6 +732,7 @@
mUninstalledPackages = new HashSet<>();
mSystemPackages = new HashSet<>();
+ mEphemeralPackages = new HashSet<>();
mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files");
@@ -1034,6 +1036,9 @@
if (mUninstalledPackages.contains(PackageWithUser.of(userId, packageName))) {
ret.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED;
}
+ if (mEphemeralPackages.contains(PackageWithUser.of(userId, packageName))) {
+ ret.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_EPHEMERAL;
+ }
if (mSystemPackages.contains(packageName)) {
ret.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerPresubmitTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerPresubmitTest.java
index 5c552a2..e6b4540f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerPresubmitTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerPresubmitTest.java
@@ -20,7 +20,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
-import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.GlobalPresubmit;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -63,7 +63,7 @@
*/
@Test
@SmallTest
- @Presubmit
+ @GlobalPresubmit
public void testPrivAppPermissions() throws PackageManager.NameNotFoundException {
List<PackageInfo> installedPackages = mPackageManager
.getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES | GET_PERMISSIONS);
@@ -103,12 +103,10 @@
// if privapp permissions are enforced, platform permissions must be whitelisted
// in SystemConfig
if (platformPermission && RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
- assertTrue("Permission " + pName
- + " should be declared in "
- + "/etc/permissions/privapp-permissions-platform.xml "
- + "or privapp-permissions-<device>.xml file for package "
+ assertTrue("Permission " + pName + " should be declared in "
+ + "privapp-permissions-<category>.xml file for package "
+ packageName,
- privAppPermissions.contains(pName));
+ privAppPermissions != null && privAppPermissions.contains(pName));
}
assertTrue("Permission " + pName + " should be granted to " + packageName, granted);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index d25923c..562de414 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -46,6 +46,7 @@
import com.android.frameworks.servicestests.R;
import com.android.server.pm.ShortcutService.ConfigConstants;
+import com.android.server.pm.ShortcutUser.PackageWithUser;
import java.io.File;
import java.io.FileWriter;
@@ -2037,4 +2038,32 @@
assertFalse(mService.isUserUnlockedL(USER_0));
assertFalse(mService.isUserUnlockedL(USER_10));
}
+
+ public void testEphemeralApp() {
+ mRunningUsers.put(USER_10, true); // this test needs user 10.
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertWith(mManager.getDynamicShortcuts()).isEmpty();
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertWith(mManager.getDynamicShortcuts()).isEmpty();
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertWith(mManager.getDynamicShortcuts()).isEmpty();
+ });
+ // Make package 1 ephemeral.
+ mEphemeralPackages.add(PackageWithUser.of(USER_0, CALLING_PACKAGE_1));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertExpectException(IllegalStateException.class, "Ephemeral apps", () -> {
+ mManager.getDynamicShortcuts();
+ });
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertWith(mManager.getDynamicShortcuts()).isEmpty();
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertWith(mManager.getDynamicShortcuts()).isEmpty();
+ });
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index 83a61ca..67e78d4 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -19,25 +19,39 @@
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
+import android.webkit.UserPackage;
import android.webkit.WebViewProviderInfo;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
public class TestSystemImpl implements SystemInterface {
private String mUserProvider = null;
private final WebViewProviderInfo[] mPackageConfigs;
- HashMap<String, PackageInfo> mPackages = new HashMap();
+ List<Integer> mUsers = new ArrayList<>();
+ // Package -> [user, package]
+ Map<String, Map<Integer, PackageInfo>> mPackages = new HashMap();
private boolean mFallbackLogicEnabled;
private final int mNumRelros;
private final boolean mIsDebuggable;
private int mMultiProcessSetting;
+ public static final int PRIMARY_USER_ID = 0;
+
public TestSystemImpl(WebViewProviderInfo[] packageConfigs, boolean fallbackLogicEnabled,
int numRelros, boolean isDebuggable) {
mPackageConfigs = packageConfigs;
mFallbackLogicEnabled = fallbackLogicEnabled;
mNumRelros = numRelros;
mIsDebuggable = isDebuggable;
+ mUsers.add(PRIMARY_USER_ID);
+ }
+
+ public void addUser(int userId) {
+ mUsers.add(userId);
}
@Override
@@ -78,17 +92,20 @@
@Override
public void enablePackageForAllUsers(Context context, String packageName, boolean enable) {
- enablePackageForUser(packageName, enable, 0);
+ for(int userId : mUsers) {
+ enablePackageForUser(packageName, enable, userId);
+ }
}
@Override
public void enablePackageForUser(String packageName, boolean enable, int userId) {
- PackageInfo packageInfo = mPackages.get(packageName);
- if (packageInfo == null) {
+ Map<Integer, PackageInfo> userPackages = mPackages.get(packageName);
+ if (userPackages == null) {
throw new IllegalArgumentException("There is no package called " + packageName);
}
+ PackageInfo packageInfo = userPackages.get(userId);
packageInfo.applicationInfo.enabled = enable;
- setPackageInfo(packageInfo);
+ setPackageInfoForUser(userId, packageInfo);
}
@Override
@@ -97,23 +114,61 @@
@Override
public PackageInfo getPackageInfoForProvider(WebViewProviderInfo info) throws
NameNotFoundException {
- PackageInfo ret = mPackages.get(info.packageName);
+ Map<Integer, PackageInfo> userPackages = mPackages.get(info.packageName);
+ if (userPackages == null) throw new NameNotFoundException(info.packageName);
+ PackageInfo ret = userPackages.get(PRIMARY_USER_ID);
if (ret == null) throw new NameNotFoundException(info.packageName);
return ret;
}
- public void setPackageInfo(PackageInfo pi) {
- mPackages.put(pi.packageName, pi);
+ @Override
+ public List<UserPackage> getPackageInfoForProviderAllUsers(
+ Context context, WebViewProviderInfo info) {
+ Map<Integer, PackageInfo> userPackages = mPackages.get(info.packageName);
+ List<UserPackage> ret = new ArrayList();
+ // Loop over defined users, and find the corresponding package for each user.
+ for (int userId : mUsers) {
+ ret.add(new UserPackage(createUserInfo(userId),
+ userPackages == null ? null : userPackages.get(userId)));
+ }
+ return ret;
}
+ private static UserInfo createUserInfo(int userId) {
+ return new UserInfo(userId, "User nr. " + userId, 0 /* flags */);
+ }
+
+ /**
+ * Set package for primary user.
+ */
+ public void setPackageInfo(PackageInfo pi) {
+ setPackageInfoForUser(PRIMARY_USER_ID, pi);
+ }
+
+ public void setPackageInfoForUser(int userId, PackageInfo pi) {
+ if (!mUsers.contains(userId)) {
+ throw new IllegalArgumentException("User nr. " + userId + " doesn't exist");
+ }
+ if (!mPackages.containsKey(pi.packageName)) {
+ mPackages.put(pi.packageName, new HashMap<Integer, PackageInfo>());
+ }
+ mPackages.get(pi.packageName).put(userId, pi);
+ }
+
+ /**
+ * Removes the package {@param packageName} for the primary user.
+ */
public void removePackageInfo(String packageName) {
- mPackages.remove(packageName);
+ mPackages.get(packageName).remove(PRIMARY_USER_ID);
}
@Override
public int getFactoryPackageVersion(String packageName) throws NameNotFoundException {
PackageInfo pi = null;
- pi = mPackages.get(packageName);
+ Map<Integer, PackageInfo> userPackages = mPackages.get(packageName);
+ if (userPackages == null) throw new NameNotFoundException();
+
+ pi = userPackages.get(PRIMARY_USER_ID);
if (pi != null && pi.applicationInfo.isSystemApp()) {
return pi.applicationInfo.versionCode;
}
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index 0519448..33cedfa 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -92,9 +92,15 @@
}
private void setEnabledAndValidPackageInfos(WebViewProviderInfo[] providers) {
+ // Set package infos for the primary user (user 0).
+ setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, providers);
+ }
+
+ private void setEnabledAndValidPackageInfosForUser(int userId,
+ WebViewProviderInfo[] providers) {
for(WebViewProviderInfo wpi : providers) {
- mTestSystemImpl.setPackageInfo(createPackageInfo(wpi.packageName, true /* enabled */,
- true /* valid */, true /* installed */));
+ mTestSystemImpl.setPackageInfoForUser(userId, createPackageInfo(wpi.packageName,
+ true /* enabled */, true /* valid */, true /* installed */));
}
}
@@ -335,7 +341,7 @@
setEnabledAndValidPackageInfos(packages);
mWebViewUpdateServiceImpl.packageStateChanged(singlePackage,
- WebViewUpdateService.PACKAGE_ADDED, 0);
+ WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID);
checkPreparationPhasesForPackage(singlePackage, 1 /* number of finished preparations */);
assertEquals(singlePackage,
@@ -344,7 +350,7 @@
// Remove the package again
mTestSystemImpl.removePackageInfo(singlePackage);
mWebViewUpdateServiceImpl.packageStateChanged(singlePackage,
- WebViewUpdateService.PACKAGE_ADDED, 0);
+ WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID);
// Package removed - ensure our interface states that there is no package
response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
@@ -374,7 +380,7 @@
createPackageInfo(wpi.packageName, true /* enabled */, true /* valid */,
true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(wpi.packageName,
- WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+ WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID);
checkPreparationPhasesForPackage(wpi.packageName, 1);
}
@@ -429,16 +435,8 @@
new WebViewProviderInfo(firstPackage, "", true, false, null),
new WebViewProviderInfo(secondPackage, "", true, false, null)};
setupWithPackages(packages);
- if (settingsChange) {
- // Have all packages be enabled, so that we can change provider however we want to
- setEnabledAndValidPackageInfos(packages);
- } else {
- // Have all packages be disabled so that we can change one to enabled later
- for(WebViewProviderInfo wpi : packages) {
- mTestSystemImpl.setPackageInfo(createPackageInfo(wpi.packageName,
- false /* enabled */, true /* valid */, true /* installed */));
- }
- }
+ // Have all packages be enabled, so that we can change provider however we want to
+ setEnabledAndValidPackageInfos(packages);
CountDownLatch countdown = new CountDownLatch(1);
@@ -457,8 +455,12 @@
mWebViewUpdateServiceImpl.waitForAndGetProvider();
assertEquals(WebViewFactory.LIBLOAD_SUCCESS, threadResponse.status);
assertEquals(secondPackage, threadResponse.packageInfo.packageName);
- // Verify that we killed the first package
- Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(firstPackage));
+ // Verify that we killed the first package if we performed a settings change -
+ // otherwise we had to disable the first package, in which case its dependents
+ // should have been killed by the framework.
+ if (settingsChange) {
+ Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(firstPackage));
+ }
countdown.countDown();
}
}).start();
@@ -470,11 +472,21 @@
if (settingsChange) {
mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage);
} else {
- // Switch provider by enabling the second one
+ // Enable the second provider
mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */,
true /* valid */, true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(
- secondPackage, WebViewUpdateService.PACKAGE_CHANGED, 0);
+ secondPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID);
+
+ // Ensure we haven't changed package yet.
+ assertEquals(firstPackage,
+ mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName);
+
+ // Switch provider by disabling the first one
+ mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, false /* enabled */,
+ true /* valid */, true /* installed */));
+ mWebViewUpdateServiceImpl.packageStateChanged(
+ firstPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID);
}
mWebViewUpdateServiceImpl.notifyRelroCreationCompleted();
// first package done, should start on second
@@ -528,7 +540,7 @@
mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, true /* enabled */,
true /* valid */, true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(
- fallbackPackage, WebViewUpdateService.PACKAGE_CHANGED, 0);
+ fallbackPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID);
if (fallbackLogicEnabled) {
// Check that we have now disabled the fallback package twice
@@ -573,7 +585,7 @@
createPackageInfo(primaryPackage, true /* enabled */ , true /* valid */,
true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
- WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+ WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID);
// Verify fallback disabled, primary package used as provider, and fallback package killed
Mockito.verify(mTestSystemImpl).uninstallAndDisablePackageForAllUsers(
@@ -583,7 +595,31 @@
}
@Test
- public void testFallbackChangesEnabledState() {
+ public void testFallbackChangesEnabledStateSingleUser() {
+ for (PackageRemovalType removalType : REMOVAL_TYPES) {
+ checkFallbackChangesEnabledState(false /* multiUser */, removalType);
+ }
+ }
+
+ @Test
+ public void testFallbackChangesEnabledStateMultiUser() {
+ for (PackageRemovalType removalType : REMOVAL_TYPES) {
+ checkFallbackChangesEnabledState(true /* multiUser */, removalType);
+ }
+ }
+
+ /**
+ * Represents how to remove a package during a tests (disabling it / uninstalling it / hiding
+ * it).
+ */
+ private enum PackageRemovalType {
+ UNINSTALL, DISABLE, HIDE
+ }
+
+ private PackageRemovalType[] REMOVAL_TYPES = PackageRemovalType.class.getEnumConstants();
+
+ public void checkFallbackChangesEnabledState(boolean multiUser,
+ PackageRemovalType removalType) {
String primaryPackage = "primary";
String fallbackPackage = "fallback";
WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
@@ -592,46 +628,68 @@
new WebViewProviderInfo(
fallbackPackage, "", true /* default available */, true /* fallback */, null)};
setupWithPackages(packages, true /* fallbackLogicEnabled */);
- setEnabledAndValidPackageInfos(packages);
+ int secondaryUserId = 10;
+ int userIdToChangePackageFor = multiUser ? secondaryUserId : TestSystemImpl.PRIMARY_USER_ID;
+ if (multiUser) {
+ mTestSystemImpl.addUser(secondaryUserId);
+ setEnabledAndValidPackageInfosForUser(secondaryUserId, packages);
+ }
+ setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages);
runWebViewBootPreparationOnMainSync();
// Verify fallback disabled at boot when primary package enabled
- Mockito.verify(mTestSystemImpl).enablePackageForUser(
- Mockito.eq(fallbackPackage), Mockito.eq(false) /* enable */,
- Matchers.anyInt());
+ checkEnablePackageForUserCalled(fallbackPackage, false, multiUser
+ ? new int[] {TestSystemImpl.PRIMARY_USER_ID, secondaryUserId}
+ : new int[] {TestSystemImpl.PRIMARY_USER_ID}, 1 /* numUsages */);
checkPreparationPhasesForPackage(primaryPackage, 1);
+ boolean enabled = !(removalType == PackageRemovalType.DISABLE);
+ boolean installed = !(removalType == PackageRemovalType.UNINSTALL);
+ boolean hidden = (removalType == PackageRemovalType.HIDE);
// Disable primary package and ensure fallback becomes enabled and used
- mTestSystemImpl.setPackageInfo(
- createPackageInfo(primaryPackage, false /* enabled */, true /* valid */,
- true /* installed */));
+ mTestSystemImpl.setPackageInfoForUser(userIdToChangePackageFor,
+ createPackageInfo(primaryPackage, enabled /* enabled */, true /* valid */,
+ installed /* installed */, null /* signature */, 0 /* updateTime */,
+ hidden /* hidden */));
mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
- WebViewUpdateService.PACKAGE_CHANGED, 0);
+ removalType == PackageRemovalType.DISABLE
+ ? WebViewUpdateService.PACKAGE_CHANGED : WebViewUpdateService.PACKAGE_REMOVED,
+ userIdToChangePackageFor); // USER ID
- Mockito.verify(mTestSystemImpl).enablePackageForUser(
- Mockito.eq(fallbackPackage), Mockito.eq(true) /* enable */,
- Matchers.anyInt());
+ checkEnablePackageForUserCalled(fallbackPackage, true, multiUser
+ ? new int[] {TestSystemImpl.PRIMARY_USER_ID, secondaryUserId}
+ : new int[] {TestSystemImpl.PRIMARY_USER_ID}, 1 /* numUsages */);
checkPreparationPhasesForPackage(fallbackPackage, 1);
// Again enable primary package and verify primary is used and fallback becomes disabled
- mTestSystemImpl.setPackageInfo(
+ mTestSystemImpl.setPackageInfoForUser(userIdToChangePackageFor,
createPackageInfo(primaryPackage, true /* enabled */, true /* valid */,
true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
- WebViewUpdateService.PACKAGE_CHANGED, 0);
+ removalType == PackageRemovalType.DISABLE
+ ? WebViewUpdateService.PACKAGE_CHANGED : WebViewUpdateService.PACKAGE_ADDED,
+ userIdToChangePackageFor);
// Verify fallback is disabled a second time when primary package becomes enabled
- Mockito.verify(mTestSystemImpl, Mockito.times(2)).enablePackageForUser(
- Mockito.eq(fallbackPackage), Mockito.eq(false) /* enable */,
- Matchers.anyInt());
+ checkEnablePackageForUserCalled(fallbackPackage, false, multiUser
+ ? new int[] {TestSystemImpl.PRIMARY_USER_ID, secondaryUserId}
+ : new int[] {TestSystemImpl.PRIMARY_USER_ID}, 2 /* numUsages */);
checkPreparationPhasesForPackage(primaryPackage, 2);
}
+ private void checkEnablePackageForUserCalled(String packageName, boolean expectEnabled,
+ int[] userIds, int numUsages) {
+ for (int userId : userIds) {
+ Mockito.verify(mTestSystemImpl, Mockito.times(numUsages)).enablePackageForUser(
+ Mockito.eq(packageName), Mockito.eq(expectEnabled), Mockito.eq(userId));
+ }
+ }
+
@Test
public void testAddUserWhenFallbackLogicEnabled() {
checkAddingNewUser(true);
@@ -651,8 +709,10 @@
new WebViewProviderInfo(
fallbackPackage, "", true /* default available */, true /* fallback */, null)};
setupWithPackages(packages, fallbackLogicEnabled);
- setEnabledAndValidPackageInfos(packages);
+ setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages);
int newUser = 100;
+ mTestSystemImpl.addUser(newUser);
+ setEnabledAndValidPackageInfosForUser(newUser, packages);
mWebViewUpdateServiceImpl.handleNewUser(newUser);
if (fallbackLogicEnabled) {
// Verify fallback package becomes disabled for new user
@@ -668,6 +728,42 @@
}
/**
+ * Ensures that adding a new user for which the current WebView package is uninstalled causes a
+ * change of WebView provider.
+ */
+ @Test
+ public void testAddingNewUserWithUninstalledPackage() {
+ String primaryPackage = "primary";
+ String fallbackPackage = "fallback";
+ WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
+ new WebViewProviderInfo(
+ primaryPackage, "", true /* default available */, false /* fallback */, null),
+ new WebViewProviderInfo(
+ fallbackPackage, "", true /* default available */, true /* fallback */, null)};
+ setupWithPackages(packages, true /* fallbackLogicEnabled */);
+ setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages);
+ int newUser = 100;
+ mTestSystemImpl.addUser(newUser);
+ // Let the primary package be uninstalled for the new user
+ mTestSystemImpl.setPackageInfoForUser(newUser,
+ createPackageInfo(primaryPackage, true /* enabled */, true /* valid */,
+ false /* installed */));
+ mTestSystemImpl.setPackageInfoForUser(newUser,
+ createPackageInfo(fallbackPackage, false /* enabled */, true /* valid */,
+ true /* installed */));
+ mWebViewUpdateServiceImpl.handleNewUser(newUser);
+ // Verify fallback package doesn't become disabled for the primary user.
+ Mockito.verify(mTestSystemImpl, Mockito.never()).enablePackageForUser(
+ Mockito.anyObject(), Mockito.eq(false) /* enable */,
+ Mockito.eq(TestSystemImpl.PRIMARY_USER_ID) /* user */);
+ // Verify that we enable the fallback package for the secondary user.
+ Mockito.verify(mTestSystemImpl, Mockito.times(1)).enablePackageForUser(
+ Mockito.eq(fallbackPackage), Mockito.eq(true) /* enable */,
+ Mockito.eq(newUser) /* user */);
+ checkPreparationPhasesForPackage(fallbackPackage, 1 /* numRelros */);
+ }
+
+ /**
* Timing dependent test where we verify that the list of valid webview packages becoming empty
* at a certain point doesn't crash us or break our state.
*/
@@ -713,7 +809,7 @@
1 /* updateTime */ ));
mWebViewUpdateServiceImpl.packageStateChanged(firstPackage,
- WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+ WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID);
// Ensure we use firstPackage
checkPreparationPhasesForPackage(firstPackage, 2 /* second preparation for this package */);
@@ -742,14 +838,14 @@
mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */,
false /* valid */, true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(secondPackage,
- WebViewUpdateService.PACKAGE_ADDED, 0);
+ WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID);
checkPreparationPhasesForPackage(firstPackage, 2 /* second time for this package */);
// Now make the second package valid again and verify that it is used again
mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */,
true /* valid */, true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(secondPackage,
- WebViewUpdateService.PACKAGE_ADDED, 0);
+ WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID);
checkPreparationPhasesForPackage(secondPackage, 2 /* second time for this package */);
}
@@ -820,7 +916,7 @@
mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage);
} else {
mWebViewUpdateServiceImpl.packageStateChanged(secondPackage,
- WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+ WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID);
}
WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
@@ -831,7 +927,7 @@
true /* valid */, true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(secondPackage,
- WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+ WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID);
checkPreparationPhasesForPackage(secondPackage, 1);
@@ -863,11 +959,11 @@
createPackageInfo(firstPackage, true /* enabled */, false /* valid */,
true /* installed */));
mWebViewUpdateServiceImpl.packageStateChanged(firstPackage,
- WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+ WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID);
} else {
mTestSystemImpl.removePackageInfo(firstPackage);
mWebViewUpdateServiceImpl.packageStateChanged(firstPackage,
- WebViewUpdateService.PACKAGE_REMOVED, 0);
+ WebViewUpdateService.PACKAGE_REMOVED, TestSystemImpl.PRIMARY_USER_ID);
}
checkPreparationPhasesForPackage(secondPackage, 1);
@@ -1098,8 +1194,10 @@
}
}
- // Ensure that the update service uses an uninstalled package if that is the only package
- // available.
+ /**
+ * Ensure that the update service does use an uninstalled package when that is the only
+ * package available.
+ */
@Test
public void testWithSingleUninstalledPackage() {
String testPackageName = "test.package.name";
@@ -1113,21 +1211,32 @@
runWebViewBootPreparationOnMainSync();
checkPreparationPhasesForPackage(testPackageName, 1 /* first preparation phase */);
+ // TODO(gsennton) change this logic to use the code below when we have created a functional
+ // stub.
+ //Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged(
+ // Matchers.anyObject());
+ //WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
+ //assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status);
+ //assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage());
}
@Test
public void testNonhiddenPackageUserOverHidden() {
- checkVisiblePackageUserOverNonVisible(false /* true == uninstalled, false == hidden */);
+ checkVisiblePackageUserOverNonVisible(false /* multiUser*/, PackageRemovalType.HIDE);
+ checkVisiblePackageUserOverNonVisible(true /* multiUser*/, PackageRemovalType.HIDE);
}
@Test
public void testInstalledPackageUsedOverUninstalled() {
- checkVisiblePackageUserOverNonVisible(true /* true == uninstalled, false == hidden */);
+ checkVisiblePackageUserOverNonVisible(false /* multiUser*/, PackageRemovalType.UNINSTALL);
+ checkVisiblePackageUserOverNonVisible(true /* multiUser*/, PackageRemovalType.UNINSTALL);
}
- private void checkVisiblePackageUserOverNonVisible(boolean uninstalledNotHidden) {
- boolean testUninstalled = uninstalledNotHidden;
- boolean testHidden = !uninstalledNotHidden;
+ private void checkVisiblePackageUserOverNonVisible(boolean multiUser,
+ PackageRemovalType removalType) {
+ assert removalType != PackageRemovalType.DISABLE;
+ boolean testUninstalled = removalType == PackageRemovalType.UNINSTALL;
+ boolean testHidden = removalType == PackageRemovalType.HIDE;
String installedPackage = "installedPackage";
String uninstalledPackage = "uninstalledPackage";
WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] {
@@ -1137,11 +1246,25 @@
false /* fallback */, null)};
setupWithPackages(webviewPackages, true /* fallback logic enabled */, 1 /* numRelros */);
- mTestSystemImpl.setPackageInfo(createPackageInfo(installedPackage, true /* enabled */,
- true /* valid */, true /* installed */));
- mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */,
+ int secondaryUserId = 5;
+ if (multiUser) {
+ mTestSystemImpl.addUser(secondaryUserId);
+ // Install all packages for the primary user.
+ setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, webviewPackages);
+ mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(
+ installedPackage, true /* enabled */, true /* valid */, true /* installed */));
+ // Hide or uninstall the primary package for the second user
+ mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */,
true /* valid */, (testUninstalled ? false : true) /* installed */,
null /* signatures */, 0 /* updateTime */, (testHidden ? true : false)));
+ } else {
+ mTestSystemImpl.setPackageInfo(createPackageInfo(installedPackage, true /* enabled */,
+ true /* valid */, true /* installed */));
+ // Hide or uninstall the primary package
+ mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */,
+ true /* valid */, (testUninstalled ? false : true) /* installed */,
+ null /* signatures */, 0 /* updateTime */, (testHidden ? true : false)));
+ }
runWebViewBootPreparationOnMainSync();
@@ -1160,9 +1283,7 @@
}
/**
- * Ensure that we won't prioritize an uninstalled (or hidden) package even if it is user-chosen,
- * and that an uninstalled (or hidden) package is not considered valid (in the
- * getValidWebViewPackages() API).
+ * Ensure that we won't prioritize an uninstalled (or hidden) package even if it is user-chosen.
*/
private void checkCantSwitchToNonVisiblePackage(boolean uninstalledNotHidden) {
boolean testUninstalled = uninstalledNotHidden;
@@ -1176,27 +1297,31 @@
false /* fallback */, null)};
setupWithPackages(webviewPackages, true /* fallback logic enabled */, 1 /* numRelros */);
- mTestSystemImpl.setPackageInfo(createPackageInfo(installedPackage, true /* enabled */,
- true /* valid */, true /* installed */));
- mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */,
- true /* valid */, (testUninstalled ? false : true) /* installed */,
- null /* signatures */, 0 /* updateTime */,
- (testHidden ? true : false) /* hidden */));
+ int secondaryUserId = 412;
+ mTestSystemImpl.addUser(secondaryUserId);
+
+ // Let all packages be installed and enabled for the primary user.
+ setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, webviewPackages);
+ // Only uninstall the 'uninstalled package' for the secondary user.
+ mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(installedPackage,
+ true /* enabled */, true /* valid */, true /* installed */));
+ mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(uninstalledPackage,
+ true /* enabled */, true /* valid */, !testUninstalled /* installed */,
+ null /* signatures */, 0 /* updateTime */, testHidden /* hidden */));
runWebViewBootPreparationOnMainSync();
checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */);
- // Ensure that only the installed package is considered valid
- WebViewProviderInfo[] validPackages = mWebViewUpdateServiceImpl.getValidWebViewPackages();
- assertEquals(1, validPackages.length);
- assertEquals(installedPackage, validPackages[0].packageName);
-
// ensure that we don't switch to the uninstalled package (it will be used if it becomes
// installed later)
assertEquals(installedPackage,
mWebViewUpdateServiceImpl.changeProviderAndSetting(uninstalledPackage));
+ // Ensure both packages are considered valid.
+ assertEquals(2, mWebViewUpdateServiceImpl.getValidWebViewPackages().length);
+
+
// We should only have called onWebViewProviderChanged once (before calling
// changeProviderAndSetting
Mockito.verify(mTestSystemImpl, Mockito.times(1)).onWebViewProviderChanged(
@@ -1227,12 +1352,16 @@
false /* fallback */, null)};
setupWithPackages(webviewPackages, true /* fallback logic enabled */, 1 /* numRelros */);
- mTestSystemImpl.setPackageInfo(createPackageInfo(installedPackage, true /* enabled */,
- true /* valid */, true /* installed */));
- mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */,
- true /* valid */, (testUninstalled ? false : true) /* installed */,
- null /* signatures */, 0 /* updateTime */,
- (testHidden ? true : false) /* hidden */));
+ int secondaryUserId = 4;
+ mTestSystemImpl.addUser(secondaryUserId);
+
+ setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, webviewPackages);
+ mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(installedPackage,
+ true /* enabled */, true /* valid */, true /* installed */));
+ mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(uninstalledPackage,
+ true /* enabled */, true /* valid */,
+ (testUninstalled ? false : true) /* installed */, null /* signatures */,
+ 0 /* updateTime */, (testHidden ? true : false) /* hidden */));
// Start with the setting pointing to the uninstalled package
mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
@@ -1242,12 +1371,21 @@
checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */);
}
+ @Test
+ public void testFallbackEnabledIfPrimaryUninstalledSingleUser() {
+ checkFallbackEnabledIfPrimaryUninstalled(false /* multiUser */);
+ }
+
+ @Test
+ public void testFallbackEnabledIfPrimaryUninstalledMultiUser() {
+ checkFallbackEnabledIfPrimaryUninstalled(true /* multiUser */);
+ }
+
/**
- * Ensures that fallback becomes enabled if the primary package is uninstalled for the current
+ * Ensures that fallback becomes enabled at boot if the primary package is uninstalled for some
* user.
*/
- @Test
- public void testFallbackEnabledIfPrimaryUninstalled() {
+ private void checkFallbackEnabledIfPrimaryUninstalled(boolean multiUser) {
String primaryPackage = "primary";
String fallbackPackage = "fallback";
WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
@@ -1256,10 +1394,24 @@
new WebViewProviderInfo(
fallbackPackage, "", true /* default available */, true /* fallback */, null)};
setupWithPackages(packages, true /* fallback logic enabled */);
- mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
+ int secondaryUserId = 5;
+ if (multiUser) {
+ mTestSystemImpl.addUser(secondaryUserId);
+ // Install all packages for the primary user.
+ setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages);
+ // Only install fallback package for secondary user.
+ mTestSystemImpl.setPackageInfoForUser(secondaryUserId,
+ createPackageInfo(primaryPackage, true /* enabled */,
+ true /* valid */, false /* installed */));
+ mTestSystemImpl.setPackageInfoForUser(secondaryUserId,
+ createPackageInfo(fallbackPackage, false /* enabled */,
+ true /* valid */, true /* installed */));
+ } else {
+ mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
true /* valid */, false /* installed */));
- mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, true /* enabled */,
+ mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, false /* enabled */,
true /* valid */, true /* installed */));
+ }
runWebViewBootPreparationOnMainSync();
// Verify that we enable the fallback package
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
index 26accc3..2af4163 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
@@ -18,12 +18,10 @@
import org.junit.Test;
-import android.os.Binder;
-import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.view.IApplicationToken;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -74,6 +72,67 @@
controller.removeContainer(sDisplayContent.getDisplayId());
// Assert orientation is unspecified to after container is removed.
assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, controller.getOrientation());
+
+ // Reset display frozen state
+ sWm.mDisplayFrozen = false;
+ }
+
+ private void assertHasStartingWindow(AppWindowToken atoken) {
+ assertNotNull(atoken.startingSurface);
+ assertNotNull(atoken.startingData);
+ assertNotNull(atoken.startingWindow);
+ }
+
+ private void assertNoStartingWindow(AppWindowToken atoken) {
+ assertNull(atoken.startingSurface);
+ assertNull(atoken.startingWindow);
+ assertNull(atoken.startingData);
+ }
+
+ @Test
+ public void testCreateRemoveStartingWindow() throws Exception {
+ final TestAppWindowContainerController controller = createAppWindowController();
+ controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false);
+ waitUntilHandlerIdle();
+ final AppWindowToken atoken = controller.getAppWindowToken();
+ assertHasStartingWindow(atoken);
+ controller.removeStartingWindow();
+ waitUntilHandlerIdle();
+ assertNoStartingWindow(atoken);
+ }
+
+ @Test
+ public void testTransferStartingWindow() throws Exception {
+ final TestAppWindowContainerController controller1 = createAppWindowController();
+ final TestAppWindowContainerController controller2 = createAppWindowController();
+ controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false);
+ waitUntilHandlerIdle();
+ controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(),
+ true, true, false);
+ waitUntilHandlerIdle();
+ assertNoStartingWindow(controller1.getAppWindowToken());
+ assertHasStartingWindow(controller2.getAppWindowToken());
+ }
+
+ @Test
+ public void testTransferStartingWindowWhileCreating() throws Exception {
+ final TestAppWindowContainerController controller1 = createAppWindowController();
+ final TestAppWindowContainerController controller2 = createAppWindowController();
+ sPolicy.setRunnableWhenAddingSplashScreen(() -> {
+
+ // Surprise, ...! Transfer window in the middle of the creation flow.
+ controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(),
+ true, true, false);
+ });
+ controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false);
+ waitUntilHandlerIdle();
+ assertNoStartingWindow(controller1.getAppWindowToken());
+ assertHasStartingWindow(controller2.getAppWindowToken());
}
private TestAppWindowContainerController createAppWindowController() {
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
index 48d4770..e1a22d9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
@@ -18,6 +18,7 @@
import static android.graphics.GraphicBuffer.USAGE_HW_TEXTURE;
import static android.graphics.GraphicBuffer.USAGE_SW_READ_NEVER;
+import static android.graphics.GraphicBuffer.USAGE_SW_READ_RARELY;
import static android.graphics.GraphicBuffer.USAGE_SW_WRITE_NEVER;
import static android.graphics.GraphicBuffer.create;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
@@ -26,13 +27,18 @@
import android.app.ActivityManager.TaskSnapshot;
import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.GraphicBuffer;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.os.Debug;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,21 +50,75 @@
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
-public class TaskSnapshotCacheTest extends WindowTestsBase {
+public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
- @Test
- public void testCleanCache() throws Exception {
- TaskSnapshotCache snapshotCache = new TaskSnapshotCache();
- final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
- snapshotCache.putSnapshot(window.getTask(), createSnapshot());
- assertNotNull(snapshotCache.getSnapshot(window.getTask()));
- snapshotCache.cleanCache(window.mAppToken);
- assertNull(snapshotCache.getSnapshot(window.getTask()));
+ private TaskSnapshotCache mCache;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mCache = new TaskSnapshotCache(sWm, mLoader);
}
- private TaskSnapshot createSnapshot() {
- GraphicBuffer buffer = create(1, 1, PixelFormat.RGBA_8888,
- USAGE_HW_TEXTURE | USAGE_SW_WRITE_NEVER | USAGE_SW_READ_NEVER);
- return new TaskSnapshot(buffer, Configuration.ORIENTATION_PORTRAIT, new Rect());
+ @Test
+ public void testAppRemoved() throws Exception {
+ final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ mCache.putSnapshot(window.getTask(), createSnapshot());
+ assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+ false /* restoreFromDisk */));
+ mCache.onAppRemoved(window.mAppToken);
+ assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+ false /* restoreFromDisk */));
+ }
+
+ @Test
+ public void testAppDied() throws Exception {
+ final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ mCache.putSnapshot(window.getTask(), createSnapshot());
+ assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+ false /* restoreFromDisk */));
+ mCache.onAppDied(window.mAppToken);
+
+ // Should still be in the retrieval cache.
+ assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+ false /* restoreFromDisk */));
+
+ // Trash retrieval cache.
+ for (int i = 0; i < 20; i++) {
+ mCache.putSnapshot(createWindow(null, FIRST_APPLICATION_WINDOW, "window").getTask(),
+ createSnapshot());
+ }
+
+ // Should not be in cache anymore
+ assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+ false /* restoreFromDisk */));
+ }
+
+ @Test
+ public void testTaskRemoved() throws Exception {
+ final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ mCache.putSnapshot(window.getTask(), createSnapshot());
+ assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+ false /* restoreFromDisk */));
+ mCache.onTaskRemoved(window.getTask().mTaskId);
+ assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
+ false /* restoreFromDisk */));
+ }
+
+ @Test
+ public void testRestoreFromDisk() throws Exception {
+ final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot());
+ mPersister.waitForQueueEmpty();
+ assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+ false /* restoreFromDisk */));
+
+ // Load it from disk
+ assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+ true /* restoreFromDisk */));
+
+ // Make sure it's in the cache now.
+ assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+ false /* restoreFromDisk */));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index 8d6d2da..dc008b5 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -16,8 +16,6 @@
package com.android.server.wm;
-import static android.graphics.GraphicBuffer.USAGE_HW_TEXTURE;
-import static android.graphics.GraphicBuffer.USAGE_SW_READ_RARELY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -25,18 +23,11 @@
import static org.junit.Assert.fail;
import android.app.ActivityManager.TaskSnapshot;
-import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.GraphicBuffer;
-import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.SystemClock;
-import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
-import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
import android.util.ArraySet;
@@ -44,11 +35,6 @@
import com.android.internal.util.Predicate;
import com.android.server.wm.TaskSnapshotPersister.RemoveObsoleteFilesQueueItem;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -62,42 +48,11 @@
@MediumTest
@Presubmit
@RunWith(AndroidJUnit4.class)
-public class TaskSnapshotPersisterLoaderTest {
+public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBase {
private static final String TEST_USER_NAME = "TaskSnapshotPersisterTest User";
private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40);
- private TaskSnapshotPersister mPersister;
- private TaskSnapshotLoader mLoader;
- private static int sTestUserId;
- private static File sFilesDir;
- private static UserManager sUserManager;
-
- @BeforeClass
- public static void setUpUser() {
- sUserManager = UserManager.get(InstrumentationRegistry.getContext());
- sTestUserId = createUser(TEST_USER_NAME, 0);
- sFilesDir = InstrumentationRegistry.getContext().getFilesDir();
- }
-
- @AfterClass
- public static void tearDownUser() {
- removeUser(sTestUserId);
- }
-
- @Before
- public void setUp() throws Exception {
- mPersister = new TaskSnapshotPersister(
- userId -> sFilesDir);
- mLoader = new TaskSnapshotLoader(mPersister);
- mPersister.start();
- }
-
- @After
- public void tearDown() throws Exception {
- cleanDirectory();
- }
-
@Test
public void testPersistAndLoadSnapshot() {
mPersister.persistSnapshot(1 , sTestUserId, createSnapshot());
@@ -188,35 +143,4 @@
new File(sFilesDir.getPath() + "/snapshots/2.png") };
assertTrueForFiles(existsFiles, File::exists, " must exist");
}
-
- private TaskSnapshot createSnapshot() {
- GraphicBuffer buffer = GraphicBuffer.create(100, 100, PixelFormat.RGBA_8888,
- USAGE_HW_TEXTURE | USAGE_SW_READ_RARELY | USAGE_SW_READ_RARELY);
- Canvas c = buffer.lockCanvas();
- c.drawColor(Color.RED);
- buffer.unlockCanvasAndPost(c);
- return new TaskSnapshot(buffer, Configuration.ORIENTATION_PORTRAIT, TEST_INSETS);
- }
-
- private static int createUser(String name, int flags) {
- UserInfo user = sUserManager.createUser(name, flags);
- if (user == null) {
- Assert.fail("Error while creating the test user: " + TEST_USER_NAME);
- }
- return user.id;
- }
-
- private static void removeUser(int userId) {
- if (!sUserManager.removeUser(userId)) {
- Assert.fail("Error while removing the test user: " + TEST_USER_NAME);
- }
- }
-
- private void cleanDirectory() {
- for (File file : new File(sFilesDir, "snapshots").listFiles()) {
- if (!file.isDirectory()) {
- file.delete();
- }
- }
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
new file mode 100644
index 0000000..6fc6edb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.graphics.GraphicBuffer.USAGE_HW_TEXTURE;
+import static android.graphics.GraphicBuffer.USAGE_SW_READ_RARELY;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.content.pm.UserInfo;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+import java.io.File;
+
+/**
+ * Base class for tests that use a {@link TaskSnapshotPersister}.
+ */
+class TaskSnapshotPersisterTestBase extends WindowTestsBase {
+
+ private static final String TEST_USER_NAME = "TaskSnapshotPersisterTest User";
+ private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40);
+
+ TaskSnapshotPersister mPersister;
+ TaskSnapshotLoader mLoader;
+ static int sTestUserId;
+ static File sFilesDir;
+ private static UserManager sUserManager;
+
+ @BeforeClass
+ public static void setUpUser() {
+ sUserManager = UserManager.get(InstrumentationRegistry.getContext());
+ sTestUserId = createUser(TEST_USER_NAME, 0);
+ sFilesDir = InstrumentationRegistry.getContext().getFilesDir();
+ }
+
+ @AfterClass
+ public static void tearDownUser() {
+ removeUser(sTestUserId);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mPersister = new TaskSnapshotPersister(
+ userId -> sFilesDir);
+ mLoader = new TaskSnapshotLoader(mPersister);
+ mPersister.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ cleanDirectory();
+ }
+
+ private static int createUser(String name, int flags) {
+ UserInfo user = sUserManager.createUser(name, flags);
+ if (user == null) {
+ Assert.fail("Error while creating the test user: " + TEST_USER_NAME);
+ }
+ return user.id;
+ }
+
+ private static void removeUser(int userId) {
+ if (!sUserManager.removeUser(userId)) {
+ Assert.fail("Error while removing the test user: " + TEST_USER_NAME);
+ }
+ }
+
+ private void cleanDirectory() {
+ for (File file : new File(sFilesDir, "snapshots").listFiles()) {
+ if (!file.isDirectory()) {
+ file.delete();
+ }
+ }
+ }
+
+ TaskSnapshot createSnapshot() {
+ GraphicBuffer buffer = GraphicBuffer.create(100, 100, PixelFormat.RGBA_8888,
+ USAGE_HW_TEXTURE | USAGE_SW_READ_RARELY | USAGE_SW_READ_RARELY);
+ Canvas c = buffer.lockCanvas();
+ c.drawColor(Color.RED);
+ buffer.unlockCanvasAndPost(c);
+ return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
new file mode 100644
index 0000000..aab75ee
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link TaskSnapshotSurface}.
+ *
+ * runtest frameworks-services -c com.android.server.wm.TaskSnapshotSurfaceTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskSnapshotSurfaceTest extends WindowTestsBase {
+
+ private TaskSnapshotSurface mSurface;
+
+ @Before
+ public void setUp() {
+ mSurface = new TaskSnapshotSurface(null, null, null, Color.WHITE);
+ }
+
+ @Test
+ public void fillEmptyBackground_fillHorizontally() throws Exception {
+ final Canvas mockCanvas = mock(Canvas.class);
+ when(mockCanvas.getWidth()).thenReturn(200);
+ when(mockCanvas.getHeight()).thenReturn(100);
+ final Bitmap b = Bitmap.createBitmap(100, 200, Config.ARGB_8888);
+ mSurface.fillEmptyBackground(mockCanvas, b);
+ verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
+ }
+
+ @Test
+ public void fillEmptyBackground_fillVertically() throws Exception {
+ final Canvas mockCanvas = mock(Canvas.class);
+ when(mockCanvas.getWidth()).thenReturn(100);
+ when(mockCanvas.getHeight()).thenReturn(200);
+ final Bitmap b = Bitmap.createBitmap(200, 100, Config.ARGB_8888);
+ mSurface.fillEmptyBackground(mockCanvas, b);
+ verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any());
+ }
+
+ @Test
+ public void fillEmptyBackground_fillBoth() throws Exception {
+ final Canvas mockCanvas = mock(Canvas.class);
+ when(mockCanvas.getWidth()).thenReturn(200);
+ when(mockCanvas.getHeight()).thenReturn(200);
+ final Bitmap b = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+ mSurface.fillEmptyBackground(mockCanvas, b);
+ verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
+ verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any());
+ }
+
+ @Test
+ public void fillEmptyBackground_dontFill_sameSize() throws Exception {
+ final Canvas mockCanvas = mock(Canvas.class);
+ when(mockCanvas.getWidth()).thenReturn(100);
+ when(mockCanvas.getHeight()).thenReturn(100);
+ final Bitmap b = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+ mSurface.fillEmptyBackground(mockCanvas, b);
+ verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
+ }
+
+ @Test
+ public void fillEmptyBackground_dontFill_bitmapLarger() throws Exception {
+ final Canvas mockCanvas = mock(Canvas.class);
+ when(mockCanvas.getWidth()).thenReturn(100);
+ when(mockCanvas.getHeight()).thenReturn(100);
+ final Bitmap b = Bitmap.createBitmap(200, 200, Config.ARGB_8888);
+ mSurface.fillEmptyBackground(mockCanvas, b);
+ verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 269b719..ec429a0 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -24,6 +24,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
import static android.view.WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY;
@@ -74,6 +75,7 @@
import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicy;
import android.view.animation.Animation;
import android.os.PowerManagerInternal;
@@ -92,6 +94,8 @@
int rotationToReport = 0;
+ private Runnable mRunnableWhenAddingSplashScreen;
+
static synchronized WindowManagerService getWindowManagerService(Context context) {
if (sWm == null) {
// We only want to do this once for the test process as we don't want WM to try to
@@ -318,11 +322,36 @@
return false;
}
+ /**
+ * Sets a runnable to run when adding a splash screen which gets executed after the window has
+ * been added but before returning the surface.
+ */
+ void setRunnableWhenAddingSplashScreen(Runnable r) {
+ mRunnableWhenAddingSplashScreen = r;
+ }
+
@Override
public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
int logo, int windowFlags, Configuration overrideConfig, 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 @@
@Override
public boolean isScreenOn() {
- return false;
+ return true;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
index 6129198..772bfb4 100644
--- a/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -45,20 +45,18 @@
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
-public class UnknownAppVisibilityControllerTest {
+public class UnknownAppVisibilityControllerTest extends WindowTestsBase {
private WindowManagerService mWm;
- private @Mock ActivityManagerInternal mAm;
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
+ super.setUp();
final Context context = InstrumentationRegistry.getTargetContext();
- LocalServices.addService(ActivityManagerInternal.class, mAm);
doAnswer((InvocationOnMock invocationOnMock) -> {
invocationOnMock.getArgumentAt(0, Runnable.class).run();
return null;
- }).when(mAm).notifyKeyguardFlagsChanged(any());
+ }).when(sMockAm).notifyKeyguardFlagsChanged(any());
mWm = TestWindowManagerPolicy.getWindowManagerService(context);
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
index 466da94..085cfd8 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -20,6 +20,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import android.app.ActivityManager.TaskDescription;
import android.content.Context;
import android.graphics.Rect;
import android.os.Binder;
@@ -76,7 +77,7 @@
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, new TaskDescription(), null);
mBounds = bounds;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 813d263..ae344dd 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 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 @@
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 @@
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 @@
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 @@
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 @@
}
/** 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, 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 @@
}
/** 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);
@@ -218,7 +240,7 @@
Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode,
boolean homeTask, TaskWindowContainerController controller) {
super(taskId, stack, userId, service, bounds, overrideConfig, isOnTopLauncher,
- resizeMode, homeTask, controller);
+ resizeMode, homeTask, new TaskDescription(), controller);
}
boolean shouldDeferRemoval() {
@@ -249,13 +271,14 @@
TestTaskWindowContainerController(int stackId) {
super(sNextTaskId++, snapshot -> {}, stackId, 0 /* userId */, null /* bounds */,
EMPTY /* overrideConfig*/, RESIZE_MODE_UNRESIZEABLE, false /* homeTask*/,
- false /* isOnTopLauncher */, true /* toTop*/, true /* showForAllUsers */);
+ 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) {
+ boolean isOnTopLauncher, TaskDescription taskDescription) {
return new TestTask(taskId, stack, userId, mService, bounds, overrideConfig,
isOnTopLauncher, resizeMode, homeTask, this);
}
@@ -279,6 +302,10 @@
0 /* inputDispatchingTimeoutNanos */, sWm);
mToken = token;
}
+
+ AppWindowToken getAppWindowToken() {
+ return (AppWindowToken) sDisplayContent.getWindowToken(mToken.asBinder());
+ }
}
class TestIApplicationToken implements IApplicationToken {
@@ -295,7 +322,7 @@
boolean resizeReported;
TestWindowState(WindowManager.LayoutParams attrs, WindowToken token) {
- super(sWm, mMockSession, mIWindow, token, null, OP_NONE, 0, attrs, 0, 0);
+ super(sWm, sMockSession, sIWindow, token, null, OP_NONE, 0, attrs, 0, 0);
}
@Override
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 68765b6..6826975 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -37,6 +37,7 @@
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
import com.android.server.SystemService;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
@@ -46,8 +47,6 @@
private static final String PROP_VERIFY_STORAGE = "fw.verify_storage";
- // TODO: pivot all methods to manual mode when quota isn't supported
-
public static class Lifecycle extends SystemService {
private StorageStatsService mService;
@@ -71,11 +70,11 @@
private final Installer mInstaller;
public StorageStatsService(Context context) {
- mContext = context;
- mAppOps = context.getSystemService(AppOpsManager.class);
- mUser = context.getSystemService(UserManager.class);
- mPackage = context.getSystemService(PackageManager.class);
- mStorage = context.getSystemService(StorageManager.class);
+ mContext = Preconditions.checkNotNull(context);
+ mAppOps = Preconditions.checkNotNull(context.getSystemService(AppOpsManager.class));
+ mUser = Preconditions.checkNotNull(context.getSystemService(UserManager.class));
+ mPackage = Preconditions.checkNotNull(context.getPackageManager());
+ mStorage = Preconditions.checkNotNull(context.getSystemService(StorageManager.class));
mInstaller = new Installer(context);
mInstaller.onStart();
@@ -107,7 +106,7 @@
case AppOpsManager.MODE_ALLOWED:
return;
case AppOpsManager.MODE_DEFAULT:
- mContext.enforceCallingPermission(
+ mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.PACKAGE_USAGE_STATS, TAG);
return;
default:
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index db7a31a..fbbe636 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -656,6 +656,7 @@
// send a sticky broadcast containing current USB state
Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(UsbManager.USB_CONNECTED, mConnected);
intent.putExtra(UsbManager.USB_HOST_CONNECTED, mHostConnected);
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 00e8f9f..ba7b6a1 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -58,12 +58,15 @@
* Input: get*Extra field {@link #EXTRA_PHONE_ACCOUNT_HANDLE} contains the component name of the
* {@link android.telecom.ConnectionService} that Telecom should bind to. Telecom will then
* ask the connection service for more information about the call prior to showing any UI.
+ *
+ * @deprecated Use {@link #addNewIncomingCall} instead.
*/
public static final String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
/**
* Similar to {@link #ACTION_INCOMING_CALL}, but is used only by Telephony to add a new
* sim-initiated MO call for carrier testing.
+ * @deprecated Use {@link #addNewUnknownCall} instead.
* @hide
*/
public static final String ACTION_NEW_UNKNOWN_CALL = "android.telecom.action.NEW_UNKNOWN_CALL";
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index c0a86d6..a853d5c 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -598,6 +598,38 @@
"vvm_cellular_data_required_bool";
/**
+ * The default OMTP visual voicemail client prefix to use. Defaulted to "//VVM"
+ */
+ public static final String KEY_VVM_CLIENT_PREFIX_STRING =
+ "vvm_client_prefix_string";
+
+ /**
+ * Whether to use SSL to connect to the visual voicemail IMAP server. Defaulted to false.
+ */
+ public static final String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
+
+ /**
+ * A set of capabilities that should not be used even if it is reported by the visual voicemail
+ * IMAP CAPABILITY command.
+ */
+ public static final String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY =
+ "vvm_disabled_capabilities_string_array";
+
+ /**
+ * Whether legacy mode should be used when the visual voicemail client is disabled.
+ *
+ * <p>Legacy mode is a mode that on the carrier side visual voicemail is still activated, but on
+ * the client side all network operations are disabled. SMSs are still monitored so a new
+ * message SYNC SMS will be translated to show a message waiting indicator, like traditional
+ * voicemails.
+ *
+ * <p>This is for carriers that does not support VVM deactivation so voicemail can continue to
+ * function without the data cost.
+ */
+ public static final String KEY_VVM_LEGACY_MODE_ENABLED_BOOL =
+ "vvm_legacy_mode_enabled_bool";
+
+ /**
* Whether to prefetch audio data on new voicemail arrival, defaulted to true.
*/
public static final String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
@@ -605,10 +637,20 @@
/**
* The package name of the carrier's visual voicemail app to ensure that dialer visual voicemail
* and carrier visual voicemail are not active at the same time.
+ *
+ * @deprecated use {@link #KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY}.
*/
+ @Deprecated
public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
/**
+ * A list of the carrier's visual voicemail app package names to ensure that dialer visual
+ * voicemail and carrier visual voicemail are not active at the same time.
+ */
+ public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY =
+ "carrier_vvm_package_name_string_array";
+
+ /**
* Flag specifying whether ICCID is showed in SIM Status screen, default to false.
*/
public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
@@ -1308,8 +1350,13 @@
sDefaults.putInt(KEY_VVM_PORT_NUMBER_INT, 0);
sDefaults.putString(KEY_VVM_TYPE_STRING, "");
sDefaults.putBoolean(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL, false);
+ sDefaults.putString(KEY_VVM_CLIENT_PREFIX_STRING,"//VVM");
+ sDefaults.putBoolean(KEY_VVM_SSL_ENABLED_BOOL,false);
+ sDefaults.putStringArray(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY, null);
+ sDefaults.putBoolean(KEY_VVM_LEGACY_MODE_ENABLED_BOOL,false);
sDefaults.putBoolean(KEY_VVM_PREFETCH_BOOL, true);
sDefaults.putString(KEY_CARRIER_VVM_PACKAGE_NAME_STRING, "");
+ sDefaults.putStringArray(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL, false);
sDefaults.putBoolean(KEY_CI_ACTION_ON_SYS_UPDATE_BOOL, false);
sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING, "");
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a3f7c18..b28627b 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4791,6 +4791,20 @@
}
}
+ /*
+ * @return true, if the device is currently on a technology (e.g. UMTS or LTE) which can support
+ * voice and data simultaneously. This can change based on location or network condition.
+ */
+ public boolean isConcurrentVoiceAndDataAllowed() {
+ try {
+ ITelephony telephony = getITelephony();
+ return (telephony == null ? false : telephony.isConcurrentVoiceAndDataAllowed(mSubId));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#isConcurrentVoiceAndDataAllowed", e);
+ }
+ return false;
+ }
+
/** @hide */
@SystemApi
public boolean handlePinMmi(String dialString) {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index c0d6768ae..9a9a092 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -466,6 +466,12 @@
*/
int getVoiceMessageCountForSubscriber(int subId);
+ /**
+ * Returns true if current state supports both voice and data
+ * simultaneously. This can change based on location or network condition.
+ */
+ boolean isConcurrentVoiceAndDataAllowed(int subId);
+
oneway void setVisualVoicemailEnabled(String callingPackage,
in PhoneAccountHandle accountHandle, boolean enabled);
diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index 0cd0651..1e67769 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -117,10 +117,7 @@
mUNM.registerMobileNetworkRequest();
assertTrue(mUNM.mobileNetworkRequested());
- assertEquals(1, mCM.requested.size());
- assertEquals(1, mCM.legacyTypeMap.size());
- assertEquals(Integer.valueOf(TYPE_MOBILE_HIPRI),
- mCM.legacyTypeMap.values().iterator().next());
+ assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
assertFalse(mCM.isDunRequested());
mUNM.stop();
@@ -143,10 +140,7 @@
mUNM.registerMobileNetworkRequest();
assertTrue(mUNM.mobileNetworkRequested());
- assertEquals(1, mCM.requested.size());
- assertEquals(1, mCM.legacyTypeMap.size());
- assertEquals(Integer.valueOf(TYPE_MOBILE_DUN),
- mCM.legacyTypeMap.values().iterator().next());
+ assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
assertTrue(mCM.isDunRequested());
mUNM.stop();
@@ -154,6 +148,38 @@
assertTrue(mCM.hasNoCallbacks());
}
+ @Test
+ public void testUpdateMobileRequiredDun() throws Exception {
+ mUNM.start();
+
+ // Test going from no-DUN to DUN correctly re-registers callbacks.
+ mUNM.updateMobileRequiresDun(false);
+ mUNM.registerMobileNetworkRequest();
+ assertTrue(mUNM.mobileNetworkRequested());
+ assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
+ assertFalse(mCM.isDunRequested());
+ mUNM.updateMobileRequiresDun(true);
+ assertTrue(mUNM.mobileNetworkRequested());
+ assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
+ assertTrue(mCM.isDunRequested());
+
+ // Test going from DUN to no-DUN correctly re-registers callbacks.
+ mUNM.updateMobileRequiresDun(false);
+ assertTrue(mUNM.mobileNetworkRequested());
+ assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
+ assertFalse(mCM.isDunRequested());
+
+ mUNM.stop();
+ assertFalse(mUNM.mobileNetworkRequested());
+ }
+
+ private void assertUpstreamTypeRequested(int upstreamType) throws Exception {
+ assertEquals(1, mCM.requested.size());
+ assertEquals(1, mCM.legacyTypeMap.size());
+ assertEquals(Integer.valueOf(upstreamType),
+ mCM.legacyTypeMap.values().iterator().next());
+ }
+
private static class TestConnectivityManager extends ConnectivityManager {
public Set<NetworkCallback> trackingDefault = new HashSet<>();
public Map<NetworkCallback, NetworkRequest> listening = new HashMap<>();
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 75a3160..045d68c 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -211,7 +211,7 @@
bool isValidResourceType(const String8& type)
{
return type == "anim" || type == "animator" || type == "interpolator"
- || type == "transition"
+ || type == "transition" || type == "font"
|| type == "drawable" || type == "layout"
|| type == "values" || type == "xml" || type == "raw"
|| type == "color" || type == "menu" || type == "mipmap";
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index 50efc7f..d0c9599 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.text.FontConfig;
import com.android.ide.common.rendering.api.AssetRepository;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
@@ -284,7 +285,7 @@
@LayoutlibDelegate
/*package*/ static boolean nAddFontWeightStyle(long nativeFamily, ByteBuffer font,
- int ttcIndex, List<FontListParser.Axis> listOfAxis,
+ int ttcIndex, List<FontConfig.Axis> listOfAxis,
int weight, boolean isItalic) {
assert false : "The only client of this method has been overriden.";
return false;
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 5cd34f6..6e337d5 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.text.FontConfig;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
@@ -208,12 +209,12 @@
}
@LayoutlibDelegate
- /*package*/ static FontFamily makeFamilyFromParsed(FontListParser.Family family,
+ /*package*/ static FontFamily makeFamilyFromParsed(FontConfig.Family family,
Map<String, ByteBuffer> bufferForPath) {
- FontFamily fontFamily = new FontFamily(family.lang, family.variant);
- for (FontListParser.Font font : family.fonts) {
- FontFamily_Delegate.addFont(fontFamily.mNativePtr, font.fontName, font.weight,
- font.isItalic);
+ FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
+ for (FontConfig.Font font : family.getFonts()) {
+ FontFamily_Delegate.addFont(fontFamily.mNativePtr, font.getFontName(),
+ font.getWeight(), font.isItalic());
}
return fontFamily;
}