Merge "Don't populate a setting instead of defaulting to 0 when not set." into oc-dev
diff --git a/Android.mk b/Android.mk
index e27aa30..01fb73e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -224,6 +224,7 @@
core/java/android/net/IEthernetManager.aidl \
core/java/android/net/IEthernetServiceListener.aidl \
core/java/android/net/INetdEventCallback.aidl \
+ core/java/android/net/IIpSecService.aidl \
core/java/android/net/INetworkManagementEventObserver.aidl \
core/java/android/net/INetworkPolicyListener.aidl \
core/java/android/net/INetworkPolicyManager.aidl \
@@ -320,8 +321,6 @@
core/java/android/service/wallpaper/IWallpaperService.aidl \
core/java/android/service/chooser/IChooserTargetService.aidl \
core/java/android/service/chooser/IChooserTargetResult.aidl \
- core/java/android/service/resolver/IResolverRankerService.aidl \
- core/java/android/service/resolver/IResolverRankerResult.aidl \
core/java/android/text/ITextClassificationService.aidl \
core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl\
core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl\
@@ -730,7 +729,6 @@
frameworks/base/core/java/android/service/notification/SnoozeCriterion.aidl \
frameworks/base/core/java/android/service/notification/StatusBarNotification.aidl \
frameworks/base/core/java/android/service/chooser/ChooserTarget.aidl \
- frameworks/base/core/java/android/service/resolver/ResolverTarget.aidl \
frameworks/base/core/java/android/speech/tts/Voice.aidl \
frameworks/base/core/java/android/app/usage/CacheQuotaHint.aidl \
frameworks/base/core/java/android/app/usage/ExternalStorageStats.aidl \
diff --git a/api/current.txt b/api/current.txt
index e640e30..fe35f61 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1609,6 +1609,7 @@
field public static final int alert_light_frame = 17301505; // 0x1080001
field public static final int arrow_down_float = 17301506; // 0x1080002
field public static final int arrow_up_float = 17301507; // 0x1080003
+ field public static final int autofilled_highlight = 17301684; // 0x10800b4
field public static final int bottom_bar = 17301658; // 0x108009a
field public static final int btn_default = 17301508; // 0x1080004
field public static final int btn_default_small = 17301509; // 0x1080005
@@ -3578,7 +3579,7 @@
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public void enterPictureInPictureMode();
method public boolean enterPictureInPictureMode(android.app.PictureInPictureArgs);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public void finish();
method public void finishActivity(int);
method public void finishActivityFromChild(android.app.Activity, int);
@@ -4358,7 +4359,7 @@
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public boolean dispatchTouchEvent(android.view.MotionEvent);
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public android.app.ActionBar getActionBar();
method public final android.content.Context getContext();
method public android.view.View getCurrentFocus();
@@ -4771,7 +4772,7 @@
method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
method public abstract int getBackStackEntryCount();
method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String);
- method public abstract java.util.Collection<android.app.Fragment> getFragments();
+ method public abstract java.util.List<android.app.Fragment> getFragments();
method public abstract android.app.Fragment getPrimaryNavigationFragment();
method public void invalidateOptionsMenu();
method public abstract boolean isDestroyed();
@@ -5116,6 +5117,7 @@
method public java.lang.String getChannel();
method public java.lang.String getGroup();
method public android.graphics.drawable.Icon getLargeIcon();
+ method public java.lang.CharSequence getSettingsText();
method public java.lang.String getShortcutId();
method public android.graphics.drawable.Icon getSmallIcon();
method public java.lang.String getSortKey();
@@ -5160,6 +5162,8 @@
field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
field public static final java.lang.String EXTRA_MEDIA_SESSION = "android.mediaSession";
field public static final java.lang.String EXTRA_MESSAGES = "android.messages";
+ field public static final java.lang.String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID";
+ field public static final java.lang.String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG";
field public static final java.lang.String EXTRA_PEOPLE = "android.people";
field public static final java.lang.String EXTRA_PICTURE = "android.picture";
field public static final java.lang.String EXTRA_PROGRESS = "android.progress";
@@ -5345,6 +5349,7 @@
method public android.app.Notification.Builder setProgress(int, int, boolean);
method public android.app.Notification.Builder setPublicVersion(android.app.Notification);
method public android.app.Notification.Builder setRemoteInputHistory(java.lang.CharSequence[]);
+ method public android.app.Notification.Builder setSettingsText(java.lang.CharSequence);
method public android.app.Notification.Builder setShortcutId(java.lang.String);
method public android.app.Notification.Builder setShowWhen(boolean);
method public android.app.Notification.Builder setSmallIcon(int);
@@ -7044,6 +7049,7 @@
method public android.appwidget.AppWidgetProviderInfo getAppWidgetInfo(int);
method public android.os.Bundle getAppWidgetOptions(int);
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders();
+ method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForPackage(java.lang.String, android.os.UserHandle);
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(android.os.UserHandle);
method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
method public boolean isRequestPinAppWidgetSupported();
@@ -10801,6 +10807,7 @@
field public static final int PROTECTION_FLAG_PRE23 = 128; // 0x80
field public static final int PROTECTION_FLAG_PREINSTALLED = 1024; // 0x400
field public static final int PROTECTION_FLAG_PRIVILEGED = 16; // 0x10
+ field public static final int PROTECTION_FLAG_RUNTIME_ONLY = 8192; // 0x2000
field public static final int PROTECTION_FLAG_SETUP = 2048; // 0x800
field public static final deprecated int PROTECTION_FLAG_SYSTEM = 16; // 0x10
field public static final int PROTECTION_FLAG_VERIFIER = 512; // 0x200
@@ -25556,7 +25563,7 @@
method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform);
method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform);
- method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+ method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
@@ -25590,7 +25597,6 @@
method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
- method public android.net.IpSecTransform.Builder setSpi(int, int);
method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
}
@@ -34520,14 +34526,15 @@
public static final class FontsContract.Columns implements android.provider.BaseColumns {
ctor public FontsContract.Columns();
+ field public static final java.lang.String ITALIC = "font_italic";
field public static final java.lang.String RESULT_CODE = "result_code";
field public static final int RESULT_CODE_FONT_NOT_FOUND = 1; // 0x1
field public static final int RESULT_CODE_FONT_UNAVAILABLE = 2; // 0x2
field public static final int RESULT_CODE_MALFORMED_QUERY = 3; // 0x3
field public static final int RESULT_CODE_OK = 0; // 0x0
- 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";
+ field public static final java.lang.String WEIGHT = "font_weight";
}
public final deprecated class LiveFolders implements android.provider.BaseColumns {
@@ -37050,7 +37057,7 @@
ctor public FillResponse.Builder();
method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset);
method public android.service.autofill.FillResponse build();
- method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender, android.widget.RemoteViews);
+ method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle);
method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
}
@@ -37066,8 +37073,10 @@
field public static final android.os.Parcelable.Creator<android.service.autofill.SaveInfo> CREATOR;
field public static final int SAVE_DATA_TYPE_ADDRESS = 2; // 0x2
field public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3; // 0x3
+ field public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 5; // 0x5
field public static final int SAVE_DATA_TYPE_GENERIC = 0; // 0x0
field public static final int SAVE_DATA_TYPE_PASSWORD = 1; // 0x1
+ field public static final int SAVE_DATA_TYPE_USERNAME = 4; // 0x4
}
public static final class SaveInfo.Builder {
@@ -38749,6 +38758,7 @@
method public java.lang.String getCallerDisplayName();
method public int getCallerDisplayNamePresentation();
method public final long getConnectTimeMillis();
+ method public long getCreationTimeMillis();
method public android.telecom.DisconnectCause getDisconnectCause();
method public android.os.Bundle getExtras();
method public android.telecom.GatewayInfo getGatewayInfo();
@@ -46604,7 +46614,7 @@
method public void clearFlags(int);
method public abstract void closeAllPanels();
method public abstract void closePanel(int);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public boolean getAllowEnterTransitionOverlap();
method public boolean getAllowReturnTransitionOverlap();
method public final android.view.WindowManager.LayoutParams getAttributes();
diff --git a/api/removed.txt b/api/removed.txt
index 42b2ae6..d20c08c 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -296,6 +296,10 @@
field public static final deprecated java.lang.String TIMESTAMP = "timestamp";
}
+ public static final class FontsContract.Columns implements android.provider.BaseColumns {
+ field public static final java.lang.String STYLE = "font_style";
+ }
+
public static final class Settings.Global extends android.provider.Settings.NameValueTable {
field public static final deprecated java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync";
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 383b332..16e6f5b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -53,7 +53,6 @@
field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
- field public static final java.lang.String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE";
field public static final java.lang.String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE";
field public static final java.lang.String BIND_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE";
field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
@@ -1728,6 +1727,7 @@
field public static final int alert_light_frame = 17301505; // 0x1080001
field public static final int arrow_down_float = 17301506; // 0x1080002
field public static final int arrow_up_float = 17301507; // 0x1080003
+ field public static final int autofilled_highlight = 17301684; // 0x10800b4
field public static final int bottom_bar = 17301658; // 0x108009a
field public static final int btn_default = 17301508; // 0x1080004
field public static final int btn_default_small = 17301509; // 0x1080005
@@ -3703,7 +3703,7 @@
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public void enterPictureInPictureMode();
method public boolean enterPictureInPictureMode(android.app.PictureInPictureArgs);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public void finish();
method public void finishActivity(int);
method public void finishActivityFromChild(android.app.Activity, int);
@@ -4510,7 +4510,7 @@
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public boolean dispatchTouchEvent(android.view.MotionEvent);
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public android.app.ActionBar getActionBar();
method public final android.content.Context getContext();
method public android.view.View getCurrentFocus();
@@ -4622,6 +4622,7 @@
method public android.database.Cursor query(android.app.DownloadManager.Query);
method public int remove(long...);
field public static final java.lang.String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";
+ field public static final java.lang.String ACTION_DOWNLOAD_COMPLETED = "android.intent.action.DOWNLOAD_COMPLETED";
field public static final java.lang.String ACTION_NOTIFICATION_CLICKED = "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
field public static final java.lang.String ACTION_VIEW_DOWNLOADS = "android.intent.action.VIEW_DOWNLOADS";
field public static final java.lang.String COLUMN_BYTES_DOWNLOADED_SO_FAR = "bytes_so_far";
@@ -4931,7 +4932,7 @@
method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
method public abstract int getBackStackEntryCount();
method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String);
- method public abstract java.util.Collection<android.app.Fragment> getFragments();
+ method public abstract java.util.List<android.app.Fragment> getFragments();
method public abstract android.app.Fragment getPrimaryNavigationFragment();
method public void invalidateOptionsMenu();
method public abstract boolean isDestroyed();
@@ -5289,6 +5290,7 @@
method public java.lang.String getGroup();
method public android.graphics.drawable.Icon getLargeIcon();
method public static java.lang.Class<? extends android.app.Notification.Style> getNotificationStyleClass(java.lang.String);
+ method public java.lang.CharSequence getSettingsText();
method public java.lang.String getShortcutId();
method public android.graphics.drawable.Icon getSmallIcon();
method public java.lang.String getSortKey();
@@ -5334,6 +5336,8 @@
field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
field public static final java.lang.String EXTRA_MEDIA_SESSION = "android.mediaSession";
field public static final java.lang.String EXTRA_MESSAGES = "android.messages";
+ field public static final java.lang.String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID";
+ field public static final java.lang.String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG";
field public static final java.lang.String EXTRA_PEOPLE = "android.people";
field public static final java.lang.String EXTRA_PICTURE = "android.picture";
field public static final java.lang.String EXTRA_PROGRESS = "android.progress";
@@ -5521,6 +5525,7 @@
method public android.app.Notification.Builder setProgress(int, int, boolean);
method public android.app.Notification.Builder setPublicVersion(android.app.Notification);
method public android.app.Notification.Builder setRemoteInputHistory(java.lang.CharSequence[]);
+ method public android.app.Notification.Builder setSettingsText(java.lang.CharSequence);
method public android.app.Notification.Builder setShortcutId(java.lang.String);
method public android.app.Notification.Builder setShowWhen(boolean);
method public android.app.Notification.Builder setSmallIcon(int);
@@ -7503,6 +7508,7 @@
method public android.appwidget.AppWidgetProviderInfo getAppWidgetInfo(int);
method public android.os.Bundle getAppWidgetOptions(int);
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders();
+ method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForPackage(java.lang.String, android.os.UserHandle);
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(android.os.UserHandle);
method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
method public boolean isRequestPinAppWidgetSupported();
@@ -9797,6 +9803,7 @@
field public static final java.lang.String ACTION_DREAMING_STARTED = "android.intent.action.DREAMING_STARTED";
field public static final java.lang.String ACTION_DREAMING_STOPPED = "android.intent.action.DREAMING_STOPPED";
field public static final java.lang.String ACTION_EDIT = "android.intent.action.EDIT";
+ field public static final deprecated java.lang.String ACTION_EPHEMERAL_RESOLVER_SETTINGS = "android.intent.action.EPHEMERAL_RESOLVER_SETTINGS";
field public static final java.lang.String ACTION_EXTERNAL_APPLICATIONS_AVAILABLE = "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE";
field public static final java.lang.String ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE = "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE";
field public static final java.lang.String ACTION_FACTORY_RESET = "android.intent.action.FACTORY_RESET";
@@ -9809,7 +9816,10 @@
field public static final java.lang.String ACTION_INPUT_METHOD_CHANGED = "android.intent.action.INPUT_METHOD_CHANGED";
field public static final java.lang.String ACTION_INSERT = "android.intent.action.INSERT";
field public static final java.lang.String ACTION_INSERT_OR_EDIT = "android.intent.action.INSERT_OR_EDIT";
+ field public static final deprecated java.lang.String ACTION_INSTALL_EPHEMERAL_PACKAGE = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE";
+ field public static final java.lang.String ACTION_INSTALL_INSTANT_APP_PACKAGE = "android.intent.action.INSTALL_INSTANT_APP_PACKAGE";
field public static final java.lang.String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
+ field public static final java.lang.String ACTION_INSTANT_APP_RESOLVER_SETTINGS = "android.intent.action.INSTANT_APP_RESOLVER_SETTINGS";
field public static final java.lang.String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
field public static final java.lang.String ACTION_LOCALE_CHANGED = "android.intent.action.LOCALE_CHANGED";
field public static final java.lang.String ACTION_LOCKED_BOOT_COMPLETED = "android.intent.action.LOCKED_BOOT_COMPLETED";
@@ -9868,6 +9878,8 @@
field public static final java.lang.String ACTION_QUICK_CLOCK = "android.intent.action.QUICK_CLOCK";
field public static final java.lang.String ACTION_QUICK_VIEW = "android.intent.action.QUICK_VIEW";
field public static final java.lang.String ACTION_REBOOT = "android.intent.action.REBOOT";
+ field public static final deprecated java.lang.String ACTION_RESOLVE_EPHEMERAL_PACKAGE = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE";
+ field public static final java.lang.String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
field public static final java.lang.String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS";
field public static final java.lang.String ACTION_RUN = "android.intent.action.RUN";
field public static final java.lang.String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF";
@@ -9877,6 +9889,7 @@
field public static final java.lang.String ACTION_SEND = "android.intent.action.SEND";
field public static final java.lang.String ACTION_SENDTO = "android.intent.action.SENDTO";
field public static final java.lang.String ACTION_SEND_MULTIPLE = "android.intent.action.SEND_MULTIPLE";
+ field public static final deprecated java.lang.String ACTION_SERVICE_STATE = "android.intent.action.SERVICE_STATE";
field public static final java.lang.String ACTION_SET_WALLPAPER = "android.intent.action.SET_WALLPAPER";
field public static final java.lang.String ACTION_SHOW_APP_INFO = "android.intent.action.SHOW_APP_INFO";
field public static final java.lang.String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN";
@@ -9951,6 +9964,8 @@
field public static final java.lang.String EXTRA_BCC = "android.intent.extra.BCC";
field public static final java.lang.String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
field public static final java.lang.String EXTRA_CC = "android.intent.extra.CC";
+ field public static final deprecated java.lang.String EXTRA_CDMA_DEFAULT_ROAMING_INDICATOR = "cdmaDefaultRoamingIndicator";
+ field public static final deprecated java.lang.String EXTRA_CDMA_ROAMING_INDICATOR = "cdmaRoamingIndicator";
field public static final deprecated java.lang.String EXTRA_CHANGED_COMPONENT_NAME = "android.intent.extra.changed_component_name";
field public static final java.lang.String EXTRA_CHANGED_COMPONENT_NAME_LIST = "android.intent.extra.changed_component_name_list";
field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list";
@@ -9960,7 +9975,14 @@
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
field public static final java.lang.String EXTRA_CONTENT_ANNOTATIONS = "android.intent.extra.CONTENT_ANNOTATIONS";
+ field public static final deprecated java.lang.String EXTRA_CSS_INDICATOR = "cssIndicator";
+ field public static final deprecated java.lang.String EXTRA_DATA_OPERATOR_ALPHA_LONG = "data-operator-alpha-long";
+ field public static final deprecated java.lang.String EXTRA_DATA_OPERATOR_ALPHA_SHORT = "data-operator-alpha-short";
+ field public static final deprecated java.lang.String EXTRA_DATA_OPERATOR_NUMERIC = "data-operator-numeric";
+ field public static final deprecated java.lang.String EXTRA_DATA_RADIO_TECH = "dataRadioTechnology";
+ field public static final deprecated java.lang.String EXTRA_DATA_REG_STATE = "dataRegState";
field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
+ field public static final deprecated java.lang.String EXTRA_DATA_ROAMING_TYPE = "dataRoamingType";
field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE";
field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2
field public static final int EXTRA_DOCK_STATE_DESK = 1; // 0x1
@@ -9969,6 +9991,7 @@
field public static final int EXTRA_DOCK_STATE_UNDOCKED = 0; // 0x0
field public static final java.lang.String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP";
field public static final java.lang.String EXTRA_EMAIL = "android.intent.extra.EMAIL";
+ field public static final deprecated java.lang.String EXTRA_EMERGENCY_ONLY = "emergencyOnly";
field public static final java.lang.String EXTRA_EXCLUDE_COMPONENTS = "android.intent.extra.EXCLUDE_COMPONENTS";
field public static final java.lang.String EXTRA_FORCE_FACTORY_RESET = "android.intent.extra.FORCE_FACTORY_RESET";
field public static final java.lang.String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
@@ -9976,10 +9999,18 @@
field public static final java.lang.String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS";
field public static final java.lang.String EXTRA_INSTALLER_PACKAGE_NAME = "android.intent.extra.INSTALLER_PACKAGE_NAME";
field public static final java.lang.String EXTRA_INTENT = "android.intent.extra.INTENT";
+ field public static final deprecated java.lang.String EXTRA_IS_DATA_ROAMING_FROM_REGISTRATION = "isDataRoamingFromRegistration";
+ field public static final deprecated java.lang.String EXTRA_IS_USING_CARRIER_AGGREGATION = "isUsingCarrierAggregation";
field public static final java.lang.String EXTRA_KEY_EVENT = "android.intent.extra.KEY_EVENT";
field public static final java.lang.String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY";
+ field public static final deprecated java.lang.String EXTRA_LTE_EARFCN_RSRP_BOOST = "LteEarfcnRsrpBoost";
+ field public static final deprecated java.lang.String EXTRA_MANUAL = "manual";
field public static final java.lang.String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES";
+ field public static final deprecated java.lang.String EXTRA_NETWORK_ID = "networkId";
field public static final java.lang.String EXTRA_NOT_UNKNOWN_SOURCE = "android.intent.extra.NOT_UNKNOWN_SOURCE";
+ field public static final deprecated java.lang.String EXTRA_OPERATOR_ALPHA_LONG = "operator-alpha-long";
+ field public static final deprecated java.lang.String EXTRA_OPERATOR_ALPHA_SHORT = "operator-alpha-short";
+ field public static final deprecated java.lang.String EXTRA_OPERATOR_NUMERIC = "operator-numeric";
field public static final java.lang.String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID";
field public static final java.lang.String EXTRA_ORIGINATING_URI = "android.intent.extra.ORIGINATING_URI";
field public static final java.lang.String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
@@ -10011,11 +10042,15 @@
field public static final java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
field public static final java.lang.String EXTRA_SUBJECT = "android.intent.extra.SUBJECT";
field public static final java.lang.String EXTRA_SUBSCRIPTION_INDEX = "android.intent.extra.SUBSCRIPTION_INDEX";
+ field public static final deprecated java.lang.String EXTRA_SYSTEM_ID = "systemId";
field public static final java.lang.String EXTRA_TEMPLATE = "android.intent.extra.TEMPLATE";
field public static final java.lang.String EXTRA_TEXT = "android.intent.extra.TEXT";
field public static final java.lang.String EXTRA_TITLE = "android.intent.extra.TITLE";
field public static final java.lang.String EXTRA_UID = "android.intent.extra.UID";
field public static final java.lang.String EXTRA_USER = "android.intent.extra.USER";
+ field public static final deprecated java.lang.String EXTRA_VOICE_RADIO_TECH = "radioTechnology";
+ field public static final deprecated java.lang.String EXTRA_VOICE_REG_STATE = "voiceRegState";
+ field public static final deprecated java.lang.String EXTRA_VOICE_ROAMING_TYPE = "voiceRoamingType";
field public static final int FILL_IN_ACTION = 1; // 0x1
field public static final int FILL_IN_CATEGORIES = 4; // 0x4
field public static final int FILL_IN_CLIP_DATA = 128; // 0x80
@@ -11520,6 +11555,7 @@
field public static final int PROTECTION_FLAG_PRE23 = 128; // 0x80
field public static final int PROTECTION_FLAG_PREINSTALLED = 1024; // 0x400
field public static final int PROTECTION_FLAG_PRIVILEGED = 16; // 0x10
+ field public static final int PROTECTION_FLAG_RUNTIME_ONLY = 8192; // 0x2000
field public static final int PROTECTION_FLAG_SETUP = 2048; // 0x800
field public static final deprecated int PROTECTION_FLAG_SYSTEM = 16; // 0x10
field public static final int PROTECTION_FLAG_VERIFIER = 512; // 0x200
@@ -27688,13 +27724,11 @@
public final class IpSecManager {
method public void applyTransportModeTransform(java.net.Socket, android.net.IpSecTransform) throws java.io.IOException;
method public void applyTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform) throws java.io.IOException;
- method public void applyTunnelModeTransform(android.net.Network, android.net.IpSecTransform);
method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform);
method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform);
- method public void removeTunnelModeTransform(android.net.Network, android.net.IpSecTransform);
- method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+ method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
@@ -27725,12 +27759,10 @@
public static class IpSecTransform.Builder {
ctor public IpSecTransform.Builder(android.content.Context);
method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
- method public android.net.IpSecTransform buildTunnelModeTransform(java.net.InetAddress, java.net.InetAddress);
method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
method public android.net.IpSecTransform.Builder setNattKeepalive(int);
- method public android.net.IpSecTransform.Builder setSpi(int, int);
method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
method public android.net.IpSecTransform.Builder setUnderlyingNetwork(android.net.Network);
}
@@ -37430,14 +37462,15 @@
public static final class FontsContract.Columns implements android.provider.BaseColumns {
ctor public FontsContract.Columns();
+ field public static final java.lang.String ITALIC = "font_italic";
field public static final java.lang.String RESULT_CODE = "result_code";
field public static final int RESULT_CODE_FONT_NOT_FOUND = 1; // 0x1
field public static final int RESULT_CODE_FONT_UNAVAILABLE = 2; // 0x2
field public static final int RESULT_CODE_MALFORMED_QUERY = 3; // 0x3
field public static final int RESULT_CODE_OK = 0; // 0x0
- 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";
+ field public static final java.lang.String WEIGHT = "font_weight";
}
public final deprecated class LiveFolders implements android.provider.BaseColumns {
@@ -40086,7 +40119,7 @@
ctor public FillResponse.Builder();
method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset);
method public android.service.autofill.FillResponse build();
- method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender, android.widget.RemoteViews);
+ method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle);
method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
}
@@ -40102,8 +40135,10 @@
field public static final android.os.Parcelable.Creator<android.service.autofill.SaveInfo> CREATOR;
field public static final int SAVE_DATA_TYPE_ADDRESS = 2; // 0x2
field public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3; // 0x3
+ field public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 5; // 0x5
field public static final int SAVE_DATA_TYPE_GENERIC = 0; // 0x0
field public static final int SAVE_DATA_TYPE_PASSWORD = 1; // 0x1
+ field public static final int SAVE_DATA_TYPE_USERNAME = 4; // 0x4
}
public static final class SaveInfo.Builder {
@@ -40329,7 +40364,6 @@
method public int getUser();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
- field public static final java.lang.String KEY_CHANNEL_ID = "key_channel_id";
field public static final java.lang.String KEY_PEOPLE = "key_people";
field public static final java.lang.String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
}
@@ -40384,14 +40418,10 @@
ctor public NotificationAssistantService();
method public final void adjustNotification(android.service.notification.Adjustment);
method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
- method public void createNotificationChannel(java.lang.String, android.app.NotificationChannel);
- method public void deleteNotificationChannel(java.lang.String, java.lang.String);
- method public java.util.List<android.app.NotificationChannel> getNotificationChannels(java.lang.String);
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String);
method public final void unsnoozeNotification(java.lang.String);
- method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
}
@@ -40589,36 +40619,6 @@
}
-package android.service.resolver {
-
- public abstract class ResolverRankerService extends android.app.Service {
- ctor public ResolverRankerService();
- method public android.os.IBinder onBind(android.content.Intent);
- method public void onPredictSharingProbabilities(java.util.List<android.service.resolver.ResolverTarget>);
- method public void onTrainRankingModel(java.util.List<android.service.resolver.ResolverTarget>, int);
- field public static final java.lang.String BIND_PERMISSION = "android.permission.BIND_RESOLVER_RANKER_SERVICE";
- field public static final java.lang.String SERVICE_INTERFACE = "android.service.resolver.ResolverRankerService";
- }
-
- public final class ResolverTarget implements android.os.Parcelable {
- ctor public ResolverTarget();
- method public int describeContents();
- method public float getChooserScore();
- method public float getLaunchScore();
- method public float getRecencyScore();
- method public float getSelectProbability();
- method public float getTimeSpentScore();
- method public void setChooserScore(float);
- method public void setLaunchScore(float);
- method public void setRecencyScore(float);
- method public void setSelectProbability(float);
- method public void setTimeSpentScore(float);
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.resolver.ResolverTarget> CREATOR;
- }
-
-}
-
package android.service.restrictions {
public abstract class RestrictionsReceiver extends android.content.BroadcastReceiver {
@@ -41942,6 +41942,7 @@
method public java.lang.String getCallerDisplayName();
method public int getCallerDisplayNamePresentation();
method public final long getConnectTimeMillis();
+ method public long getCreationTimeMillis();
method public android.telecom.DisconnectCause getDisconnectCause();
method public android.os.Bundle getExtras();
method public android.telecom.GatewayInfo getGatewayInfo();
@@ -50098,7 +50099,7 @@
method public void clearFlags(int);
method public abstract void closeAllPanels();
method public abstract void closePanel(int);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public boolean getAllowEnterTransitionOverlap();
method public boolean getAllowReturnTransitionOverlap();
method public final android.view.WindowManager.LayoutParams getAttributes();
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 1bafe96..1effe9c 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -290,6 +290,10 @@
field public static final deprecated java.lang.String TIMESTAMP = "timestamp";
}
+ public static final class FontsContract.Columns implements android.provider.BaseColumns {
+ field public static final java.lang.String STYLE = "font_style";
+ }
+
public static final class Settings.Global extends android.provider.Settings.NameValueTable {
field public static final deprecated java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync";
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 795342f..d1dc37c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1609,6 +1609,7 @@
field public static final int alert_light_frame = 17301505; // 0x1080001
field public static final int arrow_down_float = 17301506; // 0x1080002
field public static final int arrow_up_float = 17301507; // 0x1080003
+ field public static final int autofilled_highlight = 17301684; // 0x10800b4
field public static final int bottom_bar = 17301658; // 0x108009a
field public static final int btn_default = 17301508; // 0x1080004
field public static final int btn_default_small = 17301509; // 0x1080005
@@ -3580,7 +3581,7 @@
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public void enterPictureInPictureMode();
method public boolean enterPictureInPictureMode(android.app.PictureInPictureArgs);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public void finish();
method public void finishActivity(int);
method public void finishActivityFromChild(android.app.Activity, int);
@@ -4370,7 +4371,7 @@
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public boolean dispatchTouchEvent(android.view.MotionEvent);
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public android.app.ActionBar getActionBar();
method public final android.content.Context getContext();
method public android.view.View getCurrentFocus();
@@ -4783,7 +4784,7 @@
method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
method public abstract int getBackStackEntryCount();
method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String);
- method public abstract java.util.Collection<android.app.Fragment> getFragments();
+ method public abstract java.util.List<android.app.Fragment> getFragments();
method public abstract android.app.Fragment getPrimaryNavigationFragment();
method public void invalidateOptionsMenu();
method public abstract boolean isDestroyed();
@@ -5128,6 +5129,7 @@
method public java.lang.String getChannel();
method public java.lang.String getGroup();
method public android.graphics.drawable.Icon getLargeIcon();
+ method public java.lang.CharSequence getSettingsText();
method public java.lang.String getShortcutId();
method public android.graphics.drawable.Icon getSmallIcon();
method public java.lang.String getSortKey();
@@ -5172,6 +5174,8 @@
field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
field public static final java.lang.String EXTRA_MEDIA_SESSION = "android.mediaSession";
field public static final java.lang.String EXTRA_MESSAGES = "android.messages";
+ field public static final java.lang.String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID";
+ field public static final java.lang.String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG";
field public static final java.lang.String EXTRA_PEOPLE = "android.people";
field public static final java.lang.String EXTRA_PICTURE = "android.picture";
field public static final java.lang.String EXTRA_PROGRESS = "android.progress";
@@ -5357,6 +5361,7 @@
method public android.app.Notification.Builder setProgress(int, int, boolean);
method public android.app.Notification.Builder setPublicVersion(android.app.Notification);
method public android.app.Notification.Builder setRemoteInputHistory(java.lang.CharSequence[]);
+ method public android.app.Notification.Builder setSettingsText(java.lang.CharSequence);
method public android.app.Notification.Builder setShortcutId(java.lang.String);
method public android.app.Notification.Builder setShowWhen(boolean);
method public android.app.Notification.Builder setSmallIcon(int);
@@ -7074,6 +7079,7 @@
method public android.appwidget.AppWidgetProviderInfo getAppWidgetInfo(int);
method public android.os.Bundle getAppWidgetOptions(int);
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders();
+ method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForPackage(java.lang.String, android.os.UserHandle);
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(android.os.UserHandle);
method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
method public boolean isRequestPinAppWidgetSupported();
@@ -10841,6 +10847,7 @@
field public static final int PROTECTION_FLAG_PRE23 = 128; // 0x80
field public static final int PROTECTION_FLAG_PREINSTALLED = 1024; // 0x400
field public static final int PROTECTION_FLAG_PRIVILEGED = 16; // 0x10
+ field public static final int PROTECTION_FLAG_RUNTIME_ONLY = 8192; // 0x2000
field public static final int PROTECTION_FLAG_SETUP = 2048; // 0x800
field public static final deprecated int PROTECTION_FLAG_SYSTEM = 16; // 0x10
field public static final int PROTECTION_FLAG_VERIFIER = 512; // 0x200
@@ -25669,7 +25676,7 @@
method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform);
method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform);
- method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+ method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
@@ -25703,7 +25710,6 @@
method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
- method public android.net.IpSecTransform.Builder setSpi(int, int);
method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
}
@@ -34660,14 +34666,15 @@
public static final class FontsContract.Columns implements android.provider.BaseColumns {
ctor public FontsContract.Columns();
+ field public static final java.lang.String ITALIC = "font_italic";
field public static final java.lang.String RESULT_CODE = "result_code";
field public static final int RESULT_CODE_FONT_NOT_FOUND = 1; // 0x1
field public static final int RESULT_CODE_FONT_UNAVAILABLE = 2; // 0x2
field public static final int RESULT_CODE_MALFORMED_QUERY = 3; // 0x3
field public static final int RESULT_CODE_OK = 0; // 0x0
- 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";
+ field public static final java.lang.String WEIGHT = "font_weight";
}
public final deprecated class LiveFolders implements android.provider.BaseColumns {
@@ -37208,7 +37215,7 @@
ctor public FillResponse.Builder();
method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset);
method public android.service.autofill.FillResponse build();
- method public android.service.autofill.FillResponse.Builder setAuthentication(android.content.IntentSender, android.widget.RemoteViews);
+ method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
method public android.service.autofill.FillResponse.Builder setExtras(android.os.Bundle);
method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
}
@@ -37224,8 +37231,10 @@
field public static final android.os.Parcelable.Creator<android.service.autofill.SaveInfo> CREATOR;
field public static final int SAVE_DATA_TYPE_ADDRESS = 2; // 0x2
field public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3; // 0x3
+ field public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 5; // 0x5
field public static final int SAVE_DATA_TYPE_GENERIC = 0; // 0x0
field public static final int SAVE_DATA_TYPE_PASSWORD = 1; // 0x1
+ field public static final int SAVE_DATA_TYPE_USERNAME = 4; // 0x4
}
public static final class SaveInfo.Builder {
@@ -37451,7 +37460,6 @@
method public int getUser();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
- field public static final java.lang.String KEY_CHANNEL_ID = "key_channel_id";
field public static final java.lang.String KEY_PEOPLE = "key_people";
field public static final java.lang.String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
}
@@ -37506,14 +37514,10 @@
ctor public NotificationAssistantService();
method public final void adjustNotification(android.service.notification.Adjustment);
method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
- method public void createNotificationChannel(java.lang.String, android.app.NotificationChannel);
- method public void deleteNotificationChannel(java.lang.String, java.lang.String);
- method public java.util.List<android.app.NotificationChannel> getNotificationChannels(java.lang.String);
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String);
method public final void unsnoozeNotification(java.lang.String);
- method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
}
@@ -38953,6 +38957,7 @@
method public java.lang.String getCallerDisplayName();
method public int getCallerDisplayNamePresentation();
method public final long getConnectTimeMillis();
+ method public long getCreationTimeMillis();
method public android.telecom.DisconnectCause getDisconnectCause();
method public android.os.Bundle getExtras();
method public android.telecom.GatewayInfo getGatewayInfo();
@@ -46035,6 +46040,7 @@
method public void setAnimation(android.view.animation.Animation);
method public void setAutofillHints(java.lang.String...);
method public void setAutofillMode(int);
+ method public void setAutofilled(boolean);
method public void setBackground(android.graphics.drawable.Drawable);
method public void setBackgroundColor(int);
method public deprecated void setBackgroundDrawable(android.graphics.drawable.Drawable);
@@ -46986,7 +46992,7 @@
method public void clearFlags(int);
method public abstract void closeAllPanels();
method public abstract void closePanel(int);
- method public android.view.View findViewById(int);
+ method public <T extends android.view.View> T findViewById(int);
method public boolean getAllowEnterTransitionOverlap();
method public boolean getAllowReturnTransitionOverlap();
method public final android.view.WindowManager.LayoutParams getAttributes();
diff --git a/api/test-removed.txt b/api/test-removed.txt
index 42b2ae6..d20c08c 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -296,6 +296,10 @@
field public static final deprecated java.lang.String TIMESTAMP = "timestamp";
}
+ public static final class FontsContract.Columns implements android.provider.BaseColumns {
+ field public static final java.lang.String STYLE = "font_style";
+ }
+
public static final class Settings.Global extends android.provider.Settings.NameValueTable {
field public static final deprecated java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync";
}
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 2435ffa..7394490 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -370,7 +370,8 @@
bool BootAnimation::android()
{
- ALOGD("BootAnimationShownTiming start time: %" PRId64 "ms", elapsedRealtime());
+ ALOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
+ elapsedRealtime());
initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");
@@ -896,7 +897,8 @@
const int animationX = (mWidth - animation.width) / 2;
const int animationY = (mHeight - animation.height) / 2;
- ALOGD("BootAnimationShownTiming start time: %" PRId64 "ms", elapsedRealtime());
+ ALOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
+ elapsedRealtime());
for (size_t i=0 ; i<pcount ; i++) {
const Animation::Part& part(animation.parts[i]);
const size_t fcount = part.frames.size();
diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java
index db17b28..ce114fd 100644
--- a/cmds/bu/src/com/android/commands/bu/Backup.java
+++ b/cmds/bu/src/com/android/commands/bu/Backup.java
@@ -101,8 +101,10 @@
doCompress = true;
} else if ("-nocompress".equals(arg)) {
doCompress = false;
- } else if ("-includekeyvalue".equals(arg)) {
+ } else if ("-keyvalue".equals(arg)) {
doKeyValue = true;
+ } else if ("-nokeyvalue".equals(arg)) {
+ doKeyValue = false;
} else {
Log.w(TAG, "Unknown backup flag " + arg);
continue;
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index 35011b5..8442585 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -40,8 +40,8 @@
import java.io.IOException;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@@ -98,11 +98,10 @@
"alwaysPromptForAccount";
/**
- * If set then this string willb e used as the description rather than
+ * If set then this string will be used as the description rather than
* the default.
*/
- public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE =
- "descriptionTextOverride";
+ public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE = "descriptionTextOverride";
public static final int REQUEST_NULL = 0;
public static final int REQUEST_CHOOSE_TYPE = 1;
@@ -112,7 +111,8 @@
private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts";
private static final String KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME = "selectedAccountName";
private static final String KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT = "selectedAddAccount";
- private static final String KEY_INSTANCE_STATE_ACCOUNT_LIST = "accountAndVisibilityList";
+ private static final String KEY_INSTANCE_STATE_ACCOUNTS_LIST = "accountsList";
+ private static final String KEY_INSTANCE_STATE_VISIBILITY_LIST = "visibilityList";
private static final int SELECTED_ITEM_NONE = -1;
@@ -122,7 +122,7 @@
private boolean mSelectedAddNewAccount = false;
private String mDescriptionOverride;
- private Map<Account, Integer> mAccounts;
+ private LinkedHashMap<Account, Integer> mAccounts;
// TODO Redesign flow to show NOT_VISIBLE accounts
// and display a warning if they are selected.
// Currently NOT_VISBILE accounts are not shown at all.
@@ -164,6 +164,10 @@
// save some items we use frequently
final Intent intent = getIntent();
+ mSetOfAllowableAccounts = getAllowableAccountSet(intent);
+ mSetOfRelevantAccountTypes = getReleventAccountTypes(intent);
+ mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE);
+
if (savedInstanceState != null) {
mPendingRequest = savedInstanceState.getInt(KEY_INSTANCE_STATE_PENDING_REQUEST);
mExistingAccounts =
@@ -174,8 +178,15 @@
savedInstanceState.getString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME);
mSelectedAddNewAccount =
savedInstanceState.getBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
- mAccounts = (Map<Account, Integer>) savedInstanceState
- .getSerializable(KEY_INSTANCE_STATE_ACCOUNT_LIST);
+ // restore mAccounts
+ Parcelable[] accounts =
+ savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_ACCOUNTS_LIST);
+ ArrayList<Integer> visibility =
+ savedInstanceState.getIntegerArrayList(KEY_INSTANCE_STATE_VISIBILITY_LIST);
+ mAccounts = new LinkedHashMap<>();
+ for (int i = 0; i < accounts.length; i++) {
+ mAccounts.put((Account) accounts[i], visibility.get(i));
+ }
} else {
mPendingRequest = REQUEST_NULL;
mExistingAccounts = null;
@@ -185,20 +196,21 @@
if (selectedAccount != null) {
mSelectedAccountName = selectedAccount.name;
}
+ mAccounts = getAcceptableAccountChoices(AccountManager.get(this));
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "selected account name is " + mSelectedAccountName);
}
+ mPossiblyVisibleAccounts = new ArrayList<>(mAccounts.size());
+ for (Map.Entry<Account, Integer> entry : mAccounts.entrySet()) {
+ if (AccountManager.VISIBILITY_NOT_VISIBLE != entry.getValue()) {
+ mPossiblyVisibleAccounts.add(entry.getKey());
+ }
+ }
- mSetOfAllowableAccounts = getAllowableAccountSet(intent);
- mSetOfRelevantAccountTypes = getReleventAccountTypes(intent);
- mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE);
-
- mAccounts = getAcceptableAccountChoices(AccountManager.get(this));
- if (mAccounts.isEmpty()
- && mDisallowAddAccounts) {
+ if (mPossiblyVisibleAccounts.isEmpty() && mDisallowAddAccounts) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.app_not_authorized);
mDontShowPicker = true;
@@ -216,7 +228,7 @@
if (mPendingRequest == REQUEST_NULL) {
// If there are no relevant accounts and only one relevant account type go directly to
// add account. Otherwise let the user choose.
- if (mAccounts.isEmpty()) {
+ if (mPossiblyVisibleAccounts.isEmpty()) {
setNonLabelThemeAndCallSuperCreate(savedInstanceState);
if (mSetOfRelevantAccountTypes.size() == 1) {
runAddAccountForAuthenticator(mSetOfRelevantAccountTypes.iterator().next());
@@ -226,12 +238,6 @@
}
}
- mPossiblyVisibleAccounts = new ArrayList<>(mAccounts.size());
- for (Map.Entry<Account, Integer> entry : mAccounts.entrySet()) {
- if (AccountManager.VISIBILITY_NOT_VISIBLE != entry.getValue()) {
- mPossiblyVisibleAccounts.add(entry.getKey());
- }
- }
String[] listItems = getListOfDisplayableOptions(mPossiblyVisibleAccounts);
mSelectedItemIndex = getItemIndexToSelect(mPossiblyVisibleAccounts, mSelectedAccountName,
mSelectedAddNewAccount);
@@ -270,10 +276,16 @@
mPossiblyVisibleAccounts.get(mSelectedItemIndex).name);
}
}
- // should be HashMap by default.
- HashMap<Account, Integer> accountsHashMap = (mAccounts instanceof HashMap)
- ? (HashMap) mAccounts : new HashMap<Account, Integer>(mAccounts);
- outState.putSerializable(KEY_INSTANCE_STATE_ACCOUNT_LIST, accountsHashMap);
+ // save mAccounts
+ Parcelable[] accounts = new Parcelable[mAccounts.size()];
+ ArrayList<Integer> visibility = new ArrayList<>(mAccounts.size());
+ int i = 0;
+ for (Map.Entry<Account, Integer> e : mAccounts.entrySet()) {
+ accounts[i++] = e.getKey();
+ visibility.add(e.getValue());
+ }
+ outState.putParcelableArray(KEY_INSTANCE_STATE_ACCOUNTS_LIST, accounts);
+ outState.putIntegerArrayList(KEY_INSTANCE_STATE_VISIBILITY_LIST, visibility);
}
public void onCancelButtonClicked(View view) {
@@ -308,7 +320,7 @@
if (resultCode == RESULT_CANCELED) {
// if canceling out of addAccount and the original state caused us to skip this,
// finish this activity
- if (mAccounts.isEmpty()) {
+ if (mPossiblyVisibleAccounts.isEmpty()) {
setResult(Activity.RESULT_CANCELED);
finish();
}
@@ -428,18 +440,20 @@
private void setResultAndFinish(final String accountName, final String accountType) {
// Mark account as visible since user chose it.
Account account = new Account(accountName, accountType);
- Integer oldVisibility = mAccounts.get(account);
- // oldVisibility is null if new account was added
- if (oldVisibility == null) {
- Map<Account, Integer> accountsAndVisibility = AccountManager.get(this)
- .getAccountsAndVisibilityForPackage(mCallingPackage, null /* type */);
- oldVisibility = accountsAndVisibility.get(account);
- }
+ Integer oldVisibility =
+ AccountManager.get(this).getAccountVisibility(account, mCallingPackage);
if (oldVisibility != null
&& oldVisibility == AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE) {
AccountManager.get(this).setAccountVisibility(account, mCallingPackage,
AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
}
+
+ if (oldVisibility != null && oldVisibility == AccountManager.VISIBILITY_NOT_VISIBLE) {
+ // Added account is not visible to caller.
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
Bundle bundle = new Bundle();
bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
@@ -448,6 +462,7 @@
Log.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: selected account "
+ accountName + ", " + accountType);
}
+
finish();
}
@@ -509,22 +524,24 @@
* that don't match the allowable types, if provided, or that don't match the allowable
* accounts, if provided.
*/
- private Map<Account, Integer> getAcceptableAccountChoices(AccountManager accountManager) {
- Map<Account, Integer> accountsAndVisibility =
- accountManager.getAccountsAndVisibilityForPackage(mCallingPackage, null /* type */);
-
- Map<Account, Integer> accountsToPopulate =
- new HashMap<Account, Integer>(accountsAndVisibility.size());
- for (Map.Entry<Account, Integer> entry : accountsAndVisibility.entrySet()) {
+ private LinkedHashMap<Account, Integer> getAcceptableAccountChoices(AccountManager accountManager) {
+ Map<Account, Integer> accountsAndVisibilityForCaller =
+ accountManager.getAccountsAndVisibilityForPackage(mCallingPackage, null);
+ Account[] allAccounts = accountManager.getAccounts();
+ LinkedHashMap<Account, Integer> accountsToPopulate =
+ new LinkedHashMap<>(accountsAndVisibilityForCaller.size());
+ for (Account account : allAccounts) {
if (mSetOfAllowableAccounts != null
- && !mSetOfAllowableAccounts.contains(entry.getKey())) {
+ && !mSetOfAllowableAccounts.contains(account)) {
continue;
}
if (mSetOfRelevantAccountTypes != null
- && !mSetOfRelevantAccountTypes.contains(entry.getKey().type)) {
+ && !mSetOfRelevantAccountTypes.contains(account.type)) {
continue;
}
- accountsToPopulate.put(entry.getKey(), entry.getValue());
+ if (accountsAndVisibilityForCaller.get(account) != null) {
+ accountsToPopulate.put(account, accountsAndVisibilityForCaller.get(account));
+ }
}
return accountsToPopulate;
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d432160..2376a95 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -724,6 +724,7 @@
public static final int FINISH_TASK_WITH_ACTIVITY = 2;
static final String FRAGMENTS_TAG = "android:fragments";
+ static final String AUTOFILL_RESET_NEEDED_TAG = "android:autofillResetNeeded";
private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState";
private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds";
@@ -1057,6 +1058,12 @@
* @see #onSaveInstanceState
*/
protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ mAutoFillResetNeeded = savedInstanceState.getBoolean(AUTOFILL_RESET_NEEDED_TAG, false);
+
+ if (mAutoFillResetNeeded) {
+ getSystemService(AutofillManager.class).onRestoreInstanceState(savedInstanceState);
+ }
+
if (mWindow != null) {
Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if (windowState != null) {
@@ -1502,6 +1509,10 @@
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
+ if (mAutoFillResetNeeded) {
+ outState.putBoolean(AUTOFILL_RESET_NEEDED_TAG, mAutoFillResetNeeded);
+ getSystemService(AutofillManager.class).onSaveInstanceState(outState);
+ }
getApplication().dispatchActivitySaveInstanceState(this, outState);
}
@@ -2443,13 +2454,20 @@
}
/**
- * Finds a view that was identified by the id attribute from the XML that
- * was processed in {@link #onCreate}.
+ * Finds a view that was identified by the {@code android:id} XML attribute
+ * that was processed in {@link #onCreate}.
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
*
- * @return The view if found or null otherwise.
+ * @param id the ID to search for
+ * @return a view with given ID if found, or {@code null} otherwise
+ * @see View#findViewById(int)
*/
@Nullable
- public View findViewById(@IdRes int id) {
+ public <T extends View> T findViewById(@IdRes int id) {
return getWindow().findViewById(id);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d1d462c..7299d6b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -38,6 +38,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.content.res.AssetManager;
@@ -869,16 +870,20 @@
sendMessage(H.UNBIND_SERVICE, s);
}
- public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
- int flags ,Intent args) {
- ServiceArgsData s = new ServiceArgsData();
- s.token = token;
- s.taskRemoved = taskRemoved;
- s.startId = startId;
- s.flags = flags;
- s.args = args;
+ public final void scheduleServiceArgs(IBinder token, ParceledListSlice args) {
+ List<ServiceStartArgs> list = args.getList();
- sendMessage(H.SERVICE_ARGS, s);
+ for (int i = 0; i < list.size(); i++) {
+ ServiceStartArgs ssa = list.get(i);
+ ServiceArgsData s = new ServiceArgsData();
+ s.token = token;
+ s.taskRemoved = ssa.taskRemoved;
+ s.startId = ssa.startId;
+ s.flags = ssa.flags;
+ s.args = ssa.args;
+
+ sendMessage(H.SERVICE_ARGS, s);
+ }
}
public final void scheduleStopService(IBinder token) {
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 72ccf72..943c572 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -498,14 +498,22 @@
}
/**
- * Finds a child view with the given identifier. Returns null if the
- * specified child view does not exist or the dialog has not yet been fully
- * created (for example, via {@link #show()} or {@link #create()}).
+ * Finds the first descendant view with the given ID or {@code null} if the
+ * ID is invalid (< 0), there is no matching view in the hierarchy, or the
+ * dialog has not yet been fully created (for example, via {@link #show()}
+ * or {@link #create()}).
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
*
- * @param id the identifier of the view to find
- * @return The view with the given id or null.
+ * @param id the ID to search for
+ * @return a view with given ID if found, or {@code null} otherwise
+ * @see View#findViewById(int)
*/
- public @Nullable View findViewById(@IdRes int id) {
+ @Nullable
+ public <T extends View> T findViewById(@IdRes int id) {
return mWindow.findViewById(id);
}
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 462f66f..b89c165 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.ContentResolver;
import android.content.ContentUris;
@@ -321,6 +322,11 @@
*/
public static final String EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS = "extra_click_download_ids";
+ /** {@hide} */
+ @SystemApi
+ public static final String ACTION_DOWNLOAD_COMPLETED =
+ "android.intent.action.DOWNLOAD_COMPLETED";
+
/**
* columns to request from DownloadProvider.
* @hide
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 3102a93..a3c123f 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -2608,6 +2608,12 @@
}
}
+ void noteStateNotSaved() {
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.noteStateNotSaved();
+ }
+ }
+
@Deprecated
void performMultiWindowModeChanged(boolean isInMultiWindowMode) {
onMultiWindowModeChanged(isInMultiWindowMode);
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 399987f..75d6295 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -314,14 +314,17 @@
public abstract Fragment getFragment(Bundle bundle, String key);
/**
- * Get a collection of all fragments that are currently added to the FragmentManager.
+ * Get a list of all fragments that are currently added to the FragmentManager.
* This may include those that are hidden as well as those that are shown.
* This will not include any fragments only in the back stack, or fragments that
* are detached or removed.
+ * <p>
+ * The order of the fragments in the list is the order in which they were
+ * added or attached.
*
- * @return A collection of all fragments that are added to the FragmentManager.
+ * @return A list of all fragments that are added to the FragmentManager.
*/
- public abstract Collection<Fragment> getFragments();
+ public abstract List<Fragment> getFragments();
/**
* Save the current instance state of the given Fragment. This can be
@@ -695,6 +698,9 @@
// This is dangerous, but we want to keep from breaking old applications.
boolean mAllowOldReentrantBehavior;
+ // Saved FragmentManagerNonConfig during saveAllState() and cleared in noteStateNotSaved()
+ FragmentManagerNonConfig mSavedNonConfig;
+
Runnable mExecCommit = new Runnable() {
@Override
public void run() {
@@ -907,12 +913,12 @@
}
@Override
- public Collection<Fragment> getFragments() {
+ public List<Fragment> getFragments() {
if (mAdded == null) {
return Collections.EMPTY_LIST;
}
synchronized (mAdded) {
- return (Collection<Fragment>) mAdded.clone();
+ return (List<Fragment>) mAdded.clone();
}
}
@@ -2518,6 +2524,35 @@
}
FragmentManagerNonConfig retainNonConfig() {
+ setRetaining(mSavedNonConfig);
+ return mSavedNonConfig;
+ }
+
+ /**
+ * Recurse the FragmentManagerNonConfig fragments and set the mRetaining to true. This
+ * was previously done while saving the non-config state, but that has been moved to
+ * {@link #saveNonConfig()} called from {@link #saveAllState()}. If mRetaining is set too
+ * early, the fragment won't be destroyed when the FragmentManager is destroyed.
+ */
+ private static void setRetaining(FragmentManagerNonConfig nonConfig) {
+ if (nonConfig == null) {
+ return;
+ }
+ List<Fragment> fragments = nonConfig.getFragments();
+ if (fragments != null) {
+ for (Fragment fragment : fragments) {
+ fragment.mRetaining = true;
+ }
+ }
+ List<FragmentManagerNonConfig> children = nonConfig.getChildNonConfigs();
+ if (children != null) {
+ for (FragmentManagerNonConfig child : children) {
+ setRetaining(child);
+ }
+ }
+ }
+
+ void saveNonConfig() {
ArrayList<Fragment> fragments = null;
ArrayList<FragmentManagerNonConfig> childFragments = null;
if (mActive != null) {
@@ -2529,13 +2564,13 @@
fragments = new ArrayList<>();
}
fragments.add(f);
- f.mRetaining = true;
f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f);
}
boolean addedChild = false;
if (f.mChildFragmentManager != null) {
- FragmentManagerNonConfig child = f.mChildFragmentManager.retainNonConfig();
+ f.mChildFragmentManager.saveNonConfig();
+ FragmentManagerNonConfig child = f.mChildFragmentManager.mSavedNonConfig;
if (child != null) {
if (childFragments == null) {
childFragments = new ArrayList<>();
@@ -2554,9 +2589,10 @@
}
}
if (fragments == null && childFragments == null) {
- return null;
+ mSavedNonConfig = null;
+ } else {
+ mSavedNonConfig = new FragmentManagerNonConfig(fragments, childFragments);
}
- return new FragmentManagerNonConfig(fragments, childFragments);
}
void saveFragmentViewState(Fragment f) {
@@ -2617,6 +2653,7 @@
execPendingActions();
mStateSaved = true;
+ mSavedNonConfig = null;
if (mActive == null || mActive.size() <= 0) {
return null;
@@ -2717,6 +2754,7 @@
if (mPrimaryNav != null) {
fms.mPrimaryNavActiveIndex = mPrimaryNav.mIndex;
}
+ saveNonConfig();
return fms;
}
@@ -2892,9 +2930,17 @@
}
public void noteStateNotSaved() {
+ mSavedNonConfig = null;
mStateSaved = false;
+ final int addedCount = mAdded == null ? 0 : mAdded.size();
+ for (int i = 0; i < addedCount; i++) {
+ Fragment fragment = mAdded.get(i);
+ if (fragment != null) {
+ fragment.noteStateNotSaved();
+ }
+ }
}
-
+
public void dispatchCreate() {
mStateSaved = false;
dispatchMoveToState(Fragment.CREATED);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index f4d26fd..079bbcd 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -635,6 +635,11 @@
*/
int getLastResumedActivityUserId();
+ /**
+ * Add a bare uid to the background restrictions whitelist. Only the system uid may call this.
+ */
+ void backgroundWhitelistUid(int uid);
+
// WARNING: when these transactions are updated, check if they are any callers on the native
// side. If so, make sure they are using the correct transaction ids and arguments.
// If a transaction which will also be used on the native side is being inserted, add it
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 6c43fe3..1b3c00b 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -25,6 +25,7 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ParceledListSlice;
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.content.res.CompatibilityInfo;
@@ -86,8 +87,7 @@
in Bundle coreSettings, in String buildSerial);
void scheduleExit();
void scheduleConfigurationChanged(in Configuration config);
- void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
- int flags, in Intent args);
+ void scheduleServiceArgs(IBinder token, in ParceledListSlice args);
void updateTimeZone();
void processInBackground();
void scheduleBindService(IBinder token,
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 43cad5b..61dacef 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -104,10 +104,6 @@
void applyEnqueuedAdjustmentFromAssistant(in INotificationListener token, in Adjustment adjustment);
void applyAdjustmentFromAssistant(in INotificationListener token, in Adjustment adjustment);
void applyAdjustmentsFromAssistant(in INotificationListener token, in List<Adjustment> adjustments);
- void createNotificationChannelFromAssistant(in INotificationListener token, String pkg, in NotificationChannel channel);
- void updateNotificationChannelFromAssistant(in INotificationListener token, String pkg, in NotificationChannel channel);
- void deleteNotificationChannelFromAssistant(in INotificationListener token, String pkg, String channelId);
- ParceledListSlice getNotificationChannelsFromAssistant(in INotificationListener token, String pkg);
void unsnoozeNotificationFromAssistant(in INotificationListener token, String key);
ComponentName getEffectsSuppressor();
diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java
index 2bdfa99..88399e5 100644
--- a/core/java/android/app/InstantAppResolverService.java
+++ b/core/java/android/app/InstantAppResolverService.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.InstantAppResolveInfo;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -28,9 +29,12 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
import com.android.internal.os.SomeArgs;
+import java.util.Arrays;
import java.util.List;
/**
@@ -39,6 +43,9 @@
*/
@SystemApi
public abstract class InstantAppResolverService extends Service {
+ private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE;
+ private static final String TAG = "PackageManager";
+
/** @hide */
public static final String EXTRA_RESOLVE_INFO = "android.app.extra.RESOLVE_INFO";
/** @hide */
@@ -132,11 +139,19 @@
@Deprecated
void _onGetInstantAppResolveInfo(int[] digestPrefix, String token,
InstantAppResolutionCallback callback) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Instant resolver; getInstantAppResolveInfo;"
+ + " prefix: " + Arrays.toString(digestPrefix));
+ }
onGetInstantAppResolveInfo(digestPrefix, token, callback);
}
@Deprecated
void _onGetInstantAppIntentFilter(int digestPrefix[], String token, String hostName,
InstantAppResolutionCallback callback) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Instant resolver; getInstantAppIntentFilter;"
+ + " prefix: " + Arrays.toString(digestPrefix));
+ }
onGetInstantAppIntentFilter(digestPrefix, token, callback);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 3d66135..6d7486b 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -103,8 +103,7 @@
/**
* An activity that provides a user interface for adjusting notification preferences for its
- * containing application. Optional but recommended for apps that post
- * {@link android.app.Notification Notifications}.
+ * containing application.
*/
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES
@@ -113,11 +112,25 @@
/**
* Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
* contain a {@link NotificationChannel#getId() channel id} that can be used to narrow down
- * what in app notifications settings should be shown.
+ * what settings should be shown in the target app.
*/
public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
/**
+ * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
+ * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)}
+ * that can be used to narrow down what settings should be shown in the target app.
+ */
+ public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG";
+
+ /**
+ * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
+ * contain the id provided to {@link NotificationManager#notify(String, int, Notification)}
+ * that can be used to narrow down what settings should be shown in the target app.
+ */
+ public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID";
+
+ /**
* Use all default values (where applicable).
*/
public static final int DEFAULT_ALL = ~0;
@@ -1082,6 +1095,7 @@
private long mTimeout;
private String mShortcutId;
+ private CharSequence mSettingsText;
/**
* If this notification is being shown as a badge, always show as a number.
@@ -1851,6 +1865,10 @@
}
mBadgeIcon = parcel.readInt();
+
+ if (parcel.readInt() != 0) {
+ mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ }
}
@Override
@@ -1960,6 +1978,9 @@
that.mChannelId = this.mChannelId;
that.mTimeout = this.mTimeout;
+ that.mShortcutId = this.mShortcutId;
+ that.mBadgeIcon = this.mBadgeIcon;
+ that.mSettingsText = this.mSettingsText;
if (!heavy) {
that.lightenPayload(); // will clean out extras
@@ -2229,6 +2250,13 @@
}
parcel.writeInt(mBadgeIcon);
+
+ if (mSettingsText != null) {
+ parcel.writeInt(1);
+ TextUtils.writeToParcel(mSettingsText, parcel, flags);
+ } else {
+ parcel.writeInt(0);
+ }
}
/**
@@ -2458,6 +2486,14 @@
return mShortcutId;
}
+
+ /**
+ * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}.
+ */
+ public CharSequence getSettingsText() {
+ return mSettingsText;
+ }
+
/**
* The small icon representing this notification in the status bar and content view.
*
@@ -2887,6 +2923,24 @@
}
/**
+ * Provides text that will appear as a link to your application's settings.
+ *
+ * <p>This text does not appear within notification {@link Style templates} but may
+ * appear when the user uses an affordance to learn more about the notification.
+ * Additionally, this text will not appear unless you provide a valid link target by
+ * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}.
+ *
+ * <p>This text is meant to be concise description about what the user can customize
+ * when they click on this link. The recommended maximum length is 40 characters.
+ * @param text
+ * @return
+ */
+ public Builder setSettingsText(CharSequence text) {
+ mN.mSettingsText = safeCharSequence(text);
+ return this;
+ }
+
+ /**
* Set the remote input history.
*
* This should be set to the most recent inputs that have been sent
@@ -3793,9 +3847,9 @@
// Ambient view does not have these
bindHeaderText(contentView);
bindHeaderChronometerAndTime(contentView);
- bindExpandButton(contentView);
bindProfileBadge(contentView);
}
+ bindExpandButton(contentView);
}
private void bindExpandButton(RemoteViews contentView) {
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 489a0f0..d620a81 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -945,7 +945,7 @@
final ResourcesKey key = mResourceImpls.keyAt(i);
final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
- if (impl != null && key.mResDir.equals(assetPath)) {
+ if (impl != null && Objects.equals(key.mResDir, assetPath)) {
if (!ArrayUtils.contains(key.mLibDirs, libAsset)) {
final int newLibAssetCount = 1 +
(key.mLibDirs != null ? key.mLibDirs.length : 0);
diff --git a/core/java/android/service/resolver/ResolverTarget.aidl b/core/java/android/app/ServiceStartArgs.aidl
similarity index 88%
rename from core/java/android/service/resolver/ResolverTarget.aidl
rename to core/java/android/app/ServiceStartArgs.aidl
index 6cab2d4..fd2cf0f 100644
--- a/core/java/android/service/resolver/ResolverTarget.aidl
+++ b/core/java/android/app/ServiceStartArgs.aidl
@@ -14,9 +14,7 @@
* limitations under the License.
*/
-package android.service.resolver;
+package android.app;
-/**
- * @hide
- */
-parcelable ResolverTarget;
+/** @hide */
+parcelable ServiceStartArgs;
diff --git a/core/java/android/app/ServiceStartArgs.java b/core/java/android/app/ServiceStartArgs.java
new file mode 100644
index 0000000..f030cba
--- /dev/null
+++ b/core/java/android/app/ServiceStartArgs.java
@@ -0,0 +1,82 @@
+/*
+ * 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.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Describes a Service.onStartCommand() request from the system.
+ * @hide
+ */
+public class ServiceStartArgs implements Parcelable {
+ final public boolean taskRemoved;
+ final public int startId;
+ final public int flags;
+ final public Intent args;
+
+ public ServiceStartArgs(boolean _taskRemoved, int _startId, int _flags, Intent _args) {
+ taskRemoved = _taskRemoved;
+ startId = _startId;
+ flags = _flags;
+ args = _args;
+ }
+
+ public String toString() {
+ return "ServiceStartArgs{taskRemoved=" + taskRemoved + ", startId=" + startId
+ + ", flags=0x" + Integer.toHexString(flags) + ", args=" + args + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(taskRemoved ? 1 : 0);
+ out.writeInt(startId);
+ out.writeInt(flags);
+ if (args != null) {
+ out.writeInt(1);
+ args.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+ }
+
+ public static final Parcelable.Creator<ServiceStartArgs> CREATOR
+ = new Parcelable.Creator<ServiceStartArgs>() {
+ public ServiceStartArgs createFromParcel(Parcel in) {
+ return new ServiceStartArgs(in);
+ }
+
+ public ServiceStartArgs[] newArray(int size) {
+ return new ServiceStartArgs[size];
+ }
+ };
+
+ public ServiceStartArgs(Parcel in) {
+ taskRemoved = in.readInt() != 0;
+ startId = in.readInt();
+ flags = in.readInt();
+ if (in.readInt() != 0) {
+ args = Intent.CREATOR.createFromParcel(in);
+ } else {
+ args = null;
+ }
+ }
+}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 6b05bdf..4572578 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -74,7 +74,9 @@
import android.net.EthernetManager;
import android.net.IConnectivityManager;
import android.net.IEthernetManager;
+import android.net.IIpSecService;
import android.net.INetworkPolicyManager;
+import android.net.IpSecManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
import android.net.nsd.INsdManager;
@@ -261,6 +263,15 @@
return new ConnectivityManager(context, service);
}});
+ registerService(Context.IPSEC_SERVICE, IpSecManager.class,
+ new StaticServiceFetcher<IpSecManager>() {
+ @Override
+ public IpSecManager createService() {
+ IBinder b = ServiceManager.getService(Context.IPSEC_SERVICE);
+ IIpSecService service = IIpSecService.Stub.asInterface(b);
+ return new IpSecManager(service);
+ }});
+
registerService(Context.COUNTRY_DETECTOR, CountryDetector.class,
new StaticServiceFetcher<CountryDetector>() {
@Override
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 2f0a630..82ad825 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -697,8 +697,8 @@
"android.app.extra.PROVISIONING_ORGANIZATION_NAME";
/**
- * A String extra holding a url to the website of the device's provider. The website can be
- * opened in a browser during provisioning.
+ * A String extra holding a url to the website of the device provider so the user can open it
+ * during provisioning. If the url is not HTTPS, an error will be shown.
*
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
*
@@ -2768,9 +2768,11 @@
* or clears the lockscreen password.
* <p>
* <em>This token is highly sensitive and should be treated at the same level as user
- * credentials. In particular, NEVER store this token on device in plaintext, especially in
- * Device-Encrypted storage if the token will be used to reset password on FBE devices before
- * user unlocks.
+ * credentials. In particular, NEVER store this token on device in plaintext. Do not store
+ * the plaintext token in device-encrypted storage if it will be needed to reset password on
+ * file-based encryption devices before user unlocks. Consider carefully how any password token
+ * will be stored on your server and who will need access to them. Tokens may be the subject of
+ * legal access requests.
* </em>
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 7d2db5c..545aef5 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -27,9 +27,6 @@
import android.view.ViewStructure.HtmlInfo.Builder;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.view.autofill.AutoFillId;
-import android.view.autofill.AutoFillType;
-import android.view.autofill.AutoFillValue;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
@@ -602,6 +599,10 @@
boolean mSanitized;
HtmlInfo mHtmlInfo;
+ // POJO used to override some autofill-related values when the node is parcelized.
+ // Not written to parcel.
+ AutofillOverlay mAutofillOverlay;
+
int mX;
int mY;
int mScrollX;
@@ -759,6 +760,7 @@
boolean writeSensitive = true;
int flags = mFlags & ~FLAGS_ALL_CONTROL;
+
if (mId != View.NO_ID) {
flags |= FLAGS_HAS_ID;
}
@@ -813,6 +815,13 @@
// Remove 'checked' from sanitized autofill request.
writtenFlags = flags & ~FLAGS_CHECKED;
}
+ if (mAutofillOverlay != null) {
+ if (mAutofillOverlay.focused) {
+ writtenFlags |= ViewNode.FLAGS_FOCUSED;
+ } else {
+ writtenFlags &= ~ViewNode.FLAGS_FOCUSED;
+ }
+ }
out.writeInt(writtenFlags);
if ((flags&FLAGS_HAS_ID) != 0) {
@@ -832,7 +841,14 @@
out.writeParcelable(mAutofillId, 0);
out.writeInt(mAutofillType);
out.writeStringArray(mAutofillHints);
- final AutofillValue sanitizedValue = writeSensitive ? mAutofillValue : null;
+ final AutofillValue sanitizedValue;
+ if (mAutofillOverlay != null && mAutofillOverlay.value != null) {
+ sanitizedValue = mAutofillOverlay.value;
+ } else if (writeSensitive) {
+ sanitizedValue = mAutofillValue;
+ } else {
+ sanitizedValue = null;
+ }
out.writeParcelable(sanitizedValue, 0);
out.writeStringArray(mAutofillOptions);
if (mHtmlInfo instanceof Parcelable) {
@@ -920,15 +936,6 @@
}
/**
- * @hide
- * @deprecated TODO(b/35956626): remove once clients use getAutoFilltype
- */
- @Deprecated
- public AutoFillId getAutoFillId() {
- return AutoFillId.forDaRealId(mAutofillId);
- }
-
- /**
* Gets the id that can be used to autofill the view contents.
*
* <p>It's only set when the {@link AssistStructure} is used for autofilling purposes, not
@@ -939,26 +946,6 @@
}
/**
- * @hide
- * @deprecated TODO(b/35956626): remove once clients use getAutoFilltype()
- */
- @Deprecated
- public AutoFillType getAutoFillType() {
- switch (getAutofillType()) {
- case View.AUTOFILL_TYPE_TEXT:
- return AutoFillType.forText();
- case View.AUTOFILL_TYPE_TOGGLE:
- return AutoFillType.forToggle();
- case View.AUTOFILL_TYPE_LIST:
- return AutoFillType.forList();
- case View.AUTOFILL_TYPE_DATE:
- return AutoFillType.forDate();
- default:
- return null;
- }
- }
-
- /**
* Gets the the type of value that can be used to autofill the view contents.
*
* <p>It's only set when the {@link AssistStructure} is used for autofilling purposes, not
@@ -982,15 +969,6 @@
}
/**
- * @hide
- * @deprecated TODO(b/35956626): remove once clients use getAutoFilltype
- */
- @Deprecated
- public AutoFillValue getAutoFillValue() {
- return AutoFillValue.forDaRealValue(mAutofillValue);
- }
-
- /**
* Gets the the value of this view.
*
* <p>It's only set when the {@link AssistStructure} is used for autofilling purposes, not
@@ -1000,6 +978,11 @@
return mAutofillValue;
}
+ /** @hide **/
+ public void setAutofillOverlay(AutofillOverlay overlay) {
+ mAutofillOverlay = overlay;
+ }
+
/**
* Gets the options that can be used to autofill this structure.
*
@@ -1381,6 +1364,16 @@
}
}
+ /**
+ * POJO used to override some autofill-related values when the node is parcelized.
+ *
+ * @hide
+ */
+ static public class AutofillOverlay {
+ public boolean focused;
+ public AutofillValue value;
+ }
+
static class ViewNodeBuilder extends ViewStructure {
final AssistStructure mAssist;
final ViewNode mNode;
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 74a39e8..9f35e85 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -710,9 +710,9 @@
* user may have a corporate profile. In this case the parent user profile has a
* child profile, the corporate one.
*
- * @param profile The profile for which to get providers. Passing null is equivaled
- * to passing only the current user handle.
- * @return The intalled providers.
+ * @param profile The profile for which to get providers. Passing null is equivalent
+ * to querying for only the calling user.
+ * @return The installed providers.
*
* @see android.os.Process#myUserHandle()
* @see android.os.UserManager#getUserProfiles()
@@ -722,7 +722,31 @@
return Collections.emptyList();
}
return getInstalledProvidersForProfile(AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
- profile);
+ profile, null);
+ }
+
+ /**
+ * Gets the AppWidget providers for the given package and user profile. User
+ * profile can only be the current user or a profile of the current user. For
+ * example, the current user may have a corporate profile. In this case the
+ * parent user profile has a child profile, the corporate one.
+ *
+ * @param packageName The package for which to get providers. If null, this method is
+ * equivalent to {@link #getInstalledProvidersForProfile(UserHandle)}.
+ * @param profile The profile for which to get providers. Passing null is equivalent
+ * to querying for only the calling user.
+ * @return The installed providers.
+ *
+ * @see android.os.Process#myUserHandle()
+ * @see android.os.UserManager#getUserProfiles()
+ */
+ public List<AppWidgetProviderInfo> getInstalledProvidersForPackage(@Nullable String packageName,
+ @Nullable UserHandle profile) {
+ if (mService == null) {
+ return Collections.emptyList();
+ }
+ return getInstalledProvidersForProfile(AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
+ profile, packageName);
}
/**
@@ -733,7 +757,7 @@
return Collections.emptyList();
}
return getInstalledProvidersForProfile(AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
- null);
+ null, null);
}
/**
@@ -752,7 +776,7 @@
if (mService == null) {
return Collections.emptyList();
}
- return getInstalledProvidersForProfile(categoryFilter, null);
+ return getInstalledProvidersForProfile(categoryFilter, null, null);
}
/**
@@ -766,6 +790,7 @@
* @param profile A profile of the current user which to be queried. The user
* is itself also a profile. If null, the providers only for the current user
* are returned.
+ * @param packageName If specified, will only return providers from the given package.
* @return The intalled providers.
*
* @see android.os.Process#myUserHandle()
@@ -774,7 +799,7 @@
* @hide
*/
public List<AppWidgetProviderInfo> getInstalledProvidersForProfile(int categoryFilter,
- UserHandle profile) {
+ @Nullable UserHandle profile, @Nullable String packageName) {
if (mService == null) {
return Collections.emptyList();
}
@@ -785,7 +810,7 @@
try {
ParceledListSlice<AppWidgetProviderInfo> providers = mService.getInstalledProvidersForProfile(
- categoryFilter, profile.getIdentifier());
+ categoryFilter, profile.getIdentifier(), packageName);
if (providers == null) {
return Collections.emptyList();
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index da887af..5415eb5 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1505,22 +1505,38 @@
public static final String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
/**
- * Activity Action: Launch ephemeral installer.
- * <p>
- * Input: The data must be a http: URI that the ephemeral application is registered
- * to handle.
+ * @hide
+ * @deprecated Do not use. This will go away.
+ * Replace with {@link #ACTION_INSTALL_INSTANT_APP_PACKAGE}.
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_INSTALL_EPHEMERAL_PACKAGE
+ = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE";
+ /**
+ * Activity Action: Launch instant application installer.
* <p class="note">
* This is a protected intent that can only be sent by the system.
* </p>
*
* @hide
*/
+ @SystemApi
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_INSTALL_EPHEMERAL_PACKAGE
- = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE";
+ public static final String ACTION_INSTALL_INSTANT_APP_PACKAGE
+ = "android.intent.action.INSTALL_INSTANT_APP_PACKAGE";
/**
- * Service Action: Resolve ephemeral application.
+ * @hide
+ * @deprecated Do not use. This will go away.
+ * Replace with {@link #ACTION_RESOLVE_INSTANT_APP_PACKAGE}.
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String ACTION_RESOLVE_EPHEMERAL_PACKAGE
+ = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE";
+ /**
+ * Service Action: Resolve instant application.
* <p>
* The system will have a persistent connection to this service.
* This is a protected intent that can only be sent by the system.
@@ -1528,12 +1544,22 @@
*
* @hide
*/
+ @SystemApi
@SdkConstant(SdkConstantType.SERVICE_ACTION)
- public static final String ACTION_RESOLVE_EPHEMERAL_PACKAGE
- = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE";
+ public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE
+ = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
/**
- * Activity Action: Launch ephemeral settings.
+ * @hide
+ * @deprecated Do not use. This will go away.
+ * Replace with {@link #ACTION_INSTANT_APP_RESOLVER_SETTINGS}.
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_EPHEMERAL_RESOLVER_SETTINGS
+ = "android.intent.action.EPHEMERAL_RESOLVER_SETTINGS";
+ /**
+ * Activity Action: Launch instant app settings.
*
* <p class="note">
* This is a protected intent that can only be sent by the system.
@@ -1541,9 +1567,10 @@
*
* @hide
*/
+ @SystemApi
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_EPHEMERAL_RESOLVER_SETTINGS
- = "android.intent.action.EPHEMERAL_RESOLVER_SETTINGS";
+ public static final String ACTION_INSTANT_APP_RESOLVER_SETTINGS
+ = "android.intent.action.INSTANT_APP_RESOLVER_SETTINGS";
/**
* Used as a string extra field with {@link #ACTION_INSTALL_PACKAGE} to install a
@@ -3479,6 +3506,261 @@
public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
/**
+ * Broadcast Action: indicate that the phone service state has changed.
+ * The intent will have the following extra values:</p>
+ * <p>
+ * @see #EXTRA_VOICE_REG_STATE
+ * @see #EXTRA_DATA_REG_STATE
+ * @see #EXTRA_VOICE_ROAMING_TYPE
+ * @see #EXTRA_DATA_ROAMING_TYPE
+ * @see #EXTRA_OPERATOR_ALPHA_LONG
+ * @see #EXTRA_OPERATOR_ALPHA_SHORT
+ * @see #EXTRA_OPERATOR_NUMERIC
+ * @see #EXTRA_DATA_OPERATOR_ALPHA_LONG
+ * @see #EXTRA_DATA_OPERATOR_ALPHA_SHORT
+ * @see #EXTRA_DATA_OPERATOR_NUMERIC
+ * @see #EXTRA_MANUAL
+ * @see #EXTRA_VOICE_RADIO_TECH
+ * @see #EXTRA_DATA_RADIO_TECH
+ * @see #EXTRA_CSS_INDICATOR
+ * @see #EXTRA_NETWORK_ID
+ * @see #EXTRA_SYSTEM_ID
+ * @see #EXTRA_CDMA_ROAMING_INDICATOR
+ * @see #EXTRA_CDMA_DEFAULT_ROAMING_INDICATOR
+ * @see #EXTRA_EMERGENCY_ONLY
+ * @see #EXTRA_IS_DATA_ROAMING_FROM_REGISTRATION
+ * @see #EXTRA_IS_USING_CARRIER_AGGREGATION
+ * @see #EXTRA_LTE_EARFCN_RSRP_BOOST
+ *
+ * <p class="note">
+ * Requires the READ_PHONE_STATE permission.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SERVICE_STATE = "android.intent.action.SERVICE_STATE";
+
+ /**
+ * An int extra used with {@link #ACTION_SERVICE_STATE} which indicates voice registration
+ * state.
+ * @see android.telephony.ServiceState#STATE_EMERGENCY_ONLY
+ * @see android.telephony.ServiceState#STATE_IN_SERVICE
+ * @see android.telephony.ServiceState#STATE_OUT_OF_SERVICE
+ * @see android.telephony.ServiceState#STATE_POWER_OFF
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_VOICE_REG_STATE = "voiceRegState";
+
+ /**
+ * An int extra used with {@link #ACTION_SERVICE_STATE} which indicates data registration state.
+ * @see android.telephony.ServiceState#STATE_EMERGENCY_ONLY
+ * @see android.telephony.ServiceState#STATE_IN_SERVICE
+ * @see android.telephony.ServiceState#STATE_OUT_OF_SERVICE
+ * @see android.telephony.ServiceState#STATE_POWER_OFF
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_DATA_REG_STATE = "dataRegState";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} which indicates the voice roaming
+ * type.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_VOICE_ROAMING_TYPE = "voiceRoamingType";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} which indicates the data roaming
+ * type.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_DATA_ROAMING_TYPE = "dataRoamingType";
+
+ /**
+ * A string extra used with {@link #ACTION_SERVICE_STATE} which represents the current
+ * registered voice operator name in long alphanumeric format.
+ * {@code null} if the operator name is not known or unregistered.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_OPERATOR_ALPHA_LONG = "operator-alpha-long";
+
+ /**
+ * A string extra used with {@link #ACTION_SERVICE_STATE} which represents the current
+ * registered voice operator name in short alphanumeric format.
+ * {@code null} if the operator name is not known or unregistered.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_OPERATOR_ALPHA_SHORT = "operator-alpha-short";
+
+ /**
+ * A string extra used with {@link #ACTION_SERVICE_STATE} containing the MCC
+ * (Mobile Country Code, 3 digits) and MNC (Mobile Network code, 2-3 digits) for the mobile
+ * network.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_OPERATOR_NUMERIC = "operator-numeric";
+
+ /**
+ * A string extra used with {@link #ACTION_SERVICE_STATE} which represents the current
+ * registered data operator name in long alphanumeric format.
+ * {@code null} if the operator name is not known or unregistered.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_DATA_OPERATOR_ALPHA_LONG = "data-operator-alpha-long";
+
+ /**
+ * A string extra used with {@link #ACTION_SERVICE_STATE} which represents the current
+ * registered data operator name in short alphanumeric format.
+ * {@code null} if the operator name is not known or unregistered.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_DATA_OPERATOR_ALPHA_SHORT = "data-operator-alpha-short";
+
+ /**
+ * A string extra used with {@link #ACTION_SERVICE_STATE} containing the MCC
+ * (Mobile Country Code, 3 digits) and MNC (Mobile Network code, 2-3 digits) for the
+ * data operator.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_DATA_OPERATOR_NUMERIC = "data-operator-numeric";
+
+ /**
+ * A boolean extra used with {@link #ACTION_SERVICE_STATE} which indicates whether the current
+ * network selection mode is manual.
+ * Will be {@code true} if manual mode, {@code false} if automatic mode.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_MANUAL = "manual";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} which represents the current voice
+ * radio technology.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_VOICE_RADIO_TECH = "radioTechnology";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} which represents the current data
+ * radio technology.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_DATA_RADIO_TECH = "dataRadioTechnology";
+
+ /**
+ * A boolean extra used with {@link #ACTION_SERVICE_STATE} which represents concurrent service
+ * support on CDMA network.
+ * Will be {@code true} if support, {@code false} otherwise.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_CSS_INDICATOR = "cssIndicator";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} which represents the CDMA network
+ * id. {@code Integer.MAX_VALUE} if unknown.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_NETWORK_ID = "networkId";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} which represents the CDMA system id.
+ * {@code Integer.MAX_VALUE} if unknown.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_SYSTEM_ID = "systemId";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} represents the TSB-58 roaming
+ * indicator if registered on a CDMA or EVDO system or {@code -1} if not.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_CDMA_ROAMING_INDICATOR = "cdmaRoamingIndicator";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} represents the default roaming
+ * indicator from the PRL if registered on a CDMA or EVDO system {@code -1} if not.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_CDMA_DEFAULT_ROAMING_INDICATOR = "cdmaDefaultRoamingIndicator";
+
+ /**
+ * A boolean extra used with {@link #ACTION_SERVICE_STATE} which indicates if under emergency
+ * only mode.
+ * {@code true} if in emergency only mode, {@code false} otherwise.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_EMERGENCY_ONLY = "emergencyOnly";
+
+ /**
+ * A boolean extra used with {@link #ACTION_SERVICE_STATE} which indicates whether data network
+ * registration state is roaming.
+ * {@code true} if registration indicates roaming, {@code false} otherwise
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_IS_DATA_ROAMING_FROM_REGISTRATION =
+ "isDataRoamingFromRegistration";
+
+ /**
+ * A boolean extra used with {@link #ACTION_SERVICE_STATE} which indicates if carrier
+ * aggregation is in use.
+ * {@code true} if carrier aggregation is in use, {@code false} otherwise.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_IS_USING_CARRIER_AGGREGATION = "isUsingCarrierAggregation";
+
+ /**
+ * An integer extra used with {@link #ACTION_SERVICE_STATE} representing the offset which
+ * is reduced from the rsrp threshold while calculating signal strength level.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_LTE_EARFCN_RSRP_BOOST = "LteEarfcnRsrpBoost";
+
+ /**
* The name of the extra used to define the text to be processed, as a
* CharSequence. Note that this may be a styled CharSequence, so you must use
* {@link Bundle#getCharSequence(String) Bundle.getCharSequence()} to retrieve it.
diff --git a/core/java/android/content/pm/BaseParceledListSlice.java b/core/java/android/content/pm/BaseParceledListSlice.java
index c4e4e06..aaa5f19c 100644
--- a/core/java/android/content/pm/BaseParceledListSlice.java
+++ b/core/java/android/content/pm/BaseParceledListSlice.java
@@ -50,6 +50,8 @@
private final List<T> mList;
+ private int mInlineCountLimit = Integer.MAX_VALUE;
+
public BaseParceledListSlice(List<T> list) {
mList = list;
}
@@ -135,6 +137,14 @@
}
/**
+ * Set a limit on the maximum number of entries in the array that will be included
+ * inline in the initial parcelling of this object.
+ */
+ public void setInlineCountLimit(int maxCount) {
+ mInlineCountLimit = maxCount;
+ }
+
+ /**
* Write this to another Parcel. Note that this discards the internal Parcel
* and should not be used anymore. This is so we can pass this to a Binder
* where we won't have a chance to call recycle on this.
@@ -149,7 +159,7 @@
final Class<?> listElementClass = mList.get(0).getClass();
writeParcelableCreator(mList.get(0), dest);
int i = 0;
- while (i < N && dest.dataSize() < MAX_IPC_SIZE) {
+ while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) {
dest.writeInt(1);
final T parcelable = mList.get(i);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a493f33..50e3e68 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1688,6 +1688,10 @@
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
+ /** {@hide} */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CTS = "android.software.cts";
+
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports one or more methods of
@@ -6243,7 +6247,7 @@
* Return the {@link ComponentName} of the activity providing Settings for the Instant App
* resolver.
*
- * @see {@link android.content.intent#ACTION_EPHEMERAL_RESOLVER_SETTINGS}
+ * @see {@link android.content.intent#ACTION_INSTANT_APP_RESOLVER_SETTINGS}
* @hide
*/
@SystemApi
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 9457d15..940447c 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3114,6 +3114,7 @@
if ((perm.info.protectionLevel&PermissionInfo.PROTECTION_MASK_FLAGS) != 0) {
if ( (perm.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_EPHEMERAL) == 0
+ && (perm.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) == 0
&& (perm.info.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE) !=
PermissionInfo.PROTECTION_SIGNATURE) {
outError[0] = "<permission> protectionLevel specifies a non-ephemeral flag but is "
@@ -6172,6 +6173,7 @@
cpuAbiOverride = dest.readString();
use32bitAbi = (dest.readInt() == 1);
restrictUpdateHash = dest.createByteArray();
+ visibleToInstantApps = dest.readInt() == 1;
}
private static void internStringArrayList(List<String> list) {
@@ -6286,6 +6288,7 @@
dest.writeString(cpuAbiOverride);
dest.writeInt(use32bitAbi ? 1 : 0);
dest.writeByteArray(restrictUpdateHash);
+ dest.writeInt(visibleToInstantApps ? 1 : 0);
}
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 0703138..694e607 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -131,6 +131,13 @@
public static final int PROTECTION_FLAG_EPHEMERAL = 0x1000;
/**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>runtime</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ */
+ public static final int PROTECTION_FLAG_RUNTIME_ONLY = 0x2000;
+
+ /**
* Mask for {@link #protectionLevel}: the basic protection type.
*/
public static final int PROTECTION_MASK_BASE = 0xf;
@@ -250,6 +257,9 @@
if ((level&PermissionInfo.PROTECTION_FLAG_EPHEMERAL) != 0) {
protLevel += "|ephemeral";
}
+ if ((level&PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0) {
+ protLevel += "|runtime";
+ }
return protLevel;
}
diff --git a/core/java/android/hardware/SensorAdditionalInfo.java b/core/java/android/hardware/SensorAdditionalInfo.java
index 572a287..ea1d01b 100644
--- a/core/java/android/hardware/SensorAdditionalInfo.java
+++ b/core/java/android/hardware/SensorAdditionalInfo.java
@@ -131,6 +131,64 @@
*/
public static final int TYPE_SAMPLING = 0x10004;
+ /**
+ * Local geo-magnetic Field.
+ *
+ * Additional into to sensor hardware. Local geomagnetic field information based on
+ * device geo location. This type is primarily for for magnetic field calibration and rotation
+ * vector sensor fusion.
+ *
+ * float[3]: strength (uT), declination and inclination angle (rad).
+ * @hide
+ */
+ public static final int TYPE_LOCAL_GEOMAGNETIC_FIELD = 0x30000;
+
+ /**
+ * Local gravity acceleration strength.
+ *
+ * Additional info to sensor hardware for accelerometer calibration.
+ *
+ * float: gravitational acceleration norm in m/s^2.
+ * @hide
+ */
+ public static final int TYPE_LOCAL_GRAVITY = 0x30001;
+
+ /**
+ * Device dock state.
+ *
+ * Additional info to sensor hardware indicating dock states of device.
+ *
+ * int32_t: dock state following definition of {@link android.content.Intent#EXTRA_DOCK_STATE}.
+ * Undefined values are ignored.
+ * @hide
+ */
+ public static final int TYPE_DOCK_STATE = 0x30002;
+
+ /**
+ * High performance mode.
+ *
+ * Additional info to sensor hardware. Device is able to use up more power and take more
+ * resources to improve throughput and latency in high performance mode. One possible use case
+ * is virtual reality, when sensor latency need to be carefully controlled.
+ *
+ * int32_t: 1 or 0, denoting device is in or out of high performance mode, respectively.
+ * Other values are ignored.
+ * @hide
+ */
+ public static final int TYPE_HIGH_PERFORMANCE_MODE = 0x30003;
+
+ /**
+ * Magnetic field calibration hint.
+ *
+ * Additional info to sensor hardware. Device is notified when manually triggered magnetic field
+ * calibration procedure is started or stopped. The calibration procedure is assumed timed out
+ * after 1 minute from start, even if an explicit stop is not received.
+ *
+ * int32_t: 1 for calibration start, 0 for stop, other values are ignored.
+ * @hide
+ */
+ public static final int TYPE_MAGNETIC_FIELD_CALIBRATION = 0x30004;
+
SensorAdditionalInfo(
Sensor aSensor, int aType, int aSerial, int [] aIntValues, float [] aFloatValues) {
sensor = aSensor;
@@ -139,4 +197,18 @@
intValues = aIntValues;
floatValues = aFloatValues;
}
+
+ /** @hide */
+ public static SensorAdditionalInfo createLocalGeomagneticField(
+ float strength, float declination, float inclination) {
+ if (strength < 10 || strength > 100 // much beyond extreme values on earth
+ || declination < 0 || declination > Math.PI
+ || inclination < -Math.PI / 2 || inclination > Math.PI / 2) {
+ throw new IllegalArgumentException("Geomagnetic field info out of range");
+ }
+
+ return new SensorAdditionalInfo(
+ null, TYPE_LOCAL_GEOMAGNETIC_FIELD, 0,
+ null, new float[] { strength, declination, inclination});
+ }
}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index a6930b0..1dc6478 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -1927,4 +1927,12 @@
}
return delay;
}
+
+ /** @hide */
+ public boolean setOperationParameter(SensorAdditionalInfo parameter) {
+ return setOperationParameterImpl(parameter);
+ }
+
+ /** @hide */
+ protected abstract boolean setOperationParameterImpl(SensorAdditionalInfo parameter);
}
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 7029847..0677179 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -67,6 +67,9 @@
private static native int nativeConfigDirectChannel(
long nativeInstance, int channelHandle, int sensorHandle, int rate);
+ private static native int nativeSetOperationParameter(
+ long nativeInstance, int type, float[] floatValues, int[] intValues);
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static boolean sNativeClassInited = false;
@@ -928,4 +931,9 @@
}
}
+
+ protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
+ return nativeSetOperationParameter(
+ mNativeInstance, parameter.type, parameter.floatValues, parameter.intValues) == 0;
+ }
}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 2aa6af6..46ad3f0 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -67,6 +67,15 @@
*
* @hide
*/
+ public Key(String name, Class<T> type, long vendorId) {
+ mKey = new CameraMetadataNative.Key<T>(name, type, vendorId);
+ }
+
+ /**
+ * Visible for testing and vendor extensions only.
+ *
+ * @hide
+ */
public Key(String name, Class<T> type) {
mKey = new CameraMetadataNative.Key<T>(name, type);
}
@@ -99,6 +108,15 @@
}
/**
+ * Return vendor tag id.
+ *
+ * @hide
+ */
+ public long getVendorId() {
+ return mKey.getVendorId();
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
@@ -159,6 +177,7 @@
*/
public CameraCharacteristics(CameraMetadataNative properties) {
mProperties = CameraMetadataNative.move(properties);
+ setNativeInstance(mProperties);
}
/**
@@ -227,7 +246,7 @@
}
mKeys = Collections.unmodifiableList(
- getKeysStatic(getClass(), getKeyClass(), this, filterTags));
+ getKeys(getClass(), getKeyClass(), this, filterTags));
return mKeys;
}
@@ -320,7 +339,7 @@
"metadataClass must be a subclass of CameraMetadata");
}
- List<TKey> staticKeyList = CameraCharacteristics.<TKey>getKeysStatic(
+ List<TKey> staticKeyList = getKeys(
metadataClass, keyClass, /*instance*/null, filterTags);
return Collections.unmodifiableList(staticKeyList);
}
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index e289627..8c8c49f 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -52,6 +52,7 @@
private static final String TAG = "CameraMetadataAb";
private static final boolean DEBUG = false;
+ private CameraMetadataNative mNativeInstance = null;
/**
* Set a camera metadata field to a value. The field definitions can be
@@ -89,6 +90,13 @@
/**
* @hide
*/
+ protected void setNativeInstance(CameraMetadataNative nativeInstance) {
+ mNativeInstance = nativeInstance;
+ }
+
+ /**
+ * @hide
+ */
protected abstract Class<TKey> getKeyClass();
/**
@@ -108,7 +116,7 @@
public List<TKey> getKeys() {
Class<CameraMetadata<TKey>> thisClass = (Class<CameraMetadata<TKey>>) getClass();
return Collections.unmodifiableList(
- getKeysStatic(thisClass, getKeyClass(), this, /*filterTags*/null));
+ getKeys(thisClass, getKeyClass(), this, /*filterTags*/null));
}
/**
@@ -126,7 +134,7 @@
* </p>
*/
/*package*/ @SuppressWarnings("unchecked")
- static <TKey> ArrayList<TKey> getKeysStatic(
+ <TKey> ArrayList<TKey> getKeys(
Class<?> type, Class<TKey> keyClass,
CameraMetadata<TKey> instance,
int[] filterTags) {
@@ -173,23 +181,31 @@
}
}
- ArrayList<TKey> vendorKeys = CameraMetadataNative.getAllVendorKeys(keyClass);
+ if (null == mNativeInstance) {
+ return keyList;
+ }
+
+ ArrayList<TKey> vendorKeys = mNativeInstance.getAllVendorKeys(keyClass);
if (vendorKeys != null) {
for (TKey k : vendorKeys) {
String keyName;
+ long vendorId;
if (k instanceof CaptureRequest.Key<?>) {
keyName = ((CaptureRequest.Key<?>) k).getName();
+ vendorId = ((CaptureRequest.Key<?>) k).getVendorId();
} else if (k instanceof CaptureResult.Key<?>) {
keyName = ((CaptureResult.Key<?>) k).getName();
+ vendorId = ((CaptureResult.Key<?>) k).getVendorId();
} else if (k instanceof CameraCharacteristics.Key<?>) {
keyName = ((CameraCharacteristics.Key<?>) k).getName();
+ vendorId = ((CameraCharacteristics.Key<?>) k).getVendorId();
} else {
continue;
}
if (filterTags == null || Arrays.binarySearch(filterTags,
- CameraMetadataNative.getTag(keyName)) >= 0) {
+ CameraMetadataNative.getTag(keyName, vendorId)) >= 0) {
keyList.add(k);
}
}
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 12b46c1..1cf8f03 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -101,6 +101,15 @@
*
* @hide
*/
+ public Key(String name, Class<T> type, long vendorId) {
+ mKey = new CameraMetadataNative.Key<T>(name, type, vendorId);
+ }
+
+ /**
+ * Visible for testing and vendor extensions only.
+ *
+ * @hide
+ */
public Key(String name, Class<T> type) {
mKey = new CameraMetadataNative.Key<T>(name, type);
}
@@ -133,6 +142,15 @@
}
/**
+ * Return vendor tag id.
+ *
+ * @hide
+ */
+ public long getVendorId() {
+ return mKey.getVendorId();
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
@@ -199,6 +217,7 @@
*/
private CaptureRequest() {
mSettings = new CameraMetadataNative();
+ setNativeInstance(mSettings);
mSurfaceSet = new HashSet<Surface>();
mIsReprocess = false;
mReprocessableSessionId = CameraCaptureSession.SESSION_ID_NONE;
@@ -212,6 +231,7 @@
@SuppressWarnings("unchecked")
private CaptureRequest(CaptureRequest source) {
mSettings = new CameraMetadataNative(source.mSettings);
+ setNativeInstance(mSettings);
mSurfaceSet = (HashSet<Surface>) source.mSurfaceSet.clone();
mIsReprocess = source.mIsReprocess;
mIsPartOfCHSRequestList = source.mIsPartOfCHSRequestList;
@@ -242,6 +262,7 @@
private CaptureRequest(CameraMetadataNative settings, boolean isReprocess,
int reprocessableSessionId) {
mSettings = CameraMetadataNative.move(settings);
+ setNativeInstance(mSettings);
mSurfaceSet = new HashSet<Surface>();
mIsReprocess = isReprocess;
if (isReprocess) {
@@ -441,6 +462,7 @@
*/
private void readFromParcel(Parcel in) {
mSettings.readFromParcel(in);
+ setNativeInstance(mSettings);
mSurfaceSet.clear();
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 3f8b57a..419e3e2 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -78,6 +78,15 @@
*
* @hide
*/
+ public Key(String name, Class<T> type, long vendorId) {
+ mKey = new CameraMetadataNative.Key<T>(name, type, vendorId);
+ }
+
+ /**
+ * Visible for testing and vendor extensions only.
+ *
+ * @hide
+ */
public Key(String name, Class<T> type) {
mKey = new CameraMetadataNative.Key<T>(name, type);
}
@@ -110,6 +119,15 @@
}
/**
+ * Return vendor tag id.
+ *
+ * @hide
+ */
+ public long getVendorId() {
+ return mKey.getVendorId();
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
@@ -186,6 +204,7 @@
if (mResults.isEmpty()) {
throw new AssertionError("Results must not be empty");
}
+ setNativeInstance(mResults);
mRequest = parent;
mSequenceId = extras.getRequestId();
mFrameNumber = extras.getFrameNumber();
@@ -215,6 +234,7 @@
throw new AssertionError("Results must not be empty");
}
+ setNativeInstance(mResults);
mRequest = null;
mSequenceId = sequenceId;
mFrameNumber = -1;
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 4d92ab1cc..ebe2fa1 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -79,10 +79,28 @@
public static class Key<T> {
private boolean mHasTag;
private int mTag;
+ private long mVendorId = Long.MAX_VALUE;
private final Class<T> mType;
private final TypeReference<T> mTypeReference;
private final String mName;
private final int mHash;
+
+ /**
+ * @hide
+ */
+ public Key(String name, Class<T> type, long vendorId) {
+ if (name == null) {
+ throw new NullPointerException("Key needs a valid name");
+ } else if (type == null) {
+ throw new NullPointerException("Type needs to be non-null");
+ }
+ mName = name;
+ mType = type;
+ mVendorId = vendorId;
+ mTypeReference = TypeReference.createSpecializedTypeReference(type);
+ mHash = mName.hashCode() ^ mTypeReference.hashCode();
+ }
+
/**
* Visible for testing only.
*
@@ -194,7 +212,7 @@
*/
public final int getTag() {
if (!mHasTag) {
- mTag = CameraMetadataNative.getTag(mName);
+ mTag = CameraMetadataNative.getTag(mName, mVendorId);
mHasTag = true;
}
return mTag;
@@ -212,6 +230,15 @@
}
/**
+ * Get the vendor tag provider id.
+ *
+ * @hide
+ */
+ public final long getVendorId() {
+ return mVendorId;
+ }
+
+ /**
* Get the type reference backing the type {@code T} for this key.
*
* <p>The distinction is only important if {@code T} is a generic, e.g.
@@ -463,13 +490,14 @@
}
private <T> T getBase(Key<T> key) {
- int tag = key.getTag();
+ int tag = nativeGetTagFromKeyLocal(key.getName());
byte[] values = readValues(tag);
if (values == null) {
return null;
}
- Marshaler<T> marshaler = getMarshalerForKey(key);
+ int nativeType = nativeGetTypeFromTagLocal(tag);
+ Marshaler<T> marshaler = getMarshalerForKey(key, nativeType);
ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder());
return marshaler.unmarshal(buffer);
}
@@ -947,15 +975,15 @@
}
private <T> void setBase(Key<T> key, T value) {
- int tag = key.getTag();
-
+ int tag = nativeGetTagFromKeyLocal(key.getName());
if (value == null) {
// Erase the entry
writeValues(tag, /*src*/null);
return;
} // else update the entry to a new value
- Marshaler<T> marshaler = getMarshalerForKey(key);
+ int nativeType = nativeGetTypeFromTagLocal(tag);
+ Marshaler<T> marshaler = getMarshalerForKey(key, nativeType);
int size = marshaler.calculateMarshalSize(value);
// TODO: Optimization. Cache the byte[] and reuse if the size is big enough.
@@ -1092,10 +1120,14 @@
private native synchronized void nativeWriteValues(int tag, byte[] src);
private native synchronized void nativeDump() throws IOException; // dump to ALOGD
- private static native ArrayList nativeGetAllVendorKeys(Class keyClass);
- private static native int nativeGetTagFromKey(String keyName)
+ private native synchronized ArrayList nativeGetAllVendorKeys(Class keyClass);
+ private native synchronized int nativeGetTagFromKeyLocal(String keyName)
throws IllegalArgumentException;
- private static native int nativeGetTypeFromTag(int tag)
+ private native synchronized int nativeGetTypeFromTagLocal(int tag)
+ throws IllegalArgumentException;
+ private static native int nativeGetTagFromKey(String keyName, long vendorId)
+ throws IllegalArgumentException;
+ private static native int nativeGetTypeFromTag(int tag, long vendorId)
throws IllegalArgumentException;
/**
@@ -1133,7 +1165,7 @@
*
* @hide
*/
- public static <K> ArrayList<K> getAllVendorKeys(Class<K> keyClass) {
+ public <K> ArrayList<K> getAllVendorKeys(Class<K> keyClass) {
if (keyClass == null) {
throw new NullPointerException();
}
@@ -1149,19 +1181,32 @@
* @hide
*/
public static int getTag(String key) {
- return nativeGetTagFromKey(key);
+ return nativeGetTagFromKey(key, Long.MAX_VALUE);
+ }
+
+ /**
+ * Convert a key string into the equivalent native tag.
+ *
+ * @throws IllegalArgumentException if the key was not recognized
+ * @throws NullPointerException if the key was null
+ *
+ * @hide
+ */
+ public static int getTag(String key, long vendorId) {
+ return nativeGetTagFromKey(key, vendorId);
}
/**
* Get the underlying native type for a tag.
*
* @param tag An integer tag, see e.g. {@link #getTag}
+ * @param vendorId A vendor tag provider id
* @return An int enum for the metadata type, see e.g. {@link #TYPE_BYTE}
*
* @hide
*/
- public static int getNativeType(int tag) {
- return nativeGetTypeFromTag(tag);
+ public static int getNativeType(int tag, long vendorId) {
+ return nativeGetTypeFromTag(tag, vendorId);
}
/**
@@ -1226,9 +1271,9 @@
* @throws UnsupportedOperationException
* if the native/managed type combination for {@code key} is not supported
*/
- private static <T> Marshaler<T> getMarshalerForKey(Key<T> key) {
+ private static <T> Marshaler<T> getMarshalerForKey(Key<T> key, int nativeType) {
return MarshalRegistry.getMarshaler(key.getTypeReference(),
- getNativeType(key.getTag()));
+ nativeType);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
diff --git a/core/java/android/hardware/camera2/params/VendorTagDescriptorCache.java b/core/java/android/hardware/camera2/params/VendorTagDescriptorCache.java
new file mode 100644
index 0000000..1f92f6d
--- /dev/null
+++ b/core/java/android/hardware/camera2/params/VendorTagDescriptorCache.java
@@ -0,0 +1,67 @@
+/*
+ * 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.camera2.params;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * A class for describing the vendor tag cache declared by a camera HAL module.
+ * Generally only used by the native side of
+ * android.hardware.camera2.impl.CameraMetadataNative
+ *
+ * @hide
+ */
+public final class VendorTagDescriptorCache implements Parcelable {
+
+ private VendorTagDescriptorCache(Parcel source) {
+ }
+
+ public static final Parcelable.Creator<VendorTagDescriptorCache> CREATOR =
+ new Parcelable.Creator<VendorTagDescriptorCache>() {
+ @Override
+ public VendorTagDescriptorCache createFromParcel(Parcel source) {
+ try {
+ VendorTagDescriptorCache vendorDescriptorCache = new VendorTagDescriptorCache(source);
+ return vendorDescriptorCache;
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating VendorTagDescriptorCache from parcel", e);
+ return null;
+ }
+ }
+
+ @Override
+ public VendorTagDescriptorCache[] newArray(int size) {
+ return new VendorTagDescriptorCache[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (dest == null) {
+ throw new IllegalArgumentException("dest must not be null");
+ }
+ }
+
+ private static final String TAG = "VendorTagDescriptorCache";
+}
diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl
new file mode 100644
index 0000000..0aa3ce6
--- /dev/null
+++ b/core/java/android/net/IIpSecService.aidl
@@ -0,0 +1,46 @@
+/*
+** 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.net;
+
+import android.net.Network;
+import android.net.IpSecConfig;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * @hide
+ */
+interface IIpSecService
+{
+ Bundle reserveSecurityParameterIndex(
+ int direction, in String remoteAddress, int requestedSpi, in IBinder binder);
+
+ void releaseSecurityParameterIndex(int resourceId);
+
+ Bundle openUdpEncapsulationSocket(int port, in IBinder binder);
+
+ void closeUdpEncapsulationSocket(in ParcelFileDescriptor socket);
+
+ Bundle createTransportModeTransform(in IpSecConfig c, in IBinder binder);
+
+ void deleteTransportModeTransform(int transformId);
+
+ void applyTransportModeTransform(in ParcelFileDescriptor socket, int transformId);
+
+ void removeTransportModeTransform(in ParcelFileDescriptor socket, int transformId);
+}
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index da5cb37..7fea4a2 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -164,6 +164,8 @@
private static boolean isTruncationLengthValid(String algo, int truncLenBits) {
switch (algo) {
+ case ALGO_CRYPT_AES_CBC:
+ return (truncLenBits == 128 || truncLenBits == 192 || truncLenBits == 256);
case ALGO_AUTH_HMAC_MD5:
return (truncLenBits >= 96 && truncLenBits <= 128);
case ALGO_AUTH_HMAC_SHA1:
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index b58bf42..13dc19f 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -23,7 +23,7 @@
/** @hide */
public final class IpSecConfig implements Parcelable {
- private static final String TAG = IpSecConfig.class.getSimpleName();
+ private static final String TAG = "IpSecConfig";
//MODE_TRANSPORT or MODE_TUNNEL
int mode;
@@ -43,13 +43,13 @@
int spi;
// Encryption Algorithm
- IpSecAlgorithm encryptionAlgo;
+ IpSecAlgorithm encryption;
// Authentication Algorithm
- IpSecAlgorithm authenticationAlgo;
+ IpSecAlgorithm authentication;
}
- Flow[] flow = new Flow[2];
+ Flow[] flow = new Flow[] {new Flow(), new Flow()};
// For tunnel mode IPv4 UDP Encapsulation
// IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE
@@ -57,17 +57,15 @@
int encapLocalPort;
int encapRemotePort;
- // An optional protocol to match with the selector
- int selectorProto;
-
- // A bitmask of FEATURE_* indicating which of the fields
- // of this class are valid.
- long features;
-
// An interval, in seconds between the NattKeepalive packets
int nattKeepaliveInterval;
- public InetAddress getLocalIp() {
+ // Transport or Tunnel
+ public int getMode() {
+ return mode;
+ }
+
+ public InetAddress getLocalAddress() {
return localAddress;
}
@@ -75,19 +73,19 @@
return flow[direction].spi;
}
- public InetAddress getRemoteIp() {
+ public InetAddress getRemoteAddress() {
return remoteAddress;
}
- public IpSecAlgorithm getEncryptionAlgo(int direction) {
- return flow[direction].encryptionAlgo;
+ public IpSecAlgorithm getEncryption(int direction) {
+ return flow[direction].encryption;
}
- public IpSecAlgorithm getAuthenticationAlgo(int direction) {
- return flow[direction].authenticationAlgo;
+ public IpSecAlgorithm getAuthentication(int direction) {
+ return flow[direction].authentication;
}
- Network getNetwork() {
+ public Network getNetwork() {
return network;
}
@@ -103,18 +101,10 @@
return encapRemotePort;
}
- public int getSelectorProto() {
- return selectorProto;
- }
-
- int getNattKeepaliveInterval() {
+ public int getNattKeepaliveInterval() {
return nattKeepaliveInterval;
}
- public boolean hasProperty(int featureBits) {
- return (features & featureBits) == featureBits;
- }
-
// Parcelable Methods
@Override
@@ -124,31 +114,25 @@
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeLong(features);
// TODO: Use a byte array or other better method for storing IPs that can also include scope
out.writeString((localAddress != null) ? localAddress.getHostAddress() : null);
// TODO: Use a byte array or other better method for storing IPs that can also include scope
out.writeString((remoteAddress != null) ? remoteAddress.getHostAddress() : null);
out.writeParcelable(network, flags);
out.writeInt(flow[IpSecTransform.DIRECTION_IN].spi);
- out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryptionAlgo, flags);
- out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authenticationAlgo, flags);
+ out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryption, flags);
+ out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authentication, flags);
out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spi);
- out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryptionAlgo, flags);
- out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authenticationAlgo, flags);
+ out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryption, flags);
+ out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authentication, flags);
out.writeInt(encapType);
out.writeInt(encapLocalPort);
out.writeInt(encapRemotePort);
- out.writeInt(selectorProto);
}
// Package Private: Used by the IpSecTransform.Builder;
// there should be no public constructor for this object
- IpSecConfig() {
- flow[IpSecTransform.DIRECTION_IN].spi = 0;
- flow[IpSecTransform.DIRECTION_OUT].spi = 0;
- nattKeepaliveInterval = 0; //FIXME constant
- }
+ IpSecConfig() {}
private static InetAddress readInetAddressFromParcel(Parcel in) {
String addrString = in.readString();
@@ -164,24 +148,22 @@
}
private IpSecConfig(Parcel in) {
- features = in.readLong();
localAddress = readInetAddressFromParcel(in);
remoteAddress = readInetAddressFromParcel(in);
network = (Network) in.readParcelable(Network.class.getClassLoader());
flow[IpSecTransform.DIRECTION_IN].spi = in.readInt();
- flow[IpSecTransform.DIRECTION_IN].encryptionAlgo =
+ flow[IpSecTransform.DIRECTION_IN].encryption =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
- flow[IpSecTransform.DIRECTION_IN].authenticationAlgo =
+ flow[IpSecTransform.DIRECTION_IN].authentication =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
flow[IpSecTransform.DIRECTION_OUT].spi = in.readInt();
- flow[IpSecTransform.DIRECTION_OUT].encryptionAlgo =
+ flow[IpSecTransform.DIRECTION_OUT].encryption =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
- flow[IpSecTransform.DIRECTION_OUT].authenticationAlgo =
+ flow[IpSecTransform.DIRECTION_OUT].authentication =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
encapType = in.readInt();
encapLocalPort = in.readInt();
encapRemotePort = in.readInt();
- selectorProto = in.readInt();
}
public static final Parcelable.Creator<IpSecConfig> CREATOR =
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 2c544e9..6852beb 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -17,10 +17,11 @@
import static com.android.internal.util.Preconditions.checkNotNull;
-import android.annotation.SystemApi;
-import android.content.Context;
-import android.os.INetworkManagementService;
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.Bundle;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.util.AndroidException;
import dalvik.system.CloseGuard;
import java.io.FileDescriptor;
@@ -41,6 +42,29 @@
private static final String TAG = "IpSecManager";
/**
+ * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index.
+ *
+ * <p>No IPsec packet may contain an SPI of 0.
+ */
+ public static final int INVALID_SECURITY_PARAMETER_INDEX = 0;
+
+ /** @hide */
+ public interface Status {
+ public static final int OK = 0;
+ public static final int RESOURCE_UNAVAILABLE = 1;
+ public static final int SPI_UNAVAILABLE = 2;
+ }
+
+ /** @hide */
+ public static final String KEY_STATUS = "status";
+ /** @hide */
+ public static final String KEY_RESOURCE_ID = "resourceId";
+ /** @hide */
+ public static final String KEY_SPI = "spi";
+ /** @hide */
+ public static final int INVALID_RESOURCE_ID = 0;
+
+ /**
* Indicates that the combination of remote InetAddress and SPI was non-unique for a given
* request. If encountered, selection of a new SPI is required before a transform may be
* created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random
@@ -79,42 +103,30 @@
}
}
- private final Context mContext;
- private final INetworkManagementService mService;
+ private final IIpSecService mService;
public static final class SecurityParameterIndex implements AutoCloseable {
- private final Context mContext;
- private final InetAddress mDestinationAddress;
+ private final IIpSecService mService;
+ private final InetAddress mRemoteAddress;
private final CloseGuard mCloseGuard = CloseGuard.get();
- private int mSpi;
+ private int mSpi = INVALID_SECURITY_PARAMETER_INDEX;
+ private int mResourceId;
/** Return the underlying SPI held by this object */
public int getSpi() {
return mSpi;
}
- private SecurityParameterIndex(Context context, InetAddress destinationAddress, int spi)
- throws ResourceUnavailableException, SpiUnavailableException {
- mContext = context;
- mDestinationAddress = destinationAddress;
- mSpi = spi;
- mCloseGuard.open("open");
- }
-
/**
* Release an SPI that was previously reserved.
*
- * <p>Release an SPI for use by other users in the system. This will fail if the SPI is
- * currently in use by an IpSecTransform.
- *
- * @param destinationAddress SPIs must be unique for each combination of SPI and destination
- * address. Thus, the destinationAddress to which the SPI will communicate must be
- * supplied.
- * @param spi the previously reserved SPI to be freed.
+ * <p>Release an SPI for use by other users in the system. If a SecurityParameterIndex is
+ * applied to an IpSecTransform, it will become unusable for future transforms but should
+ * still be closed to ensure system resources are released.
*/
@Override
public void close() {
- mSpi = INVALID_SECURITY_PARAMETER_INDEX; // TODO: Invalid SPI
+ mSpi = INVALID_SECURITY_PARAMETER_INDEX;
mCloseGuard.close();
}
@@ -126,23 +138,61 @@
close();
}
+
+ private SecurityParameterIndex(
+ @NonNull IIpSecService service, int direction, InetAddress remoteAddress, int spi)
+ throws ResourceUnavailableException, SpiUnavailableException {
+ mService = service;
+ mRemoteAddress = remoteAddress;
+ try {
+ Bundle result =
+ mService.reserveSecurityParameterIndex(
+ direction, remoteAddress.getHostAddress(), spi, new Binder());
+
+ if (result == null) {
+ throw new NullPointerException("Received null response from IpSecService");
+ }
+
+ int status = result.getInt(KEY_STATUS);
+ switch (status) {
+ case Status.OK:
+ break;
+ case Status.RESOURCE_UNAVAILABLE:
+ throw new ResourceUnavailableException(
+ "No more SPIs may be allocated by this requester.");
+ case Status.SPI_UNAVAILABLE:
+ throw new SpiUnavailableException("Requested SPI is unavailable", spi);
+ default:
+ throw new RuntimeException(
+ "Unknown status returned by IpSecService: " + status);
+ }
+ mSpi = result.getInt(KEY_SPI);
+ mResourceId = result.getInt(KEY_RESOURCE_ID);
+
+ if (mSpi == INVALID_SECURITY_PARAMETER_INDEX) {
+ throw new RuntimeException("Invalid SPI returned by IpSecService: " + status);
+ }
+
+ if (mResourceId == INVALID_RESOURCE_ID) {
+ throw new RuntimeException(
+ "Invalid Resource ID returned by IpSecService: " + status);
+ }
+
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mCloseGuard.open("open");
+ }
}
/**
- * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index.
- *
- * <p>No IPsec packet may contain an SPI of 0.
- */
- public static final int INVALID_SECURITY_PARAMETER_INDEX = 0;
-
- /**
- * Reserve an SPI for traffic bound towards the specified destination address.
+ * Reserve an SPI for traffic bound towards the specified remote address.
*
* <p>If successful, this SPI is guaranteed available until released by a call to {@link
* SecurityParameterIndex#close()}.
*
- * @param destinationAddress SPIs must be unique for each combination of SPI and destination
- * address.
+ * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
+ * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress.
* @param requestedSpi the requested SPI, or '0' to allocate a random SPI.
* @return the reserved SecurityParameterIndex
* @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
@@ -150,9 +200,9 @@
* @throws SpiUnavailableException indicating that a particular SPI cannot be reserved
*/
public SecurityParameterIndex reserveSecurityParameterIndex(
- InetAddress destinationAddress, int requestedSpi)
+ int direction, InetAddress remoteAddress, int requestedSpi)
throws SpiUnavailableException, ResourceUnavailableException {
- return new SecurityParameterIndex(mContext, destinationAddress, requestedSpi);
+ return new SecurityParameterIndex(mService, direction, remoteAddress, requestedSpi);
}
/**
@@ -190,7 +240,13 @@
}
/* Call down to activate a transform */
- private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {}
+ private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
+ try {
+ mService.applyTransportModeTransform(pfd, transform.getResourceId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/**
* Apply an active Tunnel Mode IPsec Transform to a network, which will tunnel all traffic to
@@ -203,7 +259,6 @@
* @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform.
* @hide
*/
- @SystemApi
public void applyTunnelModeTransform(Network net, IpSecTransform transform) {}
/**
@@ -235,7 +290,13 @@
}
/* Call down to activate a transform */
- private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {}
+ private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
+ try {
+ mService.removeTransportModeTransform(pfd, transform.getResourceId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/**
* Remove a Tunnel Mode IPsec Transform from a {@link Network}. This must be used as part of
@@ -248,7 +309,6 @@
* network
* @hide
*/
- @SystemApi
public void removeTunnelModeTransform(Network net, IpSecTransform transform) {}
/**
@@ -260,19 +320,19 @@
*/
public static final class UdpEncapsulationSocket implements AutoCloseable {
private final FileDescriptor mFd;
- private final Context mContext;
+ private final IIpSecService mService;
private final CloseGuard mCloseGuard = CloseGuard.get();
- private UdpEncapsulationSocket(Context context, int port)
+ private UdpEncapsulationSocket(@NonNull IIpSecService service, int port)
throws ResourceUnavailableException {
- mContext = context;
+ mService = service;
mCloseGuard.open("constructor");
// TODO: go down to the kernel and get a socket on the specified
mFd = new FileDescriptor();
}
- private UdpEncapsulationSocket(Context context) throws ResourceUnavailableException {
- mContext = context;
+ private UdpEncapsulationSocket(IIpSecService service) throws ResourceUnavailableException {
+ mService = service;
mCloseGuard.open("constructor");
// TODO: go get a random socket on a random port
mFd = new FileDescriptor();
@@ -339,7 +399,7 @@
public UdpEncapsulationSocket openUdpEncapsulationSocket(int port)
throws IOException, ResourceUnavailableException {
// Temporary code
- return new UdpEncapsulationSocket(mContext, port);
+ return new UdpEncapsulationSocket(mService, port);
}
/**
@@ -363,7 +423,7 @@
public UdpEncapsulationSocket openUdpEncapsulationSocket()
throws IOException, ResourceUnavailableException {
// Temporary code
- return new UdpEncapsulationSocket(mContext);
+ return new UdpEncapsulationSocket(mService);
}
/**
@@ -372,8 +432,7 @@
* @param context the application context for this manager
* @hide
*/
- public IpSecManager(Context context, INetworkManagementService service) {
- mContext = checkNotNull(context, "missing context");
+ public IpSecManager(IIpSecService service) {
mService = checkNotNull(service, "missing service");
}
}
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index d6dd28be..801e98c 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -15,11 +15,21 @@
*/
package android.net;
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+import static android.net.IpSecManager.KEY_RESOURCE_ID;
+import static android.net.IpSecManager.KEY_STATUS;
+
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Context;
-import android.system.ErrnoException;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
+import com.android.internal.util.Preconditions;
import dalvik.system.CloseGuard;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -86,39 +96,64 @@
@Retention(RetentionPolicy.SOURCE)
public @interface EncapType {}
- /**
- * Sentinel for an invalid transform (means that this transform is inactive).
- *
- * @hide
- */
- public static final int INVALID_TRANSFORM_ID = -1;
-
private IpSecTransform(Context context, IpSecConfig config) {
mContext = context;
mConfig = config;
- mTransformId = INVALID_TRANSFORM_ID;
+ mResourceId = INVALID_RESOURCE_ID;
+ }
+
+ private IIpSecService getIpSecService() {
+ IBinder b = ServiceManager.getService(android.content.Context.IPSEC_SERVICE);
+ if (b == null) {
+ throw new RemoteException("Failed to connect to IpSecService")
+ .rethrowAsRuntimeException();
+ }
+
+ return IIpSecService.Stub.asInterface(b);
+ }
+
+ private void checkResultStatusAndThrow(int status)
+ throws IOException, IpSecManager.ResourceUnavailableException,
+ IpSecManager.SpiUnavailableException {
+ switch (status) {
+ case IpSecManager.Status.OK:
+ return;
+ // TODO: Pass Error string back from bundle so that errors can be more specific
+ case IpSecManager.Status.RESOURCE_UNAVAILABLE:
+ throw new IpSecManager.ResourceUnavailableException(
+ "Failed to allocate a new IpSecTransform");
+ case IpSecManager.Status.SPI_UNAVAILABLE:
+ Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved");
+ // Fall through
+ default:
+ throw new IllegalStateException(
+ "Failed to Create a Transform with status code " + status);
+ }
}
private IpSecTransform activate()
throws IOException, IpSecManager.ResourceUnavailableException,
IpSecManager.SpiUnavailableException {
- int transformId;
synchronized (this) {
- //try {
- transformId = INVALID_TRANSFORM_ID;
- //} catch (RemoteException e) {
- // throw e.rethrowFromSystemServer();
- //}
+ try {
+ IIpSecService svc = getIpSecService();
+ Bundle result = svc.createTransportModeTransform(mConfig, new Binder());
+ int status = result.getInt(KEY_STATUS);
+ checkResultStatusAndThrow(status);
+ mResourceId = result.getInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID);
- if (transformId < 0) {
- throw new ErrnoException("addTransform", -transformId).rethrowAsIOException();
+ /* Keepalive will silently fail if not needed by the config; but, if needed and
+ * it fails to start, we need to bail because a transform will not be reliable
+ * to use if keepalive is expected to offload and fails.
+ */
+ // FIXME: if keepalive fails, we need to fail spectacularly
+ startKeepalive(mContext);
+ Log.d(TAG, "Added Transform with Id " + mResourceId);
+ mCloseGuard.open("build");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
-
- startKeepalive(mContext); // Will silently fail if not required
- mTransformId = transformId;
- Log.d(TAG, "Added Transform with Id " + transformId);
}
- mCloseGuard.open("build");
return this;
}
@@ -133,21 +168,27 @@
* transform is no longer needed.
*/
public void close() {
- Log.d(TAG, "Removing Transform with Id " + mTransformId);
+ Log.d(TAG, "Removing Transform with Id " + mResourceId);
// Always safe to attempt cleanup
- if (mTransformId == INVALID_TRANSFORM_ID) {
+ if (mResourceId == INVALID_RESOURCE_ID) {
+ mCloseGuard.close();
return;
}
- //try {
- stopKeepalive();
- //} catch (RemoteException e) {
- // transform.setTransformId(transformId);
- // throw e.rethrowFromSystemServer();
- //} finally {
- mTransformId = INVALID_TRANSFORM_ID;
- //}
- mCloseGuard.close();
+ try {
+ /* Order matters here because the keepalive is best-effort but could fail in some
+ * horrible way to be removed if the wifi (or cell) subsystem has crashed, and we
+ * still want to clear out the transform.
+ */
+ IIpSecService svc = getIpSecService();
+ svc.deleteTransportModeTransform(mResourceId);
+ stopKeepalive();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ mResourceId = INVALID_RESOURCE_ID;
+ mCloseGuard.close();
+ }
}
@Override
@@ -164,7 +205,7 @@
}
private final IpSecConfig mConfig;
- private int mTransformId;
+ private int mResourceId;
private final Context mContext;
private final CloseGuard mCloseGuard = CloseGuard.get();
private ConnectivityManager.PacketKeepalive mKeepalive;
@@ -200,6 +241,7 @@
/* Package */
void startKeepalive(Context c) {
+ // FIXME: NO_KEEPALIVE needs to be a constant
if (mConfig.getNattKeepaliveInterval() == 0) {
return;
}
@@ -208,7 +250,7 @@
(ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
if (mKeepalive != null) {
- Log.e(TAG, "Keepalive already started for this IpSecTransform.");
+ Log.wtf(TAG, "Keepalive already started for this IpSecTransform.");
return;
}
@@ -218,10 +260,11 @@
mConfig.getNetwork(),
mConfig.getNattKeepaliveInterval(),
mKeepaliveCallback,
- mConfig.getLocalIp(),
+ mConfig.getLocalAddress(),
mConfig.getEncapLocalPort(),
- mConfig.getRemoteIp());
+ mConfig.getRemoteAddress());
try {
+ // FIXME: this is still a horrible way to fudge the synchronous callback
mKeepaliveSyncLock.wait(2000);
} catch (InterruptedException e) {
}
@@ -232,6 +275,11 @@
}
/* Package */
+ int getResourceId() {
+ return mResourceId;
+ }
+
+ /* Package */
void stopKeepalive() {
if (mKeepalive == null) {
return;
@@ -247,16 +295,6 @@
}
}
- /* Package */
- void setTransformId(int transformId) {
- mTransformId = transformId;
- }
-
- /* Package */
- int getTransformId() {
- return mTransformId;
- }
-
/**
* Builder object to facilitate the creation of IpSecTransform objects.
*
@@ -280,7 +318,7 @@
*/
public IpSecTransform.Builder setEncryption(
@TransformDirection int direction, IpSecAlgorithm algo) {
- mConfig.flow[direction].encryptionAlgo = algo;
+ mConfig.flow[direction].encryption = algo;
return this;
}
@@ -295,7 +333,7 @@
*/
public IpSecTransform.Builder setAuthentication(
@TransformDirection int direction, IpSecAlgorithm algo) {
- mConfig.flow[direction].authenticationAlgo = algo;
+ mConfig.flow[direction].authentication = algo;
return this;
}
@@ -305,32 +343,9 @@
* given destination address.
*
* <p>Care should be chosen when selecting an SPI to ensure that is is as unique as
- * possible. Random number generation is a reasonable approach to selecting an SPI. For
- * outbound SPIs, they must be reserved by calling {@link
- * IpSecManager#reserveSecurityParameterIndex(InetAddress, int)}. Otherwise, Transforms will
- * fail to build.
- *
- * <p>Unless an SPI is set for a given direction, traffic in that direction will be
- * sent/received without any IPsec applied.
- *
- * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
- * @param spi a unique 32-bit integer to identify transformed traffic
- */
- public IpSecTransform.Builder setSpi(@TransformDirection int direction, int spi) {
- mConfig.flow[direction].spi = spi;
- return this;
- }
-
- /**
- * Set the SPI, which uniquely identifies a particular IPsec session from others. Because
- * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a
- * given destination address.
- *
- * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as
- * possible. Random number generation is a reasonable approach to selecting an SPI. For
- * outbound SPIs, they must be reserved by calling {@link
- * IpSecManager#reserveSecurityParameterIndex(InetAddress, int)}. Otherwise, Transforms will
- * fail to activate.
+ * possible. To reserve a value call {@link IpSecManager#reserveSecurityParameterIndex(int,
+ * InetAddress, int)}. Otherwise, SPI collisions would prevent a transform from being
+ * activated. IpSecManager#reserveSecurityParameterIndex(int, InetAddres$s, int)}.
*
* <p>Unless an SPI is set for a given direction, traffic in that direction will be
* sent/received without any IPsec applied.
@@ -341,6 +356,8 @@
*/
public IpSecTransform.Builder setSpi(
@TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) {
+ // TODO: convert to using the resource Id of the SPI. Then build() can validate
+ // the owner in the IpSecService
mConfig.flow[direction].spi = spi.getSpi();
return this;
}
@@ -447,7 +464,6 @@
* properties is invalid.
* @hide
*/
- @SystemApi
public IpSecTransform buildTunnelModeTransform(
InetAddress localAddress, InetAddress remoteAddress) {
//FIXME: argument validation here
@@ -463,7 +479,8 @@
*
* @param context current Context
*/
- public Builder(Context context) {
+ public Builder(@NonNull Context context) {
+ Preconditions.checkNotNull(context);
mContext = context;
mConfig = new IpSecConfig();
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index dd11f68..4c6d22a 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -182,7 +182,7 @@
* New in version 19:
* - Wakelock data (wl) gets current and max times.
* New in version 20:
- * - Sensor, BluetoothScan, WifiScan get background timers and counter.
+ * - Background timers and counters for: Sensor, BluetoothScan, WifiScan, Jobs.
*/
static final String CHECKIN_VERSION = "20";
@@ -392,6 +392,16 @@
}
/**
+ * Returns the secondary Timer held by the Timer, if one exists. This secondary timer may be
+ * used, for example, for tracking background usage. Secondary timers are never pooled.
+ *
+ * Not all Timer subclasses have a secondary timer; those that don't return null.
+ */
+ public Timer getSubTimer() {
+ return null;
+ }
+
+ /**
* Returns whether the timer is currently running. Some types of timers
* (e.g. BatchTimers) don't know whether the event is currently active,
* and report false.
@@ -3303,16 +3313,17 @@
final int wifiScanCount = u.getWifiScanCount(which);
final int wifiScanCountBg = u.getWifiScanBackgroundCount(which);
// Note that 'ActualTime' are unpooled and always since reset (regardless of 'which')
- final long wifiScanActualTime = u.getWifiScanActualTime(rawRealtime);
- final long wifiScanActualTimeBg = u.getWifiScanBackgroundTime(rawRealtime);
+ final long wifiScanActualTimeMs = (u.getWifiScanActualTime(rawRealtime) + 500) / 1000;
+ final long wifiScanActualTimeMsBg = (u.getWifiScanBackgroundTime(rawRealtime) + 500)
+ / 1000;
final long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || wifiScanCount != 0
- || wifiScanCountBg != 0 || wifiScanActualTime != 0 || wifiScanActualTimeBg != 0
- || uidWifiRunningTime != 0) {
+ || wifiScanCountBg != 0 || wifiScanActualTimeMs != 0
+ || wifiScanActualTimeMsBg != 0 || uidWifiRunningTime != 0) {
dumpLine(pw, uid, category, WIFI_DATA, fullWifiLockOnTime, wifiScanTime,
uidWifiRunningTime, wifiScanCount,
/* legacy fields follow, keep at 0 */ 0, 0, 0,
- wifiScanCountBg, wifiScanActualTime, wifiScanActualTimeBg);
+ wifiScanCountBg, wifiScanActualTimeMs, wifiScanActualTimeMsBg);
}
dumpControllerActivityLine(pw, uid, category, WIFI_CONTROLLER_DATA,
@@ -3393,9 +3404,13 @@
// Convert from microseconds to milliseconds with rounding
final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
final int count = timer.getCountLocked(which);
+ final Timer bgTimer = timer.getSubTimer();
+ final long bgTime = bgTimer != null ?
+ (bgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : -1;
+ final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : -1;
if (totalTime != 0) {
dumpLine(pw, uid, category, JOB_DATA, "\"" + jobs.keyAt(ij) + "\"",
- totalTime, count);
+ totalTime, count, bgTime, bgCount);
}
}
@@ -4616,6 +4631,10 @@
// Convert from microseconds to milliseconds with rounding
final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
final int count = timer.getCountLocked(which);
+ final Timer bgTimer = timer.getSubTimer();
+ final long bgTime = bgTimer != null ?
+ (bgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : -1;
+ final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : -1;
sb.setLength(0);
sb.append(prefix);
sb.append(" Job ");
@@ -4626,6 +4645,13 @@
sb.append("realtime (");
sb.append(count);
sb.append(" times)");
+ if (bgTime > 0) {
+ sb.append(", ");
+ formatTimeMs(sb, bgTime);
+ sb.append("background (");
+ sb.append(bgCount);
+ sb.append(" times)");
+ }
} else {
sb.append("(not used)");
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index e5d73e0..b5af766 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -59,19 +59,14 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.os.AppFuseMount;
import com.android.internal.os.FuseAppLoop;
-import com.android.internal.os.FuseAppLoop.UnmountedException;
import com.android.internal.os.FuseUnavailableMountException;
import com.android.internal.os.RoSystemProperties;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.Preconditions;
-import java.io.BufferedReader;
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.InputStreamReader;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -84,7 +79,6 @@
import java.util.Objects;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
-import libcore.io.IoUtils;
/**
* StorageManager is the interface to the systems storage service. The storage
@@ -186,15 +180,6 @@
private static volatile IStorageManager sStorageManager = null;
- // TODO: the location of the primary storage block varies from device to device, so we need to
- // try the most likely candidates - a long-term solution would be a device-specific vold
- // function that returns the calculated size.
- private static final String[] INTERNAL_STORAGE_SIZE_PATHS = {
- "/sys/block/mmcblk0/size",
- "/sys/block/sda/size"
- };
- private static final int INTERNAL_STORAGE_SECTOR_SIZE = 512;
-
private final Context mContext;
private final ContentResolver mResolver;
@@ -1011,38 +996,13 @@
/** {@hide} */
public static Pair<String, Long> getPrimaryStoragePathAndSize() {
- for (String path : INTERNAL_STORAGE_SIZE_PATHS) {
- final long numberBlocks = readLong(path);
- if (numberBlocks > 0) {
- return new Pair<>(path,
- FileUtils.roundStorageSize(numberBlocks * INTERNAL_STORAGE_SECTOR_SIZE));
- }
- }
- return null;
+ return Pair.create(null,
+ FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()));
}
-
/** {@hide} */
public long getPrimaryStorageSize() {
- final Pair<String, Long> pair = getPrimaryStoragePathAndSize();
- return pair == null ? 0 : pair.second.longValue();
- }
-
- private static long readLong(String path) {
- try (final FileInputStream fis = new FileInputStream(path);
- final BufferedReader reader = new BufferedReader(new InputStreamReader(fis));) {
- return Long.parseLong(reader.readLine());
- } catch (FileNotFoundException e) {
- // This is expected since we are trying to parse multiple paths.
- Slog.i(TAG, "readLong(): Path doesn't exist: " + path + ": " + e);
- return 0;
- } catch (NumberFormatException e) {
- Slog.e(TAG, "readLong(): Could not parse " + path + ": " + e);
- return 0;
- } catch (Exception e) {
- Slog.e(TAG, "readLong(): Unknown exception while opening " + path + ": " + e);
- return 0;
- }
+ return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace());
}
/** @removed */
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index a280e59..9d83bd7 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -114,7 +114,7 @@
* download's content: uri is specified in the intent's data.
*/
public static final String ACTION_DOWNLOAD_COMPLETED =
- "android.intent.action.DOWNLOAD_COMPLETED";
+ DownloadManager.ACTION_DOWNLOAD_COMPLETED;
/**
* Broadcast Action: this is sent by the download manager to the app
@@ -127,7 +127,7 @@
* successfully.
*/
public static final String ACTION_NOTIFICATION_CLICKED =
- "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
+ DownloadManager.ACTION_NOTIFICATION_CLICKED;
/**
* The name of the column containing the URI of the data being downloaded.
diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java
index fd9d4db..4deb4ab 100644
--- a/core/java/android/provider/FontsContract.java
+++ b/core/java/android/provider/FontsContract.java
@@ -72,15 +72,28 @@
*/
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}
+ * DO NOT USE THIS COLUMN.
+ * This column is kept for preventing demo apps.
+ * TODO: Remove once nobody uses this column.
+ * @hide
+ * @removed
*/
public static final String STYLE = "font_style";
/**
* Constant used to request data from a font provider. The cursor returned from the query
+ * should have this column populated with the int weight for the resulting font. This value
+ * should be between 100 and 900. The most common values are 400 for regular weight and 700
+ * for bold weight.
+ */
+ public static final String WEIGHT = "font_weight";
+ /**
+ * Constant used to request data from a font provider. The cursor returned from the query
+ * should have this column populated with the int italic for the resulting font. This should
+ * be 0 for regular style and 1 for italic.
+ */
+ public static final String ITALIC = "font_italic";
+ /**
+ * Constant used to request data from a font provider. The cursor returned from the query
* should have this column populated to indicate the result status of the
* query. This will be checked before any other data in the cursor. Possible values are
* {@link #RESULT_CODE_OK}, {@link #RESULT_CODE_FONT_NOT_FOUND},
@@ -274,7 +287,7 @@
.build();
try (Cursor cursor = mContext.getContentResolver().query(uri, new String[] { Columns._ID,
Columns.TTC_INDEX, Columns.VARIATION_SETTINGS, Columns.STYLE,
- Columns.RESULT_CODE },
+ Columns.WEIGHT, Columns.ITALIC, Columns.RESULT_CODE },
"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.
@@ -285,6 +298,8 @@
final int idColumnIndex = cursor.getColumnIndexOrThrow(Columns._ID);
final int ttcIndexColumnIndex = cursor.getColumnIndex(Columns.TTC_INDEX);
final int vsColumnIndex = cursor.getColumnIndex(Columns.VARIATION_SETTINGS);
+ final int weightColumnIndex = cursor.getColumnIndex(Columns.WEIGHT);
+ final int italicColumnIndex = cursor.getColumnIndex(Columns.ITALIC);
final int styleColumnIndex = cursor.getColumnIndex(Columns.STYLE);
while (cursor.moveToNext()) {
resultCode = resultCodeColumnIndex != -1
@@ -313,9 +328,22 @@
? cursor.getInt(ttcIndexColumnIndex) : 0;
final String variationSettings = vsColumnIndex != -1
? cursor.getString(vsColumnIndex) : null;
- final int style = styleColumnIndex != -1
- ? cursor.getInt(styleColumnIndex) : Typeface.NORMAL;
- result.add(new FontResult(pfd, ttcIndex, variationSettings, style));
+ // TODO: Stop using STYLE column and enforce WEIGHT/ITALIC column.
+ int weight;
+ boolean italic;
+ if (weightColumnIndex != -1 && italicColumnIndex != -1) {
+ weight = cursor.getInt(weightColumnIndex);
+ italic = cursor.getInt(italicColumnIndex) == 1;
+ } else if (styleColumnIndex != -1) {
+ final int style = cursor.getInt(styleColumnIndex);
+ weight = (style & Typeface.BOLD) != 0 ? 700 : 400;
+ italic = (style & Typeface.ITALIC) != 0;
+ } else {
+ weight = 400;
+ italic = false;
+ }
+ result.add(
+ new FontResult(pfd, ttcIndex, variationSettings, weight, italic));
} catch (FileNotFoundException e) {
Log.e(TAG, "FileNotFoundException raised when interacting with content "
+ "provider " + authority, e);
diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
deleted file mode 100644
index c26f679..0000000
--- a/core/java/android/service/autofill/AutoFillService.java
+++ /dev/null
@@ -1,24 +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.service.autofill;
-
-/**
- * @hide
- * @deprecated TODO(b/35956626): remove once clients use AutofillService
- */
-@Deprecated
-public abstract class AutoFillService extends AutofillService {
-}
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 6f17d0e..ef14095 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -71,7 +71,7 @@
* Name under which a AutoFillService component publishes information about itself.
* This meta-data should reference an XML resource containing a
* <code><{@link
- * android.R.styleable#AutoFillService autofill-service}></code> tag.
+ * android.R.styleable#AutofillService autofill-service}></code> tag.
* This is a a sample XML file configuring an AutoFillService:
* <pre> <autofill-service
* android:settingsActivity="foo.bar.SettingsActivity"
@@ -204,11 +204,12 @@
* to notify the result of the request.
*
* @param structure {@link Activity}'s view structure.
- * @param data bundle containing data passed by the service on previous calls to fill.
- * This bundle allows your service to keep state between fill and save requests
- * as well as when filling different sections of the UI as the system will try to
- * aggressively unbind from the service to conserve resources. See {@link
- * FillResponse} Javadoc for examples of multiple-sections requests.
+ * @param data bundle containing data passed by the service in a last call to
+ * {@link FillResponse.Builder#setExtras(Bundle)}, if any. This bundle allows your
+ * service to keep state between fill and save requests as well as when filling different
+ * sections of the UI as the system will try to aggressively unbind from the service to
+ * conserve resources.
+ * See {@link FillResponse} for examples of multiple-sections requests.
* @param flags either {@code 0} or {@link AutofillManager#FLAG_MANUAL_REQUEST}.
* @param cancellationSignal signal for observing cancellation requests. The system will use
* this to notify you that the fill result is no longer needed and you should stop
@@ -242,11 +243,12 @@
* to notify the result of the request.
*
* @param structure {@link Activity}'s view structure.
- * @param data bundle containing data passed by the service on previous calls to fill.
- * This bundle allows your service to keep state between fill and save requests
- * as well as when filling different sections of the UI as the system will try to
- * aggressively unbind from the service to conserve resources. See {@link
- * FillResponse} Javadoc for examples of multiple-sections requests.
+ * @param data bundle containing data passed by the service in a last call to
+ * {@link FillResponse.Builder#setExtras(Bundle)}, if any. This bundle allows your
+ * service to keep state between fill and save requests as well as when filling different
+ * sections of the UI as the system will try to aggressively unbind from the service to
+ * conserve resources.
+ * See {@link FillResponse} for examples of multiple-sections requests.
* @param callback object used to notify the result of the request.
*/
public abstract void onSaveRequest(@NonNull AssistStructure structure, @Nullable Bundle data,
diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java
index f6d40db..0f4824e 100644
--- a/core/java/android/service/autofill/AutofillServiceInfo.java
+++ b/core/java/android/service/autofill/AutofillServiceInfo.java
@@ -36,7 +36,6 @@
import java.io.IOException;
-// TODO(b/33197203 , b/33802548): add CTS tests
/**
* {@link ServiceInfo} and meta-data about an {@link AutofillService}.
*
@@ -75,15 +74,8 @@
mServiceInfo = si;
final TypedArray metaDataArray = getMetaDataArray(pm, si);
if (metaDataArray != null) {
- // TODO(b/35956626): inline newSettingsActivity once clients migrate
- final String newSettingsActivity =
- metaDataArray.getString(R.styleable.AutofillService_settingsActivity);
- if (newSettingsActivity != null) {
- mSettingsActivity = newSettingsActivity;
- } else {
- mSettingsActivity =
- metaDataArray.getString(R.styleable.AutoFillService_settingsActivity);
- }
+ mSettingsActivity = metaDataArray
+ .getString(R.styleable.AutofillService_settingsActivity);
metaDataArray.recycle();
} else {
mSettingsActivity = null;
@@ -96,16 +88,11 @@
@Nullable
private static TypedArray getMetaDataArray(PackageManager pm, ServiceInfo si) {
// Check for permissions.
- // TODO(b/35956626): remove check for BIND_AUTO_FILL once clients migrate
- if (!Manifest.permission.BIND_AUTOFILL.equals(si.permission)
- && !Manifest.permission.BIND_AUTO_FILL.equals(si.permission)) {
+ if (!Manifest.permission.BIND_AUTOFILL.equals(si.permission)) {
Log.e(TAG, "Service does not require permission " + Manifest.permission.BIND_AUTOFILL);
return null;
}
- // TODO(b/35956626): remove once clients migrate
- final boolean oldStyle = !Manifest.permission.BIND_AUTOFILL.equals(si.permission);
-
// Get the AutoFill metadata, if declared.
XmlResourceParser parser = si.loadXmlMetaData(pm, AutofillService.SERVICE_META_DATA);
if (parser == null) {
@@ -141,8 +128,7 @@
return null;
}
- return oldStyle ? res.obtainAttributes(attrs, R.styleable.AutoFillService)
- : res.obtainAttributes(attrs, R.styleable.AutofillService);
+ return res.obtainAttributes(attrs, R.styleable.AutofillService);
} finally {
parser.close();
}
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index e27fa06..e77bd0d 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -23,8 +23,6 @@
import android.content.IntentSender;
import android.os.Parcel;
import android.os.Parcelable;
-import android.view.autofill.AutoFillId;
-import android.view.autofill.AutoFillValue;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.widget.RemoteViews;
@@ -78,11 +76,6 @@
}
/** @hide */
- public @Nullable RemoteViews getPresentation() {
- return mPresentation;
- }
-
- /** @hide */
public @Nullable IntentSender getAuthentication() {
return mAuthentication;
}
@@ -180,15 +173,6 @@
}
/**
- * @hide
- * @deprecated TODO(b/35956626): remove once clients use other setValue()
- */
- @Deprecated
- public @NonNull Builder setValue(@NonNull AutoFillId id, @NonNull AutoFillValue value) {
- return setValue(id.getDaRealId(), value.getDaRealValue());
- }
-
- /**
* Sets the value of a field.
*
* @param id id returned by {@link
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index c43019d..3117f98 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package android.service.autofill;
import static android.view.autofill.Helper.DEBUG;
@@ -23,6 +24,7 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.widget.RemoteViews;
@@ -112,7 +114,7 @@
*
* <p>The service could require user authentication at the {@link FillResponse} or the
* {@link Dataset} level, prior to autofilling an activity - see
- * {@link FillResponse.Builder#setAuthentication(IntentSender, RemoteViews)} and
+ * {@link FillResponse.Builder#setAuthentication(AutofillId[], IntentSender, RemoteViews)} and
* {@link Dataset.Builder#setAuthentication(IntentSender)}.
*
* <p>It is recommended that you encrypt only the sensitive data but leave the labels unencrypted
@@ -126,7 +128,7 @@
* possible options) which will start your auth flow and after successfully authenticating
* the user will be presented with the Home and Work options to pick one. Hence, you have
* flexibility how to implement your auth while storing labels non-encrypted and data
- * encrypted provides a better user experience.</p>
+ * encrypted provides a better user experience.
*/
public final class FillResponse implements Parcelable {
@@ -135,6 +137,7 @@
private final Bundle mExtras;
private final RemoteViews mPresentation;
private final IntentSender mAuthentication;
+ private AutofillId[] mAuthenticationIds;
private FillResponse(@NonNull Builder builder) {
mDatasets = builder.mDatasets;
@@ -142,6 +145,7 @@
mExtras = builder.mExtras;
mPresentation = builder.mPresentation;
mAuthentication = builder.mAuthentication;
+ mAuthenticationIds = builder.mAuthenticationIds;
}
/** @hide */
@@ -169,6 +173,11 @@
return mAuthentication;
}
+ /** @hide */
+ public @Nullable AutofillId[] getAuthenticationIds() {
+ return mAuthenticationIds;
+ }
+
/**
* Builder for {@link FillResponse} objects. You must to provide at least
* one dataset or set an authentication intent with a presentation view.
@@ -179,6 +188,7 @@
private Bundle mExtras;
private RemoteViews mPresentation;
private IntentSender mAuthentication;
+ private AutofillId[] mAuthenticationIds;
private boolean mDestroyed;
/**
@@ -193,7 +203,7 @@
* be encrypted. The provided {@link android.app.PendingIntent intent} must be an
* activity which implements your authentication flow. Also if you provide an auth
* intent you also need to specify the presentation view to be shown in the fill UI
- * for the user to trigger your authentication flow.</p>
+ * for the user to trigger your authentication flow.
*
* <p>When a user triggers autofill, the system launches the provided intent
* whose extras will have the {@link AutofillManager#EXTRA_ASSIST_STRUCTURE screen
@@ -205,40 +215,54 @@
* user's data was locked and marked that the response needs an authentication then
* in the response returned if authentication succeeds you need to provide all
* available data sets some of which may need to be further authenticated, for
- * example a credit card whose CVV needs to be entered.</p>
+ * example a credit card whose CVV needs to be entered.
*
* <p>If you provide an authentication intent you must also provide a presentation
* which is used to visualize visualize the response for triggering the authentication
- * flow.</p>
+ * flow.
*
* <p></><strong>Note:</strong> Do not make the provided pending intent
* immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
- * platform needs to fill in the authentication arguments.</p>
+ * platform needs to fill in the authentication arguments.
*
* @param authentication Intent to an activity with your authentication flow.
* @param presentation The presentation to visualize the response.
- * @return This builder.
+ * @param ids id of Views that when focused will display the authentication UI affordance.
*
+ * @return This builder.
* @see android.app.PendingIntent#getIntentSender()
*/
- public @NonNull Builder setAuthentication(@Nullable IntentSender authentication,
- @Nullable RemoteViews presentation) {
+ public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids,
+ @Nullable IntentSender authentication, @Nullable RemoteViews presentation) {
throwIfDestroyed();
+ // TODO(b/33197203): assert ids is not null nor empty once old version is removed
if (authentication == null ^ presentation == null) {
throw new IllegalArgumentException("authentication and presentation"
+ " must be both non-null or null");
}
mAuthentication = authentication;
mPresentation = presentation;
+ mAuthenticationIds = ids;
return this;
}
/**
+ * TODO(b/33197203): will be removed once clients use the version that takes ids
+ * @hide
+ * @deprecated
+ */
+ @Deprecated
+ public @NonNull Builder setAuthentication(@Nullable IntentSender authentication,
+ @Nullable RemoteViews presentation) {
+ return setAuthentication(null, authentication, presentation);
+ }
+
+ /**
* Adds a new {@link Dataset} to this response.
*
* @return This builder.
*/
- public@NonNull Builder addDataset(@Nullable Dataset dataset) {
+ public @NonNull Builder addDataset(@Nullable Dataset dataset) {
throwIfDestroyed();
if (dataset == null) {
return this;
@@ -273,6 +297,9 @@
* android.os.CancellationSignal, FillCallback)} and {@link AutofillService#onSaveRequest(
* android.app.assist.AssistStructure, Bundle, SaveCallback)}.
*
+ * <p>If this method is called on multiple {@link FillResponse} objects for the same
+ * activity, just the latest bundle is passed back to the service.
+ *
* @param extras The response extras.
* @return This builder.
*/
@@ -282,6 +309,7 @@
return this;
}
+
/**
* Builds a new {@link FillResponse} instance. You must provide at least
* one dataset or some savable ids or an authentication with a presentation
@@ -308,7 +336,7 @@
}
/////////////////////////////////////
- // Object "contract" methods. //
+ // Object "contract" methods. //
/////////////////////////////////////
@Override
public String toString() {
@@ -320,11 +348,13 @@
.append(", hasExtras=").append(mExtras != null)
.append(", hasPresentation=").append(mPresentation != null)
.append(", hasAuthentication=").append(mAuthentication != null)
+ .append(", authenticationSize=").append(mAuthenticationIds != null
+ ? mAuthenticationIds.length : "N/A")
.toString();
}
/////////////////////////////////////
- // Parcelable "contract" methods. //
+ // Parcelable "contract" methods. //
/////////////////////////////////////
@Override
@@ -337,6 +367,7 @@
parcel.writeTypedArrayList(mDatasets, flags);
parcel.writeParcelable(mSaveInfo, flags);
parcel.writeParcelable(mExtras, flags);
+ parcel.writeParcelableArray(mAuthenticationIds, flags);
parcel.writeParcelable(mAuthentication, flags);
parcel.writeParcelable(mPresentation, flags);
}
@@ -356,8 +387,8 @@
}
builder.setSaveInfo(parcel.readParcelable(null));
builder.setExtras(parcel.readParcelable(null));
- builder.setAuthentication(parcel.readParcelable(null),
- parcel.readParcelable(null));
+ builder.setAuthentication(parcel.readParcelableArray(null, AutofillId.class),
+ parcel.readParcelable(null), parcel.readParcelable(null));
return builder.build();
}
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 6213d27..f75b7af 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -25,7 +25,6 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.view.autofill.AutoFillId;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
@@ -130,6 +129,16 @@
*/
public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3;
+ /**
+ * Type used when the {@link FillResponse} represents just an username, without a password.
+ */
+ public static final int SAVE_DATA_TYPE_USERNAME = 4;
+
+ /**
+ * Type used when the {@link FillResponse} represents just an email address, without a password.
+ */
+ public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 5;
+
private final @SaveDataType int mType;
private final CharSequence mNegativeActionTitle;
private final IntentSender mNegativeActionListener;
@@ -223,6 +232,8 @@
case SAVE_DATA_TYPE_PASSWORD:
case SAVE_DATA_TYPE_ADDRESS:
case SAVE_DATA_TYPE_CREDIT_CARD:
+ case SAVE_DATA_TYPE_USERNAME:
+ case SAVE_DATA_TYPE_EMAIL_ADDRESS:
mType = type;
break;
default:
@@ -269,26 +280,6 @@
return this;
}
-
- /**
- * @hide
- */
- // TODO(b/33197203): temporary fix to runtime crash
- public @NonNull Builder addSavableIds(@Nullable AutoFillId... ids) {
- throwIfDestroyed();
-
- if (ids == null || ids.length == 0) {
- return this;
- }
- if (mRequiredIds == null) {
- mRequiredIds = new AutofillId[ids.length];
- }
- for (int i = 0; i < ids.length; i++) {
- mRequiredIds[i] = ids[i].getDaRealId();
- }
- return this;
- }
-
/**
* Sets an optional description to be shown in the UI when the user is asked to save.
*
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index e39d53f..137cf57 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -36,10 +36,6 @@
private final int mUser;
/**
- * Data type: {@code String}. See {@link NotificationChannel#getId()}.
- */
- public static final String KEY_CHANNEL_ID = "key_channel_id";
- /**
* Data type: ArrayList of {@code String}, where each is a representation of a
* {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
* See {@link android.app.Notification.Builder#addPerson(String)}.
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index 46609df..6ec9d69 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -138,69 +138,6 @@
}
}
- /**
- * Creates a notification channel that notifications can be posted to for a given package.
- *
- * @param pkg The package to create a channel for.
- * @param channel the channel to attempt to create.
- */
- public void createNotificationChannel(@NonNull String pkg,
- @NonNull NotificationChannel channel) {
- if (!isBound()) return;
- try {
- getNotificationInterface().createNotificationChannelFromAssistant(
- mWrapper, pkg, channel);
- } catch (RemoteException e) {
- Log.v(TAG, "Unable to contact notification manager", e);
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Updates a notification channel for a given package.
- *
- * @param pkg The package to the channel belongs to.
- * @param channel the channel to attempt to update.
- */
- public void updateNotificationChannel(@NonNull String pkg,
- @NonNull NotificationChannel channel) {
- if (!isBound()) return;
- try {
- getNotificationInterface().updateNotificationChannelFromAssistant(
- mWrapper, pkg, channel);
- } catch (RemoteException e) {
- Log.v(TAG, "Unable to contact notification manager", e);
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Returns all notification channels belonging to the given package.
- */
- public List<NotificationChannel> getNotificationChannels(@NonNull String pkg) {
- if (!isBound()) return null;
- try {
- return getNotificationInterface().getNotificationChannelsFromAssistant(
- mWrapper, pkg).getList();
- } catch (RemoteException e) {
- Log.v(TAG, "Unable to contact notification manager", e);
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Deletes the given notification channel.
- */
- public void deleteNotificationChannel(@NonNull String pkg, @NonNull String channelId) {
- if (!isBound()) return;
- try {
- getNotificationInterface().deleteNotificationChannelFromAssistant(
- mWrapper, pkg, channelId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
private class NotificationAssistantServiceWrapper extends NotificationListenerWrapper {
@Override
diff --git a/core/java/android/service/resolver/IResolverRankerResult.aidl b/core/java/android/service/resolver/IResolverRankerResult.aidl
deleted file mode 100644
index bda3154..0000000
--- a/core/java/android/service/resolver/IResolverRankerResult.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.resolver;
-
-import android.service.resolver.ResolverTarget;
-
-/**
- * @hide
- */
-oneway interface IResolverRankerResult
-{
- void sendResult(in List<ResolverTarget> results);
-}
\ No newline at end of file
diff --git a/core/java/android/service/resolver/IResolverRankerService.aidl b/core/java/android/service/resolver/IResolverRankerService.aidl
deleted file mode 100644
index f0d747d..0000000
--- a/core/java/android/service/resolver/IResolverRankerService.aidl
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.resolver;
-
-import android.service.resolver.IResolverRankerResult;
-import android.service.resolver.ResolverTarget;
-
-/**
- * @hide
- */
-oneway interface IResolverRankerService
-{
- void predict(in List<ResolverTarget> targets, IResolverRankerResult result);
- void train(in List<ResolverTarget> targets, int selectedPosition);
-}
\ No newline at end of file
diff --git a/core/java/android/service/resolver/ResolverRankerService.java b/core/java/android/service/resolver/ResolverRankerService.java
deleted file mode 100644
index 0506747..0000000
--- a/core/java/android/service/resolver/ResolverRankerService.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.resolver;
-
-import android.annotation.SdkConstant;
-import android.annotation.SystemApi;
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.IBinder;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.RemoteException;
-import android.service.resolver.ResolverTarget;
-import android.util.Log;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * A service to rank apps according to usage stats of apps, when the system is resolving targets for
- * an Intent.
- *
- * <p>To extend this class, you must declare the service in your manifest file with the
- * {@link android.Manifest.permission#BIND_RESOLVER_RANKER_SERVICE} permission, and include an
- * intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
- * <pre>
- * <service android:name=".MyResolverRankerService"
- * android:exported="true"
- * android:priority="100"
- * android:permission="android.permission.BIND_RESOLVER_RANKER_SERVICE">
- * <intent-filter>
- * <action android:name="android.service.resolver.ResolverRankerService" />
- * </intent-filter>
- * </service>
- * </pre>
- * @hide
- */
-@SystemApi
-public abstract class ResolverRankerService extends Service {
-
- private static final String TAG = "ResolverRankerService";
-
- private static final boolean DEBUG = false;
-
- /**
- * The Intent action that a service must respond to. Add it to the intent filter of the service
- * in its manifest.
- */
- @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
- public static final String SERVICE_INTERFACE = "android.service.resolver.ResolverRankerService";
-
- /**
- * The permission that a service must require to ensure that only Android system can bind to it.
- * If this permission is not enforced in the AndroidManifest of the service, the system will
- * skip that service.
- */
- public static final String BIND_PERMISSION = "android.permission.BIND_RESOLVER_RANKER_SERVICE";
-
- private ResolverRankerServiceWrapper mWrapper = null;
-
- /**
- * Called by the system to retrieve a list of probabilities to rank apps/options. To implement
- * it, set selectProbability of each input {@link ResolverTarget}. The higher the
- * selectProbability is, the more likely the {@link ResolverTarget} will be selected by the
- * user. Override this function to provide prediction results.
- *
- * @param targets a list of {@link ResolverTarget}, for the list of apps to be ranked.
- *
- * @throws Exception when the prediction task fails.
- */
- public void onPredictSharingProbabilities(final List<ResolverTarget> targets) {}
-
- /**
- * Called by the system to train/update a ranking service, after the user makes a selection from
- * the ranked list of apps. Override this function to enable model updates.
- *
- * @param targets a list of {@link ResolverTarget}, for the list of apps to be ranked.
- * @param selectedPosition the position of the selected app in the list.
- *
- * @throws Exception when the training task fails.
- */
- public void onTrainRankingModel(
- final List<ResolverTarget> targets, final int selectedPosition) {}
-
- private static final String HANDLER_THREAD_NAME = "RESOLVER_RANKER_SERVICE";
- private volatile Handler mHandler;
- private HandlerThread mHandlerThread;
-
- @Override
- public IBinder onBind(Intent intent) {
- if (DEBUG) Log.d(TAG, "onBind " + intent);
- if (!SERVICE_INTERFACE.equals(intent.getAction())) {
- if (DEBUG) Log.d(TAG, "bad intent action " + intent.getAction() + "; returning null");
- return null;
- }
- if (mHandlerThread == null) {
- mHandlerThread = new HandlerThread(HANDLER_THREAD_NAME);
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
- }
- if (mWrapper == null) {
- mWrapper = new ResolverRankerServiceWrapper();
- }
- return mWrapper;
- }
-
- @Override
- public void onDestroy() {
- mHandler = null;
- if (mHandlerThread != null) {
- mHandlerThread.quitSafely();
- }
- super.onDestroy();
- }
-
- private static void sendResult(List<ResolverTarget> targets, IResolverRankerResult result) {
- try {
- result.sendResult(targets);
- } catch (Exception e) {
- Log.e(TAG, "failed to send results: " + e);
- }
- }
-
- private class ResolverRankerServiceWrapper extends IResolverRankerService.Stub {
-
- @Override
- public void predict(final List<ResolverTarget> targets, final IResolverRankerResult result)
- throws RemoteException {
- Runnable predictRunnable = new Runnable() {
- @Override
- public void run() {
- try {
- if (DEBUG) {
- Log.d(TAG, "predict calls onPredictSharingProbabilities.");
- }
- onPredictSharingProbabilities(targets);
- sendResult(targets, result);
- } catch (Exception e) {
- Log.e(TAG, "onPredictSharingProbabilities failed; send null results: " + e);
- sendResult(null, result);
- }
- }
- };
- final Handler h = mHandler;
- if (h != null) {
- h.post(predictRunnable);
- }
- }
-
- @Override
- public void train(final List<ResolverTarget> targets, final int selectedPosition)
- throws RemoteException {
- Runnable trainRunnable = new Runnable() {
- @Override
- public void run() {
- try {
- if (DEBUG) {
- Log.d(TAG, "train calls onTranRankingModel");
- }
- onTrainRankingModel(targets, selectedPosition);
- } catch (Exception e) {
- Log.e(TAG, "onTrainRankingModel failed; skip train: " + e);
- }
- }
- };
- final Handler h = mHandler;
- if (h != null) {
- h.post(trainRunnable);
- }
- }
- }
-}
diff --git a/core/java/android/service/resolver/ResolverTarget.java b/core/java/android/service/resolver/ResolverTarget.java
deleted file mode 100644
index fb3e2d7..0000000
--- a/core/java/android/service/resolver/ResolverTarget.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.resolver;
-
-import android.annotation.SystemApi;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.ArrayMap;
-
-import java.util.Map;
-
-/**
- * A ResolverTarget contains features by which an app or option will be ranked, in
- * {@link ResolverRankerService}.
- * @hide
- */
-@SystemApi
-public final class ResolverTarget implements Parcelable {
- private static final String TAG = "ResolverTarget";
-
- /**
- * a float score for recency of last use.
- */
- private float mRecencyScore;
-
- /**
- * a float score for total time spent.
- */
- private float mTimeSpentScore;
-
- /**
- * a float score for number of launches.
- */
- private float mLaunchScore;
-
- /**
- * a float score for number of selected.
- */
- private float mChooserScore;
-
- /**
- * a float score for the probability to be selected.
- */
- private float mSelectProbability;
-
- // constructor for the class.
- public ResolverTarget() {}
-
- ResolverTarget(Parcel in) {
- mRecencyScore = in.readFloat();
- mTimeSpentScore = in.readFloat();
- mLaunchScore = in.readFloat();
- mChooserScore = in.readFloat();
- mSelectProbability = in.readFloat();
- }
-
- /**
- * Gets the score for how recently the target was used in the foreground.
- *
- * @return a float score whose range is [0, 1]. The higher the score is, the more recently the
- * target was used.
- */
- public float getRecencyScore() {
- return mRecencyScore;
- }
-
- /**
- * Sets the score for how recently the target was used in the foreground.
- *
- * @param recencyScore a float score whose range is [0, 1]. The higher the score is, the more
- * recently the target was used.
- */
- public void setRecencyScore(float recencyScore) {
- this.mRecencyScore = recencyScore;
- }
-
- /**
- * Gets the score for how long the target has been used in the foreground.
- *
- * @return a float score whose range is [0, 1]. The higher the score is, the longer the target
- * has been used for.
- */
- public float getTimeSpentScore() {
- return mTimeSpentScore;
- }
-
- /**
- * Sets the score for how long the target has been used in the foreground.
- *
- * @param timeSpentScore a float score whose range is [0, 1]. The higher the score is, the
- * longer the target has been used for.
- */
- public void setTimeSpentScore(float timeSpentScore) {
- this.mTimeSpentScore = timeSpentScore;
- }
-
- /**
- * Gets the score for how many times the target has been launched to the foreground.
- *
- * @return a float score whose range is [0, 1]. The higher the score is, the more times the
- * target has been launched.
- */
- public float getLaunchScore() {
- return mLaunchScore;
- }
-
- /**
- * Sets the score for how many times the target has been launched to the foreground.
- *
- * @param launchScore a float score whose range is [0, 1]. The higher the score is, the more
- * times the target has been launched.
- */
- public void setLaunchScore(float launchScore) {
- this.mLaunchScore = launchScore;
- }
-
- /**
- * Gets the score for how many times the target has been selected by the user to share the same
- * types of content.
- *
- * @return a float score whose range is [0, 1]. The higher the score is, the
- * more times the target has been selected by the user to share the same types of content for.
- */
- public float getChooserScore() {
- return mChooserScore;
- }
-
- /**
- * Sets the score for how many times the target has been selected by the user to share the same
- * types of content.
- *
- * @param chooserScore a float score whose range is [0, 1]. The higher the score is, the more
- * times the target has been selected by the user to share the same types
- * of content for.
- */
- public void setChooserScore(float chooserScore) {
- this.mChooserScore = chooserScore;
- }
-
- /**
- * Gets the probability of how likely this target will be selected by the user.
- *
- * @return a float score whose range is [0, 1]. The higher the score is, the more likely the
- * user is going to select this target.
- */
- public float getSelectProbability() {
- return mSelectProbability;
- }
-
- /**
- * Sets the probability for how like this target will be selected by the user.
- *
- * @param selectProbability a float score whose range is [0, 1]. The higher the score is, the
- * more likely tht user is going to select this target.
- */
- public void setSelectProbability(float selectProbability) {
- this.mSelectProbability = selectProbability;
- }
-
- // serialize the class to a string.
- @Override
- public String toString() {
- return "ResolverTarget{"
- + mRecencyScore + ", "
- + mTimeSpentScore + ", "
- + mLaunchScore + ", "
- + mChooserScore + ", "
- + mSelectProbability + "}";
- }
-
- // describes the kinds of special objects contained in this Parcelable instance's marshaled
- // representation.
- @Override
- public int describeContents() {
- return 0;
- }
-
- // flattens this object in to a Parcel.
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeFloat(mRecencyScore);
- dest.writeFloat(mTimeSpentScore);
- dest.writeFloat(mLaunchScore);
- dest.writeFloat(mChooserScore);
- dest.writeFloat(mSelectProbability);
- }
-
- // creator definition for the class.
- public static final Creator<ResolverTarget> CREATOR
- = new Creator<ResolverTarget>() {
- @Override
- public ResolverTarget createFromParcel(Parcel source) {
- return new ResolverTarget(source);
- }
-
- @Override
- public ResolverTarget[] newArray(int size) {
- return new ResolverTarget[size];
- }
- };
-}
diff --git a/core/java/android/text/method/DateKeyListener.java b/core/java/android/text/method/DateKeyListener.java
index e14cd2c..0accbf6 100644
--- a/core/java/android/text/method/DateKeyListener.java
+++ b/core/java/android/text/method/DateKeyListener.java
@@ -22,6 +22,7 @@
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -37,8 +38,11 @@
public class DateKeyListener extends NumberKeyListener
{
public int getInputType() {
- return InputType.TYPE_CLASS_DATETIME
- | InputType.TYPE_DATETIME_VARIATION_DATE;
+ if (mNeedsAdvancedInput) {
+ return InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+ } else {
+ return InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_DATE;
+ }
}
@Override
@@ -65,7 +69,13 @@
final boolean success = NumberKeyListener.addDigits(chars, locale)
&& NumberKeyListener.addFormatCharsFromSkeletons(
chars, locale, SKELETONS, SYMBOLS_TO_IGNORE);
- mCharacters = success ? NumberKeyListener.collectionToArray(chars) : CHARACTERS;
+ if (success) {
+ mCharacters = NumberKeyListener.collectionToArray(chars);
+ mNeedsAdvancedInput = !ArrayUtils.containsAll(CHARACTERS, mCharacters);
+ } else {
+ mCharacters = CHARACTERS;
+ mNeedsAdvancedInput = false;
+ }
}
/**
@@ -110,6 +120,7 @@
};
private final char[] mCharacters;
+ private final boolean mNeedsAdvancedInput;
private static final Object sLock = new Object();
@GuardedBy("sLock")
diff --git a/core/java/android/text/method/DateTimeKeyListener.java b/core/java/android/text/method/DateTimeKeyListener.java
index 62e3ade..551db55 100644
--- a/core/java/android/text/method/DateTimeKeyListener.java
+++ b/core/java/android/text/method/DateTimeKeyListener.java
@@ -22,6 +22,7 @@
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -37,10 +38,13 @@
public class DateTimeKeyListener extends NumberKeyListener
{
public int getInputType() {
- return InputType.TYPE_CLASS_DATETIME
- | InputType.TYPE_DATETIME_VARIATION_NORMAL;
+ if (mNeedsAdvancedInput) {
+ return InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+ } else {
+ return InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_NORMAL;
+ }
}
-
+
@Override
@NonNull
protected char[] getAcceptedChars()
@@ -70,7 +74,13 @@
chars, locale, SKELETON_12HOUR, SYMBOLS_TO_IGNORE)
&& NumberKeyListener.addFormatCharsFromSkeleton(
chars, locale, SKELETON_24HOUR, SYMBOLS_TO_IGNORE);
- mCharacters = success ? NumberKeyListener.collectionToArray(chars) : CHARACTERS;
+ if (success) {
+ mCharacters = NumberKeyListener.collectionToArray(chars);
+ mNeedsAdvancedInput = !ArrayUtils.containsAll(CHARACTERS, mCharacters);
+ } else {
+ mCharacters = CHARACTERS;
+ mNeedsAdvancedInput = false;
+ }
}
/**
@@ -114,6 +124,7 @@
};
private final char[] mCharacters;
+ private final boolean mNeedsAdvancedInput;
private static final Object sLock = new Object();
@GuardedBy("sLock")
diff --git a/core/java/android/text/method/DigitsKeyListener.java b/core/java/android/text/method/DigitsKeyListener.java
index 26c69ab..d9f2dcf 100644
--- a/core/java/android/text/method/DigitsKeyListener.java
+++ b/core/java/android/text/method/DigitsKeyListener.java
@@ -27,6 +27,7 @@
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -42,8 +43,12 @@
public class DigitsKeyListener extends NumberKeyListener
{
private char[] mAccepted;
+ private boolean mNeedsAdvancedInput;
private final boolean mSign;
private final boolean mDecimal;
+ private final boolean mStringMode;
+ @Nullable
+ private final Locale mLocale;
private static final String DEFAULT_DECIMAL_POINT_CHARS = ".";
private static final String DEFAULT_SIGN_CHARS = "-+";
@@ -112,11 +117,17 @@
this(locale, false, false);
}
- private void setToCompat(boolean sign, boolean decimal) {
+ private void setToCompat() {
mDecimalPointChars = DEFAULT_DECIMAL_POINT_CHARS;
mSignChars = DEFAULT_SIGN_CHARS;
- final int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);
+ final int kind = (mSign ? SIGN : 0) | (mDecimal ? DECIMAL : 0);
mAccepted = COMPATIBILITY_CHARACTERS[kind];
+ mNeedsAdvancedInput = false;
+ }
+
+ private void calculateNeedForAdvancedInput() {
+ final int kind = (mSign ? SIGN : 0) | (mDecimal ? DECIMAL : 0);
+ mNeedsAdvancedInput = !ArrayUtils.containsAll(COMPATIBILITY_CHARACTERS[kind], mAccepted);
}
// Takes a sign string and strips off its bidi controls, if any.
@@ -144,14 +155,16 @@
public DigitsKeyListener(@Nullable Locale locale, boolean sign, boolean decimal) {
mSign = sign;
mDecimal = decimal;
+ mStringMode = false;
+ mLocale = locale;
if (locale == null) {
- setToCompat(sign, decimal);
+ setToCompat();
return;
}
LinkedHashSet<Character> chars = new LinkedHashSet<>();
final boolean success = NumberKeyListener.addDigits(chars, locale);
if (!success) {
- setToCompat(sign, decimal);
+ setToCompat();
return;
}
if (sign || decimal) {
@@ -161,7 +174,7 @@
final String plusString = stripBidiControls(symbols.getPlusSignString());
if (minusString.length() > 1 || plusString.length() > 1) {
// non-BMP and multi-character signs are not supported.
- setToCompat(sign, decimal);
+ setToCompat();
return;
}
final char minus = minusString.charAt(0);
@@ -181,7 +194,7 @@
final String separatorString = symbols.getDecimalSeparatorString();
if (separatorString.length() > 1) {
// non-BMP and multi-character decimal separators are not supported.
- setToCompat(sign, decimal);
+ setToCompat();
return;
}
final Character separatorChar = Character.valueOf(separatorString.charAt(0));
@@ -190,13 +203,19 @@
}
}
mAccepted = NumberKeyListener.collectionToArray(chars);
+ calculateNeedForAdvancedInput();
}
private DigitsKeyListener(@NonNull final String accepted) {
mSign = false;
mDecimal = false;
+ mStringMode = true;
+ mLocale = null;
mAccepted = new char[accepted.length()];
accepted.getChars(0, accepted.length(), mAccepted, 0);
+ // Theoretically we may need advanced input, but for backward compatibility, we don't change
+ // the input type.
+ mNeedsAdvancedInput = false;
}
/**
@@ -280,13 +299,38 @@
return result;
}
- public int getInputType() {
- int contentType = InputType.TYPE_CLASS_NUMBER;
- if (mSign) {
- contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
+ /**
+ * Returns a DigitsKeyListener based on an the settings of a existing DigitsKeyListener, with
+ * the locale modified.
+ *
+ * @hide
+ */
+ @NonNull
+ public static DigitsKeyListener getInstance(
+ @Nullable Locale locale,
+ @NonNull DigitsKeyListener listener) {
+ if (listener.mStringMode) {
+ return listener; // string-mode DigitsKeyListeners have no locale.
+ } else {
+ return getInstance(locale, listener.mSign, listener.mDecimal);
}
- if (mDecimal) {
- contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL;
+ }
+
+ /**
+ * Returns the input type for the listener.
+ */
+ public int getInputType() {
+ int contentType;
+ if (mNeedsAdvancedInput) {
+ contentType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+ } else {
+ contentType = InputType.TYPE_CLASS_NUMBER;
+ if (mSign) {
+ contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
+ }
+ if (mDecimal) {
+ contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL;
+ }
}
return contentType;
}
diff --git a/core/java/android/text/method/TimeKeyListener.java b/core/java/android/text/method/TimeKeyListener.java
index c9f9f9f..5b1db11 100644
--- a/core/java/android/text/method/TimeKeyListener.java
+++ b/core/java/android/text/method/TimeKeyListener.java
@@ -22,6 +22,7 @@
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -37,8 +38,11 @@
public class TimeKeyListener extends NumberKeyListener
{
public int getInputType() {
- return InputType.TYPE_CLASS_DATETIME
- | InputType.TYPE_DATETIME_VARIATION_TIME;
+ if (mNeedsAdvancedInput) {
+ return InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+ } else {
+ return InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_TIME;
+ }
}
@Override
@@ -70,7 +74,13 @@
chars, locale, SKELETON_12HOUR, SYMBOLS_TO_IGNORE)
&& NumberKeyListener.addFormatCharsFromSkeleton(
chars, locale, SKELETON_24HOUR, SYMBOLS_TO_IGNORE);
- mCharacters = success ? NumberKeyListener.collectionToArray(chars) : CHARACTERS;
+ if (success) {
+ mCharacters = NumberKeyListener.collectionToArray(chars);
+ mNeedsAdvancedInput = !ArrayUtils.containsAll(CHARACTERS, mCharacters);
+ } else {
+ mCharacters = CHARACTERS;
+ mNeedsAdvancedInput = false;
+ }
}
/**
@@ -114,6 +124,7 @@
};
private final char[] mCharacters;
+ private final boolean mNeedsAdvancedInput;
private static final Object sLock = new Object();
@GuardedBy("sLock")
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 5d8f336..829b2b7 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -494,7 +494,8 @@
* On TV remotes, switches to viewing live TV. */
public static final int KEYCODE_TV = 170;
/** Key code constant: Window key.
- * On TV remotes, toggles picture-in-picture mode or other windowing functions. */
+ * On TV remotes, toggles picture-in-picture mode or other windowing functions.
+ * On Android Wear devices, triggers a display offset. */
public static final int KEYCODE_WINDOW = 171;
/** Key code constant: Guide key.
* On TV remotes, shows a programming guide. */
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0221040..a376c83 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2750,7 +2750,7 @@
* 1 PFLAG3_SCROLL_INDICATOR_END
* 1 PFLAG3_ASSIST_BLOCKED
* 1 PFLAG3_CLUSTER
- * x * NO LONGER NEEDED, SHOULD BE REUSED *
+ * 1 PFLAG3_IS_AUTOFILLED
* 1 PFLAG3_FINGER_DOWN
* 1 PFLAG3_FOCUSED_BY_DEFAULT
* 11 PFLAG3_AUTO_FILL_MODE_MASK
@@ -2961,6 +2961,14 @@
private static final int PFLAG3_CLUSTER = 0x8000;
/**
+ * Flag indicating that the view is autofilled
+ *
+ * @see #isAutofilled()
+ * @see #setAutofilled(boolean)
+ */
+ private static final int PFLAG3_IS_AUTOFILLED = 0x10000;
+
+ /**
* Indicates that the user is currently touching the screen.
* Currently used for the tooltip positioning only.
*/
@@ -7440,10 +7448,9 @@
* <ol>
* <li>Calling the proper getter method on {@link AutofillValue} to fetch the actual value.
* <li>Passing the actual value to the equivalent setter in the view.
- * <ol>
+ * </ol>
*
* <p>For example, a text-field view would call:
- *
* <pre class="prettyprint">
* CharSequence text = value.getTextValue();
* if (text != null) {
@@ -7451,6 +7458,10 @@
* }
* </pre>
*
+ * <p>If the value is updated asyncronously the next call to
+ * {@link AutofillManager#notifyValueChanged(View)} must happen <u>after</u> the value was
+ * changed to the autofilled value. If not, the view will not be considered autofilled.
+ *
* @param value value to be autofilled.
*/
public void autofill(@SuppressWarnings("unused") AutofillValue value) {
@@ -7461,6 +7472,9 @@
*
* <p>See {@link #autofill(AutofillValue)} and
* {@link #onProvideAutofillVirtualStructure(ViewStructure, int)} for more info.
+ * <p>To indicate that a virtual view was autofilled
+ * <code>@android:drawable/autofilled_highlight</code> should be drawn over it until the data
+ * changes.
*
* @param values map of values to be autofilled, keyed by virtual child id.
*/
@@ -7491,6 +7505,13 @@
}
/**
+ * @hide
+ */
+ public boolean isAutofilled() {
+ return (mPrivateFlags3 & PFLAG3_IS_AUTOFILLED) != 0;
+ }
+
+ /**
* Gets the {@link View}'s current autofill value.
*
* <p>By default returns {@code null}, but views should override it (and
@@ -9131,6 +9152,24 @@
}
/**
+ * @hide
+ */
+ @TestApi
+ public void setAutofilled(boolean isAutofilled) {
+ boolean wasChanged = isAutofilled != isAutofilled();
+
+ if (wasChanged) {
+ if (isAutofilled) {
+ mPrivateFlags3 |= PFLAG3_IS_AUTOFILLED;
+ } else {
+ mPrivateFlags3 &= ~PFLAG3_IS_AUTOFILLED;
+ }
+
+ invalidate();
+ }
+ }
+
+ /**
* Set whether this view should have sound effects enabled for events such as
* clicking and touching.
*
@@ -17117,9 +17156,19 @@
@CallSuper
protected Parcelable onSaveInstanceState() {
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
- if (mStartActivityRequestWho != null) {
+ if (mStartActivityRequestWho != null || isAutofilled()) {
BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);
+
+ if (mStartActivityRequestWho != null) {
+ state.mSavedData |= BaseSavedState.START_ACTIVITY_REQUESTED_WHO_SAVED;
+ }
+
+ if (isAutofilled()) {
+ state.mSavedData |= BaseSavedState.IS_AUTOFILLED;
+ }
+
state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
+ state.mIsAutofilled = isAutofilled();
return state;
}
return BaseSavedState.EMPTY_STATE;
@@ -17189,7 +17238,14 @@
+ "other views do not use the same id.");
}
if (state != null && state instanceof BaseSavedState) {
- mStartActivityRequestWho = ((BaseSavedState) state).mStartActivityRequestWhoSaved;
+ BaseSavedState baseState = (BaseSavedState) state;
+
+ if ((baseState.mSavedData & BaseSavedState.START_ACTIVITY_REQUESTED_WHO_SAVED) != 0) {
+ mStartActivityRequestWho = baseState.mStartActivityRequestWhoSaved;
+ }
+ if ((baseState.mSavedData & BaseSavedState.IS_AUTOFILLED) != 0) {
+ setAutofilled(baseState.mIsAutofilled);
+ }
}
}
@@ -17570,6 +17626,7 @@
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
+ drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
@@ -17870,6 +17927,7 @@
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
+ drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
@@ -17946,6 +18004,7 @@
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
+ drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
@@ -18630,6 +18689,8 @@
// Step 4, draw the children
dispatchDraw(canvas);
+ drawAutofilledHighlight(canvas);
+
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
@@ -18783,6 +18844,8 @@
canvas.restoreToCount(saveCount);
+ drawAutofilledHighlight(canvas);
+
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
@@ -20141,6 +20204,41 @@
}
/**
+ * Get the drawable to be overlayed when a view is autofilled
+ *
+ * @return The drawable
+ *
+ * @throws IllegalStateException if the drawable could not be found.
+ */
+ @NonNull private Drawable getAutofilledDrawable() {
+ // Lazily load the isAutofilled drawable.
+ if (mAttachInfo.mAutofilledDrawable == null) {
+ mAttachInfo.mAutofilledDrawable = mContext.getDrawable(R.drawable.autofilled_highlight);
+
+ if (mAttachInfo.mAutofilledDrawable == null) {
+ throw new IllegalStateException(
+ "Could not find android:drawable/autofilled_highlight");
+ }
+ }
+
+ return mAttachInfo.mAutofilledDrawable;
+ }
+
+ /**
+ * Draw {@link View#isAutofilled()} highlight over view if the view is autofilled.
+ *
+ * @param canvas The canvas to draw on
+ */
+ private void drawAutofilledHighlight(@NonNull Canvas canvas) {
+ if (isAutofilled()) {
+ Drawable autofilledHighlight = getAutofilledDrawable();
+
+ autofilledHighlight.setBounds(0, 0, getWidth(), getHeight());
+ autofilledHighlight.draw(canvas);
+ }
+ }
+
+ /**
* Draw any foreground content for this view.
*
* <p>Foreground content may consist of scroll bars, a {@link #setForeground foreground}
@@ -20808,11 +20906,18 @@
}
/**
- * Look for a child view with the given id. If this view has the given
- * id, return this view.
+ * Finds the first descendant view with the given ID, the view itself if
+ * the ID matches {@link #getId()}, or {@code null} if the ID is invalid
+ * (< 0) or there is no matching view in the hierarchy.
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
*
- * @param id The id to search for.
- * @return The view that has the given id in the hierarchy or null
+ * @param id the ID to search for
+ * @return a view with given ID if found, or {@code null} otherwise
+ * @see View#findViewById(int)
*/
@Nullable
public final <T extends View> T findViewById(@IdRes int id) {
@@ -24298,7 +24403,13 @@
* state in {@link android.view.View#onSaveInstanceState()}.
*/
public static class BaseSavedState extends AbsSavedState {
+ static final int START_ACTIVITY_REQUESTED_WHO_SAVED = 0b1;
+ static final int IS_AUTOFILLED = 0b10;
+
+ // Flags that describe what data in this state is valid
+ int mSavedData;
String mStartActivityRequestWhoSaved;
+ boolean mIsAutofilled;
/**
* Constructor used when reading from a parcel. Reads the state of the superclass.
@@ -24318,7 +24429,9 @@
*/
public BaseSavedState(Parcel source, ClassLoader loader) {
super(source, loader);
+ mSavedData = source.readInt();
mStartActivityRequestWhoSaved = source.readString();
+ mIsAutofilled = source.readBoolean();
}
/**
@@ -24333,7 +24446,10 @@
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
+
+ out.writeInt(mSavedData);
out.writeString(mStartActivityRequestWhoSaved);
+ out.writeBoolean(mIsAutofilled);
}
public static final Parcelable.Creator<BaseSavedState> CREATOR
@@ -24734,6 +24850,13 @@
Drawable mAccessibilityFocusDrawable;
/**
+ * The drawable for highlighting autofilled views.
+ *
+ * @see #isAutofilled()
+ */
+ Drawable mAutofilledDrawable;
+
+ /**
* Show where the margins, bounds and layout bounds are for each view.
*/
boolean mDebugLayout = SystemProperties.getBoolean(DEBUG_LAYOUT_PROPERTY, false);
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 0053caa..6dd8ecf 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1308,15 +1308,22 @@
}
/**
- * Finds a view that was identified by the id attribute from the XML that
- * was processed in {@link android.app.Activity#onCreate}. This will
- * implicitly call {@link #getDecorView} for you, with all of the
- * associated side-effects.
+ * Finds a view that was identified by the {@code android:id} XML attribute
+ * that was processed in {@link android.app.Activity#onCreate}. This will
+ * implicitly call {@link #getDecorView} with all of the associated
+ * side-effects.
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
*
- * @return The view if found or null otherwise.
+ * @param id the ID to search for
+ * @return a view with given ID if found, or {@code null} otherwise
+ * @see View#findViewById(int)
*/
@Nullable
- public View findViewById(@IdRes int id) {
+ public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 35276cc..5e6ace7 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -41,6 +41,7 @@
import android.view.IWindow;
import android.view.View;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IntPair;
import java.util.ArrayList;
@@ -126,6 +127,8 @@
final Handler mHandler;
+ final Handler.Callback mCallback;
+
boolean mIsEnabled;
int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
@@ -217,12 +220,12 @@
// is now off an exception will be thrown. We want to have the exception
// enforcement to guard against apps that fire unnecessary accessibility
// events when accessibility is off.
- mHandler.obtainMessage(MyHandler.MSG_SET_STATE, state, 0).sendToTarget();
+ mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget();
}
@Override
public void notifyServicesStateChanged() {
- mHandler.obtainMessage(MyHandler.MSG_NOTIFY_SERVICES_STATE_CHANGED).sendToTarget();
+ mHandler.obtainMessage(MyCallback.MSG_NOTIFY_SERVICES_STATE_CHANGED).sendToTarget();
}
@Override
@@ -271,7 +274,8 @@
public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
// Constructor can't be chained because we can't create an instance of an inner class
// before calling another constructor.
- mHandler = new MyHandler(context.getMainLooper());
+ mCallback = new MyCallback();
+ mHandler = new Handler(context.getMainLooper(), mCallback);
mUserId = userId;
synchronized (mLock) {
tryConnectToServiceLocked(service);
@@ -288,6 +292,7 @@
* @hide
*/
public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) {
+ mCallback = new MyCallback();
mHandler = handler;
mUserId = userId;
synchronized (mLock) {
@@ -303,6 +308,14 @@
}
/**
+ * @hide
+ */
+ @VisibleForTesting
+ public Handler.Callback getCallback() {
+ return mCallback;
+ }
+
+ /**
* Returns if the accessibility in the system is enabled.
*
* @return True if accessibility is enabled, false otherwise.
@@ -711,15 +724,15 @@
mIsHighTextContrastEnabled = highTextContrastEnabled;
if (wasEnabled != enabled) {
- mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED);
+ mHandler.sendEmptyMessage(MyCallback.MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED);
}
if (wasTouchExplorationEnabled != touchExplorationEnabled) {
- mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_EXPLORATION_STATE_CHANGED);
+ mHandler.sendEmptyMessage(MyCallback.MSG_NOTIFY_EXPLORATION_STATE_CHANGED);
}
if (wasHighTextContrastEnabled != highTextContrastEnabled) {
- mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED);
+ mHandler.sendEmptyMessage(MyCallback.MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED);
}
}
@@ -960,19 +973,15 @@
}
}
- private final class MyHandler extends Handler {
+ private final class MyCallback implements Handler.Callback {
public static final int MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED = 1;
public static final int MSG_NOTIFY_EXPLORATION_STATE_CHANGED = 2;
public static final int MSG_NOTIFY_HIGH_TEXT_CONTRAST_STATE_CHANGED = 3;
public static final int MSG_SET_STATE = 4;
public static final int MSG_NOTIFY_SERVICES_STATE_CHANGED = 5;
- public MyHandler(Looper looper) {
- super(looper, null, false);
- }
-
@Override
- public void handleMessage(Message message) {
+ public boolean handleMessage(Message message) {
switch (message.what) {
case MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED: {
handleNotifyAccessibilityStateChanged();
@@ -998,6 +1007,7 @@
}
} break;
}
+ return true;
}
}
}
diff --git a/core/java/android/view/autofill/AutoFillId.aidl b/core/java/android/view/autofill/AutoFillId.aidl
deleted file mode 100644
index fc57ce7..0000000
--- a/core/java/android/view/autofill/AutoFillId.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * Copyright (c) 2017, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.autofill;
-
-// @deprecated TODO(b/35956626): remove once clients use AutofillId
-parcelable AutoFillId;
\ No newline at end of file
diff --git a/core/java/android/view/autofill/AutoFillId.java b/core/java/android/view/autofill/AutoFillId.java
deleted file mode 100644
index 081fb02..0000000
--- a/core/java/android/view/autofill/AutoFillId.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.view.autofill;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * @hide
- * @deprecated TODO(b/35956626): remove once clients use getAutoFilltype
- */
-@Deprecated
-public final class AutoFillId implements Parcelable {
-
- private final AutofillId mRealId;
-
- /** @hide */
- public AutoFillId(AutofillId daRealId) {
- this.mRealId = daRealId;
- }
-
- @Override
- public int hashCode() {
- return mRealId.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- if (obj == null) return false;
- if (getClass() != obj.getClass()) return false;
- final AutoFillId other = (AutoFillId) obj;
- return mRealId.equals(other.mRealId);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeParcelable(mRealId, 0);
- }
-
- private AutoFillId(Parcel parcel) {
- mRealId = parcel.readParcelable(null);
- }
-
- /** @hide */
- public AutofillId getDaRealId() {
- return mRealId;
- }
-
- /** @hide */
- public static AutoFillId forDaRealId(AutofillId id) {
- return id == null ? null : new AutoFillId(id);
- }
-
- public static final Parcelable.Creator<AutoFillId> CREATOR =
- new Parcelable.Creator<AutoFillId>() {
- @Override
- public AutoFillId createFromParcel(Parcel source) {
- return new AutoFillId(source);
- }
-
- @Override
- public AutoFillId[] newArray(int size) {
- return new AutoFillId[size];
- }
- };
-}
diff --git a/core/java/android/view/autofill/AutoFillType.java b/core/java/android/view/autofill/AutoFillType.java
deleted file mode 100644
index c508ba4..0000000
--- a/core/java/android/view/autofill/AutoFillType.java
+++ /dev/null
@@ -1,188 +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.view.autofill;
-
-import static android.view.autofill.Helper.DEBUG;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.View;
-
-/**
- * Defines the type of a object that can be used to autofill a {@link View} so the
- * {@link android.service.autofill.AutofillService} can use the proper {@link AutofillValue} to
- * fill it.
- *
- * @hide
- * @deprecated TODO(b/35956626): remove once clients use getAutoFilltype
- */
-@Deprecated
-public final class AutoFillType implements Parcelable {
-
- // Cached instance for types that don't have subtype; it uses the "lazy initialization holder
- // class idiom" (Effective Java, Item 71) to avoid memory utilization when autofill is not
- // enabled.
- private static class DefaultTypesHolder {
- static final AutoFillType TEXT = new AutoFillType(TYPE_TEXT);
- static final AutoFillType TOGGLE = new AutoFillType(TYPE_TOGGLE);
- static final AutoFillType LIST = new AutoFillType(TYPE_LIST);
- static final AutoFillType DATE = new AutoFillType(TYPE_DATE);
- }
-
- private static final int TYPE_TEXT = 1;
- private static final int TYPE_TOGGLE = 2;
- private static final int TYPE_LIST = 3;
- private static final int TYPE_DATE = 4;
-
- private final int mType;
-
- private AutoFillType(int type) {
- mType = type;
- }
-
- /**
- * Checks if this is a type for a text field, which is filled by a {@link CharSequence}.
- */
- public boolean isText() {
- return mType == TYPE_TEXT;
- }
-
- /**
- * Checks if this is a a type for a togglable field, which is filled by a {@code boolean}.
- */
- public boolean isToggle() {
- return mType == TYPE_TOGGLE;
- }
-
- /**
- * Checks if this is a type for a selection list field, which is filled by a {@code integer}
- * representing the element index inside the list (starting at {@code 0}.
- */
- public boolean isList() {
- return mType == TYPE_LIST;
- }
-
- /**
- * Checks if this is a type for a date and time, which is represented by a long representing
- * the number of milliseconds since the standard base time known as "the epoch", namely
- * January 1, 1970, 00:00:00 GMT (see {@link java.util.Date#getTime()}.
- */
- public boolean isDate() {
- return mType == TYPE_DATE;
- }
-
- /////////////////////////////////////
- // Object "contract" methods. //
- /////////////////////////////////////
-
- @Override
- public String toString() {
- if (!DEBUG) return super.toString();
-
- return "AutoFillType [type=" + mType + "]";
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + mType;
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- if (obj == null) return false;
- if (getClass() != obj.getClass()) return false;
- final AutoFillType other = (AutoFillType) obj;
- if (mType != other.mType) return false;
- return true;
- }
-
- /////////////////////////////////////
- // Parcelable "contract" methods. //
- /////////////////////////////////////
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeInt(mType);
- }
-
- private AutoFillType(Parcel parcel) {
- mType = parcel.readInt();
- }
-
- public static final Parcelable.Creator<AutoFillType> CREATOR =
- new Parcelable.Creator<AutoFillType>() {
- @Override
- public AutoFillType createFromParcel(Parcel source) {
- return new AutoFillType(source);
- }
-
- @Override
- public AutoFillType[] newArray(int size) {
- return new AutoFillType[size];
- }
- };
-
- ////////////////////
- // Factory methods //
- ////////////////////
-
- /**
- * Creates a text field type, which is filled by a {@link CharSequence}.
- *
- * <p>See {@link #isText()} for more info.
- */
- public static AutoFillType forText() {
- return DefaultTypesHolder.TEXT;
- }
-
- /**
- * Creates a type that can be toggled which is filled by a {@code boolean}.
- *
- * <p>See {@link #isToggle()} for more info.
- */
- public static AutoFillType forToggle() {
- return DefaultTypesHolder.TOGGLE;
- }
-
- /**
- * Creates a selection list, which is filled by a {@code integer} representing the element index
- * inside the list (starting at {@code 0}.
- *
- * <p>See {@link #isList()} for more info.
- */
- public static AutoFillType forList() {
- return DefaultTypesHolder.LIST;
- }
-
- /**
- * Creates a type that represents a date.
- *
- * <p>See {@link #isDate()} for more info.
- */
- public static AutoFillType forDate() {
- return DefaultTypesHolder.DATE;
- }
-}
diff --git a/core/java/android/view/autofill/AutoFillValue.java b/core/java/android/view/autofill/AutoFillValue.java
deleted file mode 100644
index 4774d8f..0000000
--- a/core/java/android/view/autofill/AutoFillValue.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.autofill;
-
-import static android.view.autofill.Helper.DEBUG;
-
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.View;
-
-/**
- * @hide
- * @deprecated TODO(b/35956626): remove once clients use AutofillValue
- */
-@Deprecated
-public final class AutoFillValue implements Parcelable {
- private final AutofillValue mRealValue;
-
- private AutoFillValue(AutofillValue daRealValue) {
- this.mRealValue = daRealValue;
- }
-
- /**
- * Gets the value to autofill a text field.
- *
- * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.
- */
- public CharSequence getTextValue() {
- return mRealValue.getTextValue();
- }
-
- /**
- * Gets the value to autofill a toggable field.
- *
- * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.
- */
- public boolean getToggleValue() {
- return mRealValue.getToggleValue();
- }
-
- /**
- * Gets the value to autofill a selection list field.
- *
- * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.
- */
- public int getListValue() {
- return mRealValue.getListValue();
- }
-
- /**
- * Gets the value to autofill a date field.
- *
- * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.
- */
- public long getDateValue() {
- return mRealValue.getDateValue();
- }
-
- /////////////////////////////////////
- // Object "contract" methods. //
- /////////////////////////////////////
-
- @Override
- public int hashCode() {
- return mRealValue.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- if (obj == null) return false;
- if (getClass() != obj.getClass()) return false;
- final AutoFillValue other = (AutoFillValue) obj;
- return mRealValue.equals(other.mRealValue);
- }
-
- @Override
- public String toString() {
- if (!DEBUG) return super.toString();
-
- return mRealValue.toString();
- }
-
- /////////////////////////////////////
- // Parcelable "contract" methods. //
- /////////////////////////////////////
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeParcelable(mRealValue, 0);
- }
-
- private AutoFillValue(Parcel parcel) {
- mRealValue = parcel.readParcelable(null);
- }
-
- public static final Parcelable.Creator<AutoFillValue> CREATOR =
- new Parcelable.Creator<AutoFillValue>() {
- @Override
- public AutoFillValue createFromParcel(Parcel source) {
- return new AutoFillValue(source);
- }
-
- @Override
- public AutoFillValue[] newArray(int size) {
- return new AutoFillValue[size];
- }
- };
-
- ////////////////////
- // Factory methods //
- ////////////////////
- /**
- * Creates a new {@link AutoFillValue} to autofill a {@link View} representing a text field.
- *
- * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.
- */
- @Nullable
- public static AutoFillValue forText(@Nullable CharSequence value) {
- return value == null ? null : new AutoFillValue(AutofillValue.forText(value));
- }
-
- /**
- * Creates a new {@link AutoFillValue} to autofill a {@link View} representing a toggable
- * field.
- *
- * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.
- */
- public static AutoFillValue forToggle(boolean value) {
- return new AutoFillValue(AutofillValue.forToggle(value));
- }
-
- /**
- * Creates a new {@link AutoFillValue} to autofill a {@link View} representing a selection
- * list.
- *
- * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.
- */
- public static AutoFillValue forList(int value) {
- return new AutoFillValue(AutofillValue.forList(value));
- }
-
- /**
- * Creates a new {@link AutoFillValue} to autofill a {@link View} representing a date.
- *
- * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.
- */
- public static AutoFillValue forDate(long date) {
- return new AutoFillValue(AutofillValue.forDate(date));
- }
-
- /** @hide */
- public static AutoFillValue forDaRealValue(AutofillValue daRealValue) {
- return new AutoFillValue(daRealValue);
- }
-
- /** @hide */
- public AutofillValue getDaRealValue() {
- return mRealValue;
- }
-}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 19980fb..07fad60 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -44,6 +44,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.List;
+import java.util.Objects;
/**
* App entry point to the AutoFill Framework.
@@ -91,6 +92,8 @@
*/
public static final String EXTRA_DATA_EXTRAS = "android.view.autofill.extra.DATA_EXTRAS";
+ static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
+
// Public flags start from the lowest bit
/**
* Indicates autofill was explicitly requested by the user.
@@ -115,6 +118,9 @@
private boolean mHasSession;
private boolean mEnabled;
+ /** If a view changes to this mapping the autofill operation was successful */
+ @Nullable private ParcelableMap mLastAutofilledData;
+
/** @hide */
public interface AutofillClient {
/**
@@ -160,7 +166,31 @@
}
/**
- * Checkes whether autofill is enabled for the current user.
+ * Restore state after activity lifecycle
+ *
+ * @param savedInstanceState The state to be restored
+ *
+ * {@hide}
+ */
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ mLastAutofilledData = savedInstanceState.getParcelable(LAST_AUTOFILLED_DATA_TAG);
+ }
+
+ /**
+ * Save state before activity lifecycle
+ *
+ * @param outState Place to store the state
+ *
+ * {@hide}
+ */
+ public void onSaveInstanceState(Bundle outState) {
+ if (mLastAutofilledData != null) {
+ outState.putParcelable(LAST_AUTOFILLED_DATA_TAG, mLastAutofilledData);
+ }
+ }
+
+ /**
+ * Checks whether autofill is enabled for the current user.
*
* <p>Typically used to determine whether the option to explicitly request autofill should
* be offered - see {@link #requestAutofill(View)}.
@@ -311,12 +341,43 @@
* @param view view whose value changed.
*/
public void notifyValueChanged(View view) {
+ AutofillId id = null;
+ boolean valueWasRead = false;
+ AutofillValue value = null;
+
+ // If the session is gone some fields might still be highlighted, hence we have to remove
+ // the isAutofilled property even if no sessions are active.
+ if (mLastAutofilledData == null) {
+ view.setAutofilled(false);
+ } else {
+ id = getAutofillId(view);
+ if (mLastAutofilledData.containsKey(id)) {
+ value = view.getAutofillValue();
+ valueWasRead = true;
+
+ if (Objects.equals(mLastAutofilledData.get(id), value)) {
+ view.setAutofilled(true);
+ } else {
+ view.setAutofilled(false);
+ mLastAutofilledData.remove(id);
+ }
+ } else {
+ view.setAutofilled(false);
+ }
+ }
+
if (!mEnabled || !mHasSession) {
return;
}
- final AutofillId id = getAutofillId(view);
- final AutofillValue value = view.getAutofillValue();
+ if (id == null) {
+ id = getAutofillId(view);
+ }
+
+ if (!valueWasRead) {
+ value = view.getAutofillValue();
+ }
+
updateSession(id, null, value, FLAG_VALUE_CHANGED);
}
@@ -535,6 +596,23 @@
}
}
+ /**
+ * Sets a view as autofilled if the current value is the {code targetValue}.
+ *
+ * @param view The view that is to be autofilled
+ * @param targetValue The value we want to fill into view
+ */
+ private void setAutofilledIfValuesIs(@NonNull View view, @Nullable AutofillValue targetValue) {
+ AutofillValue currentValue = view.getAutofillValue();
+ if (Objects.equals(currentValue, targetValue)) {
+ if (mLastAutofilledData == null) {
+ mLastAutofilledData = new ParcelableMap(1);
+ }
+ mLastAutofilledData.put(getAutofillId(view), targetValue);
+ view.setAutofilled(true);
+ }
+ }
+
private void handleAutofill(IBinder windowToken, List<AutofillId> ids,
List<AutofillValue> values) {
final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken);
@@ -568,7 +646,19 @@
}
valuesByParent.put(id.getVirtualChildId(), value);
} else {
+ // Mark the view as to be autofilled with 'value'
+ if (mLastAutofilledData == null) {
+ mLastAutofilledData = new ParcelableMap(itemCount - i);
+ }
+ mLastAutofilledData.put(id, value);
+
view.autofill(value);
+
+ // Set as autofilled if the values match now, e.g. when the value was updated
+ // synchronously.
+ // If autofill happens async, the view is set to autofilled in notifyValueChanged.
+ setAutofilledIfValuesIs(view, value);
+
numApplied++;
}
}
diff --git a/core/java/android/view/autofill/ParcelableMap.java b/core/java/android/view/autofill/ParcelableMap.java
new file mode 100644
index 0000000..f97b1a0
--- /dev/null
+++ b/core/java/android/view/autofill/ParcelableMap.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 android.view.autofill;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A parcelable HashMap for {@link AutofillId} and {@link AutofillValue}
+ *
+ * {@hide}
+ */
+class ParcelableMap extends HashMap<AutofillId, AutofillValue> implements Parcelable {
+ ParcelableMap(int size) {
+ super(size);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(size());
+
+ for (Map.Entry<AutofillId, AutofillValue> entry : entrySet()) {
+ dest.writeParcelable(entry.getKey(), 0);
+ dest.writeParcelable(entry.getValue(), 0);
+ }
+ }
+
+ public static final Parcelable.Creator<ParcelableMap> CREATOR =
+ new Parcelable.Creator<ParcelableMap>() {
+ @Override
+ public ParcelableMap createFromParcel(Parcel source) {
+ int size = source.readInt();
+
+ ParcelableMap map = new ParcelableMap(size);
+
+ for (int i = 0; i < size; i++) {
+ AutofillId key = source.readParcelable(null);
+ AutofillValue value = source.readParcelable(null);
+
+ map.put(key, value);
+ }
+
+ return map;
+ }
+
+ @Override
+ public ParcelableMap[] newArray(int size) {
+ return new ParcelableMap[size];
+ }
+ };
+}
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index da00d9c..0bf2460 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -1443,7 +1443,7 @@
@Override
public void requestChildFocus(View child, View focused) {
- if (focused.getRevealOnFocusHint()) {
+ if (focused != null && focused.getRevealOnFocusHint()) {
if (!mIsLayoutDirty) {
scrollToChild(focused);
} else {
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index ab4cce4..2e8faee 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -76,6 +76,7 @@
private boolean mDropDownVerticalOffsetSet;
private boolean mIsAnimatedFromAnchor = true;
private boolean mOverlapAnchor;
+ private boolean mOverlapAnchorSet;
private int mDropDownGravity = Gravity.NO_GRAVITY;
@@ -681,7 +682,9 @@
mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
mPopup.setTouchInterceptor(mTouchInterceptor);
mPopup.setEpicenterBounds(mEpicenterBounds);
- mPopup.setOverlapAnchor(mOverlapAnchor);
+ if (mOverlapAnchorSet) {
+ mPopup.setOverlapAnchor(mOverlapAnchor);
+ }
mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset,
mDropDownVerticalOffset, mDropDownGravity);
mDropDownList.setSelection(ListView.INVALID_POSITION);
@@ -1259,6 +1262,7 @@
* @hide
*/
public void setOverlapAnchor(boolean overlap) {
+ mOverlapAnchorSet = true;
mOverlapAnchor = overlap;
}
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 0a9e361..8fc4f50 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -1468,7 +1468,7 @@
@Override
public void requestChildFocus(View child, View focused) {
- if (focused.getRevealOnFocusHint()) {
+ if (focused != null && focused.getRevealOnFocusHint()) {
if (!mIsLayoutDirty) {
scrollToChild(focused);
} else {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1b60ebc..7d9253b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -598,6 +598,11 @@
private Layout mLayout;
private boolean mLocalesChanged = false;
+ // True if setKeyListener() has been explicitly called
+ private boolean mListenerChanged = false;
+ // True if internationalized input should be used for numbers and date and time.
+ private final boolean mUseInternationalizedInput;
+
@ViewDebug.ExportedProperty(category = "text")
private int mGravity = Gravity.TOP | Gravity.START;
private boolean mHorizontallyScrolling;
@@ -1356,6 +1361,9 @@
final boolean numberPasswordInputType = variation
== (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
+ mUseInternationalizedInput =
+ context.getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O;
+
if (inputMethod != null) {
Class<?> c;
@@ -1398,15 +1406,11 @@
mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
} else if (numeric != 0) {
createEditorIfNeeded();
- mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
- (numeric & DECIMAL) != 0);
- inputType = EditorInfo.TYPE_CLASS_NUMBER;
- if ((numeric & SIGNED) != 0) {
- inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
- }
- if ((numeric & DECIMAL) != 0) {
- inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
- }
+ mEditor.mKeyListener = DigitsKeyListener.getInstance(
+ mUseInternationalizedInput ? getTextLocale() : null,
+ (numeric & SIGNED) != 0,
+ (numeric & DECIMAL) != 0);
+ inputType = mEditor.mKeyListener.getInputType();
mEditor.mInputType = inputType;
} else if (autotext || autocap != -1) {
TextKeyListener.Capitalize cap;
@@ -2308,19 +2312,13 @@
* @attr ref android.R.styleable#TextView_autoText
*/
public void setKeyListener(KeyListener input) {
+ mListenerChanged = true;
setKeyListenerOnly(input);
fixFocusableAndClickableSettings();
if (input != null) {
createEditorIfNeeded();
- try {
- mEditor.mInputType = mEditor.mKeyListener.getInputType();
- } catch (IncompatibleClassChangeError e) {
- mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
- }
- // Change inputType, without affecting transformation.
- // No need to applySingleLine since mSingleLine is unchanged.
- setInputTypeSingleLine(mSingleLine);
+ setInputTypeFromEditor();
} else {
if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
}
@@ -2329,6 +2327,17 @@
if (imm != null) imm.restartInput(this);
}
+ private void setInputTypeFromEditor() {
+ try {
+ mEditor.mInputType = mEditor.mKeyListener.getInputType();
+ } catch (IncompatibleClassChangeError e) {
+ mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
+ }
+ // Change inputType, without affecting transformation.
+ // No need to applySingleLine since mSingleLine is unchanged.
+ setInputTypeSingleLine(mSingleLine);
+ }
+
private void setKeyListenerOnly(KeyListener input) {
if (mEditor == null && input == null) return; // null is the default value
@@ -3390,6 +3399,29 @@
return mTextPaint.getTextLocales();
}
+ private void changeListenerLocaleTo(@NonNull Locale locale) {
+ if (mListenerChanged) {
+ // If a listener has been explicitly set, don't change it. We may break something.
+ return;
+ }
+ if (mEditor != null) {
+ KeyListener listener = mEditor.mKeyListener;
+ if (listener instanceof DigitsKeyListener) {
+ listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
+ } else if (listener instanceof DateKeyListener) {
+ listener = DateKeyListener.getInstance(locale);
+ } else if (listener instanceof TimeKeyListener) {
+ listener = TimeKeyListener.getInstance(locale);
+ } else if (listener instanceof DateTimeKeyListener) {
+ listener = DateTimeKeyListener.getInstance(locale);
+ } else {
+ return;
+ }
+ setKeyListenerOnly(listener);
+ setInputTypeFromEditor();
+ }
+ }
+
/**
* Set the default {@link LocaleList} of the text in this TextView to a one-member list
* containing just the given value.
@@ -3401,6 +3433,7 @@
public void setTextLocale(@NonNull Locale locale) {
mLocalesChanged = true;
mTextPaint.setTextLocale(locale);
+ changeListenerLocaleTo(locale);
if (mLayout != null) {
nullLayouts();
requestLayout();
@@ -3422,6 +3455,7 @@
public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
mLocalesChanged = true;
mTextPaint.setTextLocales(locales);
+ changeListenerLocaleTo(locales.get(0));
if (mLayout != null) {
nullLayouts();
requestLayout();
@@ -3433,7 +3467,9 @@
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (!mLocalesChanged) {
- mTextPaint.setTextLocales(LocaleList.getDefault());
+ final LocaleList locales = LocaleList.getDefault();
+ mTextPaint.setTextLocales(locales);
+ changeListenerLocaleTo(locales.get(0));
if (mLayout != null) {
nullLayouts();
requestLayout();
@@ -5567,26 +5603,35 @@
input = TextKeyListener.getInstance(autotext, cap);
} else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
input = DigitsKeyListener.getInstance(
+ mUseInternationalizedInput ? getTextLocale() : null,
(type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
(type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
+ if (mUseInternationalizedInput) {
+ type = input.getInputType(); // Override type, if necessary for i18n.
+ }
} else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
+ final Locale locale = mUseInternationalizedInput ? getTextLocale() : null;
switch (type & EditorInfo.TYPE_MASK_VARIATION) {
case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
- input = DateKeyListener.getInstance();
+ input = DateKeyListener.getInstance(locale);
break;
case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
- input = TimeKeyListener.getInstance();
+ input = TimeKeyListener.getInstance(locale);
break;
default:
- input = DateTimeKeyListener.getInstance();
+ input = DateTimeKeyListener.getInstance(locale);
break;
}
+ if (mUseInternationalizedInput) {
+ type = input.getInputType(); // Override type, if necessary for i18n.
+ }
} else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
input = DialerKeyListener.getInstance();
} else {
input = TextKeyListener.getInstance();
}
setRawInputType(type);
+ mListenerChanged = false;
if (direct) {
createEditorIfNeeded();
mEditor.mKeyListener = input;
diff --git a/core/java/com/android/internal/app/LRResolverRankerService.java b/core/java/com/android/internal/app/LRResolverRankerService.java
deleted file mode 100644
index 1cad7c7..0000000
--- a/core/java/com/android/internal/app/LRResolverRankerService.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.app;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Environment;
-import android.os.IBinder;
-import android.os.storage.StorageManager;
-import android.service.resolver.ResolverRankerService;
-import android.service.resolver.ResolverTarget;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A Logistic Regression based {@link android.service.resolver.ResolverRankerService}, to be used
- * in {@link ResolverComparator}.
- */
-public final class LRResolverRankerService extends ResolverRankerService {
- private static final String TAG = "LRResolverRankerService";
-
- private static final boolean DEBUG = false;
-
- private static final String PARAM_SHARED_PREF_NAME = "resolver_ranker_params";
- private static final String BIAS_PREF_KEY = "bias";
- private static final String VERSION_PREF_KEY = "version";
-
- private static final String LAUNCH_SCORE = "launch";
- private static final String TIME_SPENT_SCORE = "timeSpent";
- private static final String RECENCY_SCORE = "recency";
- private static final String CHOOSER_SCORE = "chooser";
-
- // parameters for a pre-trained model, to initialize the app ranker. When updating the
- // pre-trained model, please update these params, as well as initModel().
- private static final int CURRENT_VERSION = 1;
- private static final float LEARNING_RATE = 0.0001f;
- private static final float REGULARIZER_PARAM = 0.0001f;
-
- private SharedPreferences mParamSharedPref;
- private ArrayMap<String, Float> mFeatureWeights;
- private float mBias;
-
- @Override
- public IBinder onBind(Intent intent) {
- initModel();
- return super.onBind(intent);
- }
-
- @Override
- public void onPredictSharingProbabilities(List<ResolverTarget> targets) {
- final int size = targets.size();
- for (int i = 0; i < size; ++i) {
- ResolverTarget target = targets.get(i);
- ArrayMap<String, Float> features = getFeatures(target);
- target.setSelectProbability(predict(features));
- }
- }
-
- @Override
- public void onTrainRankingModel(List<ResolverTarget> targets, int selectedPosition) {
- final int size = targets.size();
- if (selectedPosition < 0 || selectedPosition >= size) {
- if (DEBUG) {
- Log.d(TAG, "Invalid Position of Selected App " + selectedPosition);
- }
- return;
- }
- final ArrayMap<String, Float> positive = getFeatures(targets.get(selectedPosition));
- final float positiveProbability = targets.get(selectedPosition).getSelectProbability();
- final int targetSize = targets.size();
- for (int i = 0; i < targetSize; ++i) {
- if (i == selectedPosition) {
- continue;
- }
- final ArrayMap<String, Float> negative = getFeatures(targets.get(i));
- final float negativeProbability = targets.get(i).getSelectProbability();
- if (negativeProbability > positiveProbability) {
- update(negative, negativeProbability, false);
- update(positive, positiveProbability, true);
- }
- }
- commitUpdate();
- }
-
- private void initModel() {
- mParamSharedPref = getParamSharedPref();
- mFeatureWeights = new ArrayMap<>(4);
- if (mParamSharedPref == null ||
- mParamSharedPref.getInt(VERSION_PREF_KEY, 0) < CURRENT_VERSION) {
- // Initializing the app ranker to a pre-trained model. When updating the pre-trained
- // model, please increment CURRENT_VERSION, and update LEARNING_RATE and
- // REGULARIZER_PARAM.
- mBias = -1.6568f;
- mFeatureWeights.put(LAUNCH_SCORE, 2.5543f);
- mFeatureWeights.put(TIME_SPENT_SCORE, 2.8412f);
- mFeatureWeights.put(RECENCY_SCORE, 0.269f);
- mFeatureWeights.put(CHOOSER_SCORE, 4.2222f);
- } else {
- mBias = mParamSharedPref.getFloat(BIAS_PREF_KEY, 0.0f);
- mFeatureWeights.put(LAUNCH_SCORE, mParamSharedPref.getFloat(LAUNCH_SCORE, 0.0f));
- mFeatureWeights.put(
- TIME_SPENT_SCORE, mParamSharedPref.getFloat(TIME_SPENT_SCORE, 0.0f));
- mFeatureWeights.put(RECENCY_SCORE, mParamSharedPref.getFloat(RECENCY_SCORE, 0.0f));
- mFeatureWeights.put(CHOOSER_SCORE, mParamSharedPref.getFloat(CHOOSER_SCORE, 0.0f));
- }
- }
-
- private ArrayMap<String, Float> getFeatures(ResolverTarget target) {
- ArrayMap<String, Float> features = new ArrayMap<>(4);
- features.put(RECENCY_SCORE, target.getRecencyScore());
- features.put(TIME_SPENT_SCORE, target.getTimeSpentScore());
- features.put(LAUNCH_SCORE, target.getLaunchScore());
- features.put(CHOOSER_SCORE, target.getChooserScore());
- return features;
- }
-
- private float predict(ArrayMap<String, Float> target) {
- if (target == null) {
- return 0.0f;
- }
- final int featureSize = target.size();
- float sum = 0.0f;
- for (int i = 0; i < featureSize; i++) {
- String featureName = target.keyAt(i);
- float weight = mFeatureWeights.getOrDefault(featureName, 0.0f);
- sum += weight * target.valueAt(i);
- }
- return (float) (1.0 / (1.0 + Math.exp(-mBias - sum)));
- }
-
- private void update(ArrayMap<String, Float> target, float predict, boolean isSelected) {
- if (target == null) {
- return;
- }
- final int featureSize = target.size();
- float error = isSelected ? 1.0f - predict : -predict;
- for (int i = 0; i < featureSize; i++) {
- String featureName = target.keyAt(i);
- float currentWeight = mFeatureWeights.getOrDefault(featureName, 0.0f);
- mBias += LEARNING_RATE * error;
- currentWeight = currentWeight - LEARNING_RATE * REGULARIZER_PARAM * currentWeight +
- LEARNING_RATE * error * target.valueAt(i);
- mFeatureWeights.put(featureName, currentWeight);
- }
- if (DEBUG) {
- Log.d(TAG, "Weights: " + mFeatureWeights + " Bias: " + mBias);
- }
- }
-
- private void commitUpdate() {
- try {
- SharedPreferences.Editor editor = mParamSharedPref.edit();
- editor.putFloat(BIAS_PREF_KEY, mBias);
- final int size = mFeatureWeights.size();
- for (int i = 0; i < size; i++) {
- editor.putFloat(mFeatureWeights.keyAt(i), mFeatureWeights.valueAt(i));
- }
- editor.putInt(VERSION_PREF_KEY, CURRENT_VERSION);
- editor.apply();
- } catch (Exception e) {
- Log.e(TAG, "Failed to commit update" + e);
- }
- }
-
- private SharedPreferences getParamSharedPref() {
- // The package info in the context isn't initialized in the way it is for normal apps,
- // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
- // build the path manually below using the same policy that appears in ContextImpl.
- if (DEBUG) {
- Log.d(TAG, "Context Package Name: " + getPackageName());
- }
- final File prefsFile = new File(new File(
- Environment.getDataUserCePackageDirectory(
- StorageManager.UUID_PRIVATE_INTERNAL, getUserId(), getPackageName()),
- "shared_prefs"),
- PARAM_SHARED_PREF_NAME + ".xml");
- return getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
- }
-}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 622b708..3f1c9ad 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -530,9 +530,6 @@
getMainThreadHandler().removeCallbacks(mPostListReadyRunnable);
mPostListReadyRunnable = null;
}
- if (mAdapter != null && mAdapter.mResolverListController != null) {
- mAdapter.mResolverListController.destroy();
- }
}
@Override
diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java
index 73b62a5..096fcb8 100644
--- a/core/java/com/android/internal/app/ResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverComparator.java
@@ -26,34 +26,20 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.SharedPreferences;
-import android.content.ServiceConnection;
import android.os.Environment;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
import android.os.storage.StorageManager;
import android.os.UserHandle;
-import android.service.resolver.IResolverRankerService;
-import android.service.resolver.IResolverRankerResult;
-import android.service.resolver.ResolverRankerService;
-import android.service.resolver.ResolverTarget;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import java.io.File;
-import java.lang.InterruptedException;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -75,15 +61,11 @@
private static final float RECENCY_MULTIPLIER = 2.f;
- // message types
- private static final int RESOLVER_RANKER_SERVICE_RESULT = 0;
- private static final int RESOLVER_RANKER_RESULT_TIMEOUT = 1;
-
- // timeout for establishing connections with a ResolverRankerService.
- private static final int CONNECTION_COST_TIMEOUT_MILLIS = 200;
- // timeout for establishing connections with a ResolverRankerService, collecting features and
- // predicting ranking scores.
- private static final int WATCHDOG_TIMEOUT_MILLIS = 500;
+ // feature names used in ranking.
+ private static final String LAUNCH_SCORE = "launch";
+ private static final String TIME_SPENT_SCORE = "timeSpent";
+ private static final String RECENCY_SCORE = "recency";
+ private static final String CHOOSER_SCORE = "chooser";
private final Collator mCollator;
private final boolean mHttp;
@@ -92,74 +74,18 @@
private final Map<String, UsageStats> mStats;
private final long mCurrentTime;
private final long mSinceTime;
- private final LinkedHashMap<ComponentName, ResolverTarget> mTargetsDict = new LinkedHashMap<>();
+ private final LinkedHashMap<ComponentName, ScoredTarget> mScoredTargets = new LinkedHashMap<>();
private final String mReferrerPackage;
- private final Object mLock = new Object();
- private ArrayList<ResolverTarget> mTargets;
private String mContentType;
private String[] mAnnotations;
private String mAction;
- private IResolverRankerService mRanker;
- private ResolverRankerServiceConnection mConnection;
- private AfterCompute mAfterCompute;
- private Context mContext;
- private CountDownLatch mConnectSignal;
+ private LogisticRegressionAppRanker mRanker;
- private final Handler mHandler = new Handler(Looper.getMainLooper()) {
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case RESOLVER_RANKER_SERVICE_RESULT:
- if (DEBUG) {
- Log.d(TAG, "RESOLVER_RANKER_SERVICE_RESULT");
- }
- if (mHandler.hasMessages(RESOLVER_RANKER_RESULT_TIMEOUT)) {
- if (msg.obj != null) {
- final List<ResolverTarget> receivedTargets =
- (List<ResolverTarget>) msg.obj;
- if (receivedTargets != null && mTargets != null
- && receivedTargets.size() == mTargets.size()) {
- final int size = mTargets.size();
- for (int i = 0; i < size; ++i) {
- mTargets.get(i).setSelectProbability(
- receivedTargets.get(i).getSelectProbability());
- }
- } else {
- Log.e(TAG, "Sizes of sent and received ResolverTargets diff.");
- }
- } else {
- Log.e(TAG, "Receiving null prediction results.");
- }
- mHandler.removeMessages(RESOLVER_RANKER_RESULT_TIMEOUT);
- mAfterCompute.afterCompute();
- }
- break;
-
- case RESOLVER_RANKER_RESULT_TIMEOUT:
- if (DEBUG) {
- Log.d(TAG, "RESOLVER_RANKER_RESULT_TIMEOUT; unbinding services");
- }
- mHandler.removeMessages(RESOLVER_RANKER_SERVICE_RESULT);
- mAfterCompute.afterCompute();
- break;
-
- default:
- super.handleMessage(msg);
- }
- }
- };
-
- public interface AfterCompute {
- public void afterCompute ();
- }
-
- public ResolverComparator(Context context, Intent intent, String referrerPackage,
- AfterCompute afterCompute) {
+ public ResolverComparator(Context context, Intent intent, String referrerPackage) {
mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
String scheme = intent.getScheme();
mHttp = "http".equals(scheme) || "https".equals(scheme);
mReferrerPackage = referrerPackage;
- mAfterCompute = afterCompute;
- mContext = context;
mPm = context.getPackageManager();
mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
@@ -170,9 +96,9 @@
mContentType = intent.getType();
getContentAnnotations(intent);
mAction = intent.getAction();
+ mRanker = new LogisticRegressionAppRanker(context);
}
- // get annotations of content from intent.
public void getContentAnnotations(Intent intent) {
ArrayList<String> annotations = intent.getStringArrayListExtra(
Intent.EXTRA_CONTENT_ANNOTATIONS);
@@ -188,24 +114,20 @@
}
}
- public void setCallBack(AfterCompute afterCompute) {
- mAfterCompute = afterCompute;
- }
-
- // compute features for each target according to usage stats of targets.
public void compute(List<ResolvedComponentInfo> targets) {
- reset();
+ mScoredTargets.clear();
final long recentSinceTime = mCurrentTime - RECENCY_TIME_PERIOD;
- float mostRecencyScore = 1.0f;
- float mostTimeSpentScore = 1.0f;
- float mostLaunchScore = 1.0f;
- float mostChooserScore = 1.0f;
+ long mostRecentlyUsedTime = recentSinceTime + 1;
+ long mostTimeSpent = 1;
+ int mostLaunched = 1;
+ int mostSelected = 1;
for (ResolvedComponentInfo target : targets) {
- final ResolverTarget resolverTarget = new ResolverTarget();
- mTargetsDict.put(target.name, resolverTarget);
+ final ScoredTarget scoredTarget
+ = new ScoredTarget(target.getResolveInfoAt(0).activityInfo);
+ mScoredTargets.put(target.name, scoredTarget);
final UsageStats pkStats = mStats.get(target.name.getPackageName());
if (pkStats != null) {
// Only count recency for apps that weren't the caller
@@ -213,33 +135,31 @@
// Persistent processes muck this up, so omit them too.
if (!target.name.getPackageName().equals(mReferrerPackage)
&& !isPersistentProcess(target)) {
- final float recencyScore =
- (float) Math.max(pkStats.getLastTimeUsed() - recentSinceTime, 0);
- resolverTarget.setRecencyScore(recencyScore);
- if (recencyScore > mostRecencyScore) {
- mostRecencyScore = recencyScore;
+ final long lastTimeUsed = pkStats.getLastTimeUsed();
+ scoredTarget.lastTimeUsed = lastTimeUsed;
+ if (lastTimeUsed > mostRecentlyUsedTime) {
+ mostRecentlyUsedTime = lastTimeUsed;
}
}
- final float timeSpentScore = (float) pkStats.getTotalTimeInForeground();
- resolverTarget.setTimeSpentScore(timeSpentScore);
- if (timeSpentScore > mostTimeSpentScore) {
- mostTimeSpentScore = timeSpentScore;
+ final long timeSpent = pkStats.getTotalTimeInForeground();
+ scoredTarget.timeSpent = timeSpent;
+ if (timeSpent > mostTimeSpent) {
+ mostTimeSpent = timeSpent;
}
- final float launchScore = (float) pkStats.mLaunchCount;
- resolverTarget.setLaunchScore(launchScore);
- if (launchScore > mostLaunchScore) {
- mostLaunchScore = launchScore;
+ final int launched = pkStats.mLaunchCount;
+ scoredTarget.launchCount = launched;
+ if (launched > mostLaunched) {
+ mostLaunched = launched;
}
- float chooserScore = 0.0f;
+ int selected = 0;
if (pkStats.mChooserCounts != null && mAction != null
&& pkStats.mChooserCounts.get(mAction) != null) {
- chooserScore = (float) pkStats.mChooserCounts.get(mAction)
- .getOrDefault(mContentType, 0);
+ selected = pkStats.mChooserCounts.get(mAction).getOrDefault(mContentType, 0);
if (mAnnotations != null) {
final int size = mAnnotations.length;
for (int i = 0; i < size; i++) {
- chooserScore += (float) pkStats.mChooserCounts.get(mAction)
+ selected += pkStats.mChooserCounts.get(mAction)
.getOrDefault(mAnnotations[i], 0);
}
}
@@ -249,37 +169,44 @@
Log.d(TAG, "Action type is null");
} else {
Log.d(TAG, "Chooser Count of " + mAction + ":" +
- target.name.getPackageName() + " is " +
- Float.toString(chooserScore));
+ target.name.getPackageName() + " is " + Integer.toString(selected));
}
}
- resolverTarget.setChooserScore(chooserScore);
- if (chooserScore > mostChooserScore) {
- mostChooserScore = chooserScore;
+ scoredTarget.chooserCount = selected;
+ if (selected > mostSelected) {
+ mostSelected = selected;
}
}
}
+
if (DEBUG) {
- Log.d(TAG, "compute - mostRecencyScore: " + mostRecencyScore
- + " mostTimeSpentScore: " + mostTimeSpentScore
- + " mostLaunchScore: " + mostLaunchScore
- + " mostChooserScore: " + mostChooserScore);
+ Log.d(TAG, "compute - mostRecentlyUsedTime: " + mostRecentlyUsedTime
+ + " mostTimeSpent: " + mostTimeSpent
+ + " recentSinceTime: " + recentSinceTime
+ + " mostLaunched: " + mostLaunched);
}
- mTargets = new ArrayList<>(mTargetsDict.values());
- for (ResolverTarget target : mTargets) {
- final float recency = target.getRecencyScore() / mostRecencyScore;
- setFeatures(target, recency * recency * RECENCY_MULTIPLIER,
- target.getLaunchScore() / mostLaunchScore,
- target.getTimeSpentScore() / mostTimeSpentScore,
- target.getChooserScore() / mostChooserScore);
- addDefaultSelectProbability(target);
+ for (ScoredTarget target : mScoredTargets.values()) {
+ final float recency = (float) Math.max(target.lastTimeUsed - recentSinceTime, 0)
+ / (mostRecentlyUsedTime - recentSinceTime);
+ target.setFeatures((float) target.launchCount / mostLaunched,
+ (float) target.timeSpent / mostTimeSpent,
+ recency * recency * RECENCY_MULTIPLIER,
+ (float) target.chooserCount / mostSelected);
+ target.selectProb = mRanker.predict(target.getFeatures());
if (DEBUG) {
Log.d(TAG, "Scores: " + target);
}
}
- predictSelectProbabilities(mTargets);
+ }
+
+ static boolean isPersistentProcess(ResolvedComponentInfo rci) {
+ if (rci != null && rci.getCount() > 0) {
+ return (rci.getResolveInfoAt(0).activityInfo.applicationInfo.flags &
+ ApplicationInfo.FLAG_PERSISTENT) != 0;
+ }
+ return false;
}
@Override
@@ -318,16 +245,16 @@
// Pinned items stay stable within a normal lexical sort and ignore scoring.
if (!lPinned && !rPinned) {
if (mStats != null) {
- final ResolverTarget lhsTarget = mTargetsDict.get(new ComponentName(
+ final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName(
lhs.activityInfo.packageName, lhs.activityInfo.name));
- final ResolverTarget rhsTarget = mTargetsDict.get(new ComponentName(
+ final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName(
rhs.activityInfo.packageName, rhs.activityInfo.name));
- final int selectProbabilityDiff = Float.compare(
- rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
+ final int selectProbDiff = Float.compare(
+ rhsTarget.selectProb, lhsTarget.selectProb);
- if (selectProbabilityDiff != 0) {
- return selectProbabilityDiff > 0 ? 1 : -1;
+ if (selectProbDiff != 0) {
+ return selectProbDiff > 0 ? 1 : -1;
}
}
}
@@ -341,234 +268,177 @@
}
public float getScore(ComponentName name) {
- final ResolverTarget target = mTargetsDict.get(name);
+ final ScoredTarget target = mScoredTargets.get(name);
if (target != null) {
- return target.getSelectProbability();
+ return target.selectProb;
}
return 0;
}
+ static class ScoredTarget {
+ public final ComponentInfo componentInfo;
+ public long lastTimeUsed;
+ public long timeSpent;
+ public long launchCount;
+ public long chooserCount;
+ public ArrayMap<String, Float> features;
+ public float selectProb;
+
+ public ScoredTarget(ComponentInfo ci) {
+ componentInfo = ci;
+ features = new ArrayMap<>(5);
+ }
+
+ @Override
+ public String toString() {
+ return "ScoredTarget{" + componentInfo
+ + " lastTimeUsed: " + lastTimeUsed
+ + " timeSpent: " + timeSpent
+ + " launchCount: " + launchCount
+ + " chooserCount: " + chooserCount
+ + " selectProb: " + selectProb
+ + "}";
+ }
+
+ public void setFeatures(float launchCountScore, float usageTimeScore, float recencyScore,
+ float chooserCountScore) {
+ features.put(LAUNCH_SCORE, launchCountScore);
+ features.put(TIME_SPENT_SCORE, usageTimeScore);
+ features.put(RECENCY_SCORE, recencyScore);
+ features.put(CHOOSER_SCORE, chooserCountScore);
+ }
+
+ public ArrayMap<String, Float> getFeatures() {
+ return features;
+ }
+ }
+
public void updateChooserCounts(String packageName, int userId, String action) {
if (mUsm != null) {
mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action);
}
}
- // update ranking model when the connection to it is valid.
public void updateModel(ComponentName componentName) {
- synchronized (mLock) {
- if (mRanker != null) {
- try {
- int selectedPos = new ArrayList<ComponentName>(mTargetsDict.keySet())
- .indexOf(componentName);
- if (selectedPos > 0) {
- mRanker.train(mTargets, selectedPos);
- } else {
- if (DEBUG) {
- Log.d(TAG, "Selected a unknown component: " + componentName);
- }
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error in Train: " + e);
- }
- } else {
- if (DEBUG) {
- Log.d(TAG, "Ranker is null; skip updateModel.");
- }
+ if (mScoredTargets == null || componentName == null ||
+ !mScoredTargets.containsKey(componentName)) {
+ return;
+ }
+ ScoredTarget selected = mScoredTargets.get(componentName);
+ for (ComponentName targetComponent : mScoredTargets.keySet()) {
+ if (targetComponent.equals(componentName)) {
+ continue;
+ }
+ ScoredTarget target = mScoredTargets.get(targetComponent);
+ // A potential point of optimization. Save updates or derive a closed form for the
+ // positive case, to avoid calculating them repeatedly.
+ if (target.selectProb >= selected.selectProb) {
+ mRanker.update(target.getFeatures(), target.selectProb, false);
+ mRanker.update(selected.getFeatures(), selected.selectProb, true);
}
}
+ mRanker.commitUpdate();
}
- // unbind the service and clear unhandled messges.
- public void destroy() {
- mHandler.removeMessages(RESOLVER_RANKER_SERVICE_RESULT);
- mHandler.removeMessages(RESOLVER_RANKER_RESULT_TIMEOUT);
- if (mConnection != null) {
- mContext.unbindService(mConnection);
- mConnection.destroy();
- }
- if (DEBUG) {
- Log.d(TAG, "Unbinded Resolver Ranker.");
- }
- }
+ class LogisticRegressionAppRanker {
+ private static final String PARAM_SHARED_PREF_NAME = "resolver_ranker_params";
+ private static final String BIAS_PREF_KEY = "bias";
+ private static final String VERSION_PREF_KEY = "version";
- // connect to a ranking service.
- private void initRanker(Context context) {
- synchronized (mLock) {
- if (mConnection != null && mRanker != null) {
- if (DEBUG) {
- Log.d(TAG, "Ranker still exists; reusing the existing one.");
- }
+ // parameters for a pre-trained model, to initialize the app ranker. When updating the
+ // pre-trained model, please update these params, as well as initModel().
+ private static final int CURRENT_VERSION = 1;
+ private static final float LEARNING_RATE = 0.0001f;
+ private static final float REGULARIZER_PARAM = 0.0001f;
+
+ private SharedPreferences mParamSharedPref;
+ private ArrayMap<String, Float> mFeatureWeights;
+ private float mBias;
+
+ public LogisticRegressionAppRanker(Context context) {
+ mParamSharedPref = getParamSharedPref(context);
+ initModel();
+ }
+
+ public float predict(ArrayMap<String, Float> target) {
+ if (target == null) {
+ return 0.0f;
+ }
+ final int featureSize = target.size();
+ float sum = 0.0f;
+ for (int i = 0; i < featureSize; i++) {
+ String featureName = target.keyAt(i);
+ float weight = mFeatureWeights.getOrDefault(featureName, 0.0f);
+ sum += weight * target.valueAt(i);
+ }
+ return (float) (1.0 / (1.0 + Math.exp(-mBias - sum)));
+ }
+
+ public void update(ArrayMap<String, Float> target, float predict, boolean isSelected) {
+ if (target == null) {
return;
}
- }
- Intent intent = resolveRankerService();
- if (intent == null) {
- return;
- }
- mConnectSignal = new CountDownLatch(1);
- mConnection = new ResolverRankerServiceConnection(mConnectSignal);
- context.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
- }
-
- // resolve the service for ranking.
- private Intent resolveRankerService() {
- Intent intent = new Intent(ResolverRankerService.SERVICE_INTERFACE);
- final List<ResolveInfo> resolveInfos = mPm.queryIntentServices(intent, 0);
- for (ResolveInfo resolveInfo : resolveInfos) {
- if (resolveInfo == null || resolveInfo.serviceInfo == null
- || resolveInfo.serviceInfo.applicationInfo == null) {
- if (DEBUG) {
- Log.d(TAG, "Failed to retrieve a ranker: " + resolveInfo);
- }
- continue;
- }
- ComponentName componentName = new ComponentName(
- resolveInfo.serviceInfo.applicationInfo.packageName,
- resolveInfo.serviceInfo.name);
- try {
- final String perm = mPm.getServiceInfo(componentName, 0).permission;
- if (!ResolverRankerService.BIND_PERMISSION.equals(perm)) {
- Log.w(TAG, "ResolverRankerService " + componentName + " does not require"
- + " permission " + ResolverRankerService.BIND_PERMISSION
- + " - this service will not be queried for ResolverComparator."
- + " add android:permission=\""
- + ResolverRankerService.BIND_PERMISSION + "\""
- + " to the <service> tag for " + componentName
- + " in the manifest.");
- continue;
- }
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Could not look up service " + componentName
- + "; component name not found");
- continue;
+ final int featureSize = target.size();
+ float error = isSelected ? 1.0f - predict : -predict;
+ for (int i = 0; i < featureSize; i++) {
+ String featureName = target.keyAt(i);
+ float currentWeight = mFeatureWeights.getOrDefault(featureName, 0.0f);
+ mBias += LEARNING_RATE * error;
+ currentWeight = currentWeight - LEARNING_RATE * REGULARIZER_PARAM * currentWeight +
+ LEARNING_RATE * error * target.valueAt(i);
+ mFeatureWeights.put(featureName, currentWeight);
}
if (DEBUG) {
- Log.d(TAG, "Succeeded to retrieve a ranker: " + componentName);
+ Log.d(TAG, "Weights: " + mFeatureWeights + " Bias: " + mBias);
}
- intent.setComponent(componentName);
- return intent;
- }
- return null;
- }
-
- // set a watchdog, to avoid waiting for ranking service for too long.
- private void startWatchDog(int timeOutLimit) {
- if (DEBUG) Log.d(TAG, "Setting watchdog timer for " + timeOutLimit + "ms");
- if (mHandler == null) {
- Log.d(TAG, "Error: Handler is Null; Needs to be initialized.");
- }
- mHandler.sendEmptyMessageDelayed(RESOLVER_RANKER_RESULT_TIMEOUT, timeOutLimit);
- }
-
- private class ResolverRankerServiceConnection implements ServiceConnection {
- private final CountDownLatch mConnectSignal;
-
- public ResolverRankerServiceConnection(CountDownLatch connectSignal) {
- mConnectSignal = connectSignal;
}
- public final IResolverRankerResult resolverRankerResult =
- new IResolverRankerResult.Stub() {
- @Override
- public void sendResult(List<ResolverTarget> targets) throws RemoteException {
- if (DEBUG) {
- Log.d(TAG, "Sending Result back to Resolver: " + targets);
- }
- synchronized (mLock) {
- final Message msg = Message.obtain();
- msg.what = RESOLVER_RANKER_SERVICE_RESULT;
- msg.obj = targets;
- mHandler.sendMessage(msg);
- }
+ public void commitUpdate() {
+ SharedPreferences.Editor editor = mParamSharedPref.edit();
+ editor.putFloat(BIAS_PREF_KEY, mBias);
+ final int size = mFeatureWeights.size();
+ for (int i = 0; i < size; i++) {
+ editor.putFloat(mFeatureWeights.keyAt(i), mFeatureWeights.valueAt(i));
}
- };
+ editor.putInt(VERSION_PREF_KEY, CURRENT_VERSION);
+ editor.apply();
+ }
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
+ private SharedPreferences getParamSharedPref(Context context) {
+ // The package info in the context isn't initialized in the way it is for normal apps,
+ // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
+ // build the path manually below using the same policy that appears in ContextImpl.
if (DEBUG) {
- Log.d(TAG, "onServiceConnected: " + name);
+ Log.d(TAG, "Context Package Name: " + context.getPackageName());
}
- synchronized (mLock) {
- mRanker = IResolverRankerService.Stub.asInterface(service);
- mConnectSignal.countDown();
- }
+ final File prefsFile = new File(new File(
+ Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
+ context.getUserId(), context.getPackageName()),
+ "shared_prefs"),
+ PARAM_SHARED_PREF_NAME + ".xml");
+ return context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
}
- @Override
- public void onServiceDisconnected(ComponentName name) {
- if (DEBUG) {
- Log.d(TAG, "onServiceDisconnected: " + name);
- }
- synchronized (mLock) {
- destroy();
+ private void initModel() {
+ mFeatureWeights = new ArrayMap<>(4);
+ if (mParamSharedPref == null ||
+ mParamSharedPref.getInt(VERSION_PREF_KEY, 0) < CURRENT_VERSION) {
+ // Initializing the app ranker to a pre-trained model. When updating the pre-trained
+ // model, please increment CURRENT_VERSION, and update LEARNING_RATE and
+ // REGULARIZER_PARAM.
+ mBias = -1.6568f;
+ mFeatureWeights.put(LAUNCH_SCORE, 2.5543f);
+ mFeatureWeights.put(TIME_SPENT_SCORE, 2.8412f);
+ mFeatureWeights.put(RECENCY_SCORE, 0.269f);
+ mFeatureWeights.put(CHOOSER_SCORE, 4.2222f);
+ } else {
+ mBias = mParamSharedPref.getFloat(BIAS_PREF_KEY, 0.0f);
+ mFeatureWeights.put(LAUNCH_SCORE, mParamSharedPref.getFloat(LAUNCH_SCORE, 0.0f));
+ mFeatureWeights.put(
+ TIME_SPENT_SCORE, mParamSharedPref.getFloat(TIME_SPENT_SCORE, 0.0f));
+ mFeatureWeights.put(RECENCY_SCORE, mParamSharedPref.getFloat(RECENCY_SCORE, 0.0f));
+ mFeatureWeights.put(CHOOSER_SCORE, mParamSharedPref.getFloat(CHOOSER_SCORE, 0.0f));
}
}
-
- public void destroy() {
- synchronized (mLock) {
- mRanker = null;
- }
- }
- }
-
- private void reset() {
- mTargetsDict.clear();
- mTargets = null;
- startWatchDog(WATCHDOG_TIMEOUT_MILLIS);
- initRanker(mContext);
- }
-
- // predict select probabilities if ranking service is valid.
- private void predictSelectProbabilities(List<ResolverTarget> targets) {
- if (mConnection == null) {
- if (DEBUG) {
- Log.d(TAG, "Has not found valid ResolverRankerService; Skip Prediction");
- }
- return;
- } else {
- try {
- mConnectSignal.await(CONNECTION_COST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
- synchronized (mLock) {
- if (mRanker != null) {
- mRanker.predict(targets, mConnection.resolverRankerResult);
- return;
- } else {
- if (DEBUG) {
- Log.d(TAG, "Ranker has not been initialized; skip predict.");
- }
- }
- }
- } catch (InterruptedException e) {
- Log.e(TAG, "Error in Wait for Service Connection.");
- } catch (RemoteException e) {
- Log.e(TAG, "Error in Predict: " + e);
- }
- }
- mAfterCompute.afterCompute();
- }
-
- // adds select prob as the default values, according to a pre-trained Logistic Regression model.
- private void addDefaultSelectProbability(ResolverTarget target) {
- float sum = 2.5543f * target.getLaunchScore() + 2.8412f * target.getTimeSpentScore() +
- 0.269f * target.getRecencyScore() + 4.2222f * target.getChooserScore();
- target.setSelectProbability((float) (1.0 / (1.0 + Math.exp(1.6568f - sum))));
- }
-
- // sets features for each target
- private void setFeatures(ResolverTarget target, float recencyScore, float launchScore,
- float timeSpentScore, float chooserScore) {
- target.setRecencyScore(recencyScore);
- target.setLaunchScore(launchScore);
- target.setTimeSpentScore(timeSpentScore);
- target.setChooserScore(chooserScore);
- }
-
- static boolean isPersistentProcess(ResolvedComponentInfo rci) {
- if (rci != null && rci.getCount() > 0) {
- return (rci.getResolveInfoAt(0).activityInfo.applicationInfo.flags &
- ApplicationInfo.FLAG_PERSISTENT) != 0;
- }
- return false;
}
}
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index e8bebb7..4071ff4 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -32,10 +32,8 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import java.lang.InterruptedException;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.concurrent.CountDownLatch;
import java.util.List;
/**
@@ -207,42 +205,14 @@
return listToReturn;
}
- private class ComputeCallback implements ResolverComparator.AfterCompute {
-
- private CountDownLatch mFinishComputeSignal;
-
- public ComputeCallback(CountDownLatch finishComputeSignal) {
- mFinishComputeSignal = finishComputeSignal;
- }
-
- public void afterCompute () {
- mFinishComputeSignal.countDown();
- }
- }
-
@VisibleForTesting
@WorkerThread
public void sort(List<ResolverActivity.ResolvedComponentInfo> inputList) {
- final CountDownLatch finishComputeSignal = new CountDownLatch(1);
- ComputeCallback callback = new ComputeCallback(finishComputeSignal);
if (mResolverComparator == null) {
- mResolverComparator =
- new ResolverComparator(mContext, mTargetIntent, mReferrerPackage, callback);
- } else {
- mResolverComparator.setCallBack(callback);
+ mResolverComparator = new ResolverComparator(mContext, mTargetIntent, mReferrerPackage);
}
- try {
- long beforeRank = System.currentTimeMillis();
- mResolverComparator.compute(inputList);
- finishComputeSignal.await();
- Collections.sort(inputList, mResolverComparator);
- long afterRank = System.currentTimeMillis();
- if (DEBUG) {
- Log.d(TAG, "Time Cost: " + Long.toString(afterRank - beforeRank));
- }
- } catch (InterruptedException e) {
- Log.e(TAG, "Compute & Sort was interrupted: " + e);
- }
+ mResolverComparator.compute(inputList);
+ Collections.sort(inputList, mResolverComparator);
}
private static boolean isSameResolvedComponent(ResolveInfo a,
@@ -263,7 +233,7 @@
@VisibleForTesting
public float getScore(ResolverActivity.DisplayResolveInfo target) {
if (mResolverComparator == null) {
- return 0.0f;
+ mResolverComparator = new ResolverComparator(mContext, mTargetIntent, mReferrerPackage);
}
return mResolverComparator.getScore(target.getResolvedComponentName());
}
@@ -279,10 +249,4 @@
mResolverComparator.updateChooserCounts(packageName, userId, action);
}
}
-
- public void destroy() {
- if (mResolverComparator != null) {
- mResolverComparator.destroy();
- }
- }
}
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index f987a9f..caf35b3 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -55,8 +55,8 @@
in RemoteViews views);
void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views);
void notifyAppWidgetViewDataChanged(String packageName, in int[] appWidgetIds, int viewId);
- ParceledListSlice getInstalledProvidersForProfile(int categoryFilter,
- int profileId);
+ ParceledListSlice getInstalledProvidersForProfile(int categoryFilter, int profileId,
+ String packageName);
AppWidgetProviderInfo getAppWidgetInfo(String callingPackage, int appWidgetId);
boolean hasBindAppWidgetPermission(in String packageName, int userId);
void setBindAppWidgetPermission(in String packageName, int userId, in boolean permission);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index b263657..d19ffad 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -114,7 +114,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 152 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 153 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -2006,107 +2006,92 @@
* State for keeping track of two DurationTimers with different TimeBases, presumably where one
* TimeBase is effectively a subset of the other.
*/
- public static class DualTimer {
- // mMainTimer typically tracks the total time. May be pooled (but since it's a durationTimer,
- // it also has the unpooled getTotalDurationMsLocked() for STATS_SINCE_CHARGED).
- private final DurationTimer mMainTimer;
+ public static class DualTimer extends DurationTimer {
+ // This class both is a DurationTimer and also holds a second DurationTimer.
+ // The main timer (this) typically tracks the total time. It may be pooled (but since it's a
+ // durationTimer, it also has the unpooled getTotalDurationMsLocked() for
+ // STATS_SINCE_CHARGED).
// mSubTimer typically tracks only part of the total time, such as background time, as
// determined by a subTimeBase. It is NOT pooled.
private final DurationTimer mSubTimer;
/**
- * Creates a DualTimer to hold a mMainTimer and a mSubTimer.
- * The mMainTimer is based on the given timeBase and timerPool.
+ * Creates a DualTimer to hold a main timer (this) and a mSubTimer.
+ * The main timer (this) is based on the given timeBase and timerPool.
* The mSubTimer is based on the given subTimeBase. The mSubTimer is not pooled, even if
- * the mMainTimer is.
+ * the main timer is.
*/
public DualTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
TimeBase timeBase, TimeBase subTimeBase, Parcel in) {
- mMainTimer = new DurationTimer(clocks, uid, type, timerPool, timeBase, in);
+ super(clocks, uid, type, timerPool, timeBase, in);
mSubTimer = new DurationTimer(clocks, uid, type, null, subTimeBase, in);
}
/**
- * Creates a DualTimer to hold a mMainTimer and a mSubTimer.
- * The mMainTimer is based on the given timeBase and timerPool.
+ * Creates a DualTimer to hold a main timer (this) and a mSubTimer.
+ * The main timer (this) is based on the given timeBase and timerPool.
* The mSubTimer is based on the given subTimeBase. The mSubTimer is not pooled, even if
- * the mMainTimer is.
+ * the main timer is.
*/
public DualTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
TimeBase timeBase, TimeBase subTimeBase) {
- mMainTimer = new DurationTimer(clocks, uid, type, timerPool, timeBase);
+ super(clocks, uid, type, timerPool, timeBase);
mSubTimer = new DurationTimer(clocks, uid, type, null, subTimeBase);
}
- /** Get the main timer. */
- public DurationTimer getMainTimer() {
- return mMainTimer;
- }
-
/** Get the secondary timer. */
+ @Override
public DurationTimer getSubTimer() {
return mSubTimer;
}
+ @Override
public void startRunningLocked(long elapsedRealtimeMs) {
- mMainTimer.startRunningLocked(elapsedRealtimeMs);
+ super.startRunningLocked(elapsedRealtimeMs);
mSubTimer.startRunningLocked(elapsedRealtimeMs);
}
+ @Override
public void stopRunningLocked(long elapsedRealtimeMs) {
- mMainTimer.stopRunningLocked(elapsedRealtimeMs);
+ super.stopRunningLocked(elapsedRealtimeMs);
mSubTimer.stopRunningLocked(elapsedRealtimeMs);
}
+ @Override
public void stopAllRunningLocked(long elapsedRealtimeMs) {
- mMainTimer.stopAllRunningLocked(elapsedRealtimeMs);
+ super.stopAllRunningLocked(elapsedRealtimeMs);
mSubTimer.stopAllRunningLocked(elapsedRealtimeMs);
}
- public void setMark(long elapsedRealtimeMs) {
- mMainTimer.setMark(elapsedRealtimeMs);
- mSubTimer.setMark(elapsedRealtimeMs);
- }
-
+ @Override
public boolean reset(boolean detachIfReset) {
boolean active = false;
- active |= !mMainTimer.reset(detachIfReset);
+ active |= !super.reset(detachIfReset);
active |= !mSubTimer.reset(detachIfReset);
return !active;
}
+ @Override
public void detach() {
- mMainTimer.detach();
+ super.detach();
mSubTimer.detach();
}
- /**
- * Writes a possibly null DualTimer to a Parcel.
- *
- * @param out the Parcel to which to write.
- * @param t a DualTimer, or null.
- */
- public static void writeDualTimerToParcel(Parcel out, DualTimer t, long elapsedRealtimeUs) {
- if (t != null) {
- out.writeInt(1);
- t.writeToParcel(out, elapsedRealtimeUs);
- } else {
- out.writeInt(0);
- }
- }
-
+ @Override
public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
- mMainTimer.writeToParcel(out, elapsedRealtimeUs);
+ super.writeToParcel(out, elapsedRealtimeUs);
mSubTimer.writeToParcel(out, elapsedRealtimeUs);
}
+ @Override
public void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) {
- mMainTimer.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
+ super.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
mSubTimer.writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
}
+ @Override
public void readSummaryFromParcelLocked(Parcel in) {
- mMainTimer.readSummaryFromParcelLocked(in);
+ super.readSummaryFromParcelLocked(in);
mSubTimer.readSummaryFromParcelLocked(in);
}
}
@@ -5488,7 +5473,7 @@
/**
* The statistics we have collected for this uid's jobs.
*/
- final OverflowArrayMap<StopwatchTimer> mJobStats;
+ final OverflowArrayMap<DualTimer> mJobStats;
/**
* The statistics we have collected for this uid's sensor activations.
@@ -5533,10 +5518,10 @@
mBsi.mOnBatteryTimeBase);
}
};
- mJobStats = mBsi.new OverflowArrayMap<StopwatchTimer>(uid) {
- @Override public StopwatchTimer instantiateObject() {
- return new StopwatchTimer(mBsi.mClocks, Uid.this, JOB, null,
- mBsi.mOnBatteryTimeBase);
+ mJobStats = mBsi.new OverflowArrayMap<DualTimer>(uid) {
+ @Override public DualTimer instantiateObject() {
+ return new DualTimer(mBsi.mClocks, Uid.this, JOB, null,
+ mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase);
}
};
@@ -5918,7 +5903,7 @@
if (mWifiScanTimer == null) {
return 0;
}
- return mWifiScanTimer.getMainTimer().getTotalTimeLocked(elapsedRealtimeUs, which);
+ return mWifiScanTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
@@ -5926,12 +5911,12 @@
if (mWifiScanTimer == null) {
return 0;
}
- return mWifiScanTimer.getMainTimer().getCountLocked(which);
+ return mWifiScanTimer.getCountLocked(which);
}
@Override
public int getWifiScanBackgroundCount(int which) {
- if (mWifiScanTimer == null) {
+ if (mWifiScanTimer == null || mWifiScanTimer.getSubTimer() == null) {
return 0;
}
return mWifiScanTimer.getSubTimer().getCountLocked(which);
@@ -5943,12 +5928,12 @@
return 0;
}
final long elapsedRealtimeMs = (elapsedRealtimeUs + 500) / 1000;
- return mWifiScanTimer.getMainTimer().getTotalDurationMsLocked(elapsedRealtimeMs) * 1000;
+ return mWifiScanTimer.getTotalDurationMsLocked(elapsedRealtimeMs) * 1000;
}
@Override
public long getWifiScanBackgroundTime(final long elapsedRealtimeUs) {
- if (mWifiScanTimer == null) {
+ if (mWifiScanTimer == null || mWifiScanTimer.getSubTimer() == null) {
return 0;
}
final long elapsedRealtimeMs = (elapsedRealtimeUs + 500) / 1000;
@@ -6008,10 +5993,7 @@
@Override
public Timer getBluetoothScanTimer() {
- if (mBluetoothScanTimer == null) {
- return null;
- }
- return mBluetoothScanTimer.getMainTimer();
+ return mBluetoothScanTimer;
}
@Override
@@ -6361,9 +6343,9 @@
}
}
mSyncStats.cleanup();
- final ArrayMap<String, StopwatchTimer> jobStats = mJobStats.getMap();
+ final ArrayMap<String, DualTimer> jobStats = mJobStats.getMap();
for (int ij=jobStats.size()-1; ij>=0; ij--) {
- StopwatchTimer timer = jobStats.valueAt(ij);
+ DualTimer timer = jobStats.valueAt(ij);
if (timer.reset(false)) {
jobStats.removeAt(ij);
timer.detach();
@@ -6531,12 +6513,12 @@
Timer.writeTimerToParcel(out, timer, elapsedRealtimeUs);
}
- final ArrayMap<String, StopwatchTimer> jobStats = mJobStats.getMap();
+ final ArrayMap<String, DualTimer> jobStats = mJobStats.getMap();
int NJ = jobStats.size();
out.writeInt(NJ);
for (int ij=0; ij<NJ; ij++) {
out.writeString(jobStats.keyAt(ij));
- StopwatchTimer timer = jobStats.valueAt(ij);
+ DualTimer timer = jobStats.valueAt(ij);
Timer.writeTimerToParcel(out, timer, elapsedRealtimeUs);
}
@@ -6756,8 +6738,8 @@
for (int j = 0; j < numJobs; j++) {
String jobName = in.readString();
if (in.readInt() != 0) {
- mJobStats.add(jobName, new StopwatchTimer(mBsi.mClocks, Uid.this, JOB, null,
- timeBase, in));
+ mJobStats.add(jobName, new DualTimer(mBsi.mClocks, Uid.this, JOB, null,
+ mBsi.mOnBatteryTimeBase, mOnBatteryBackgroundTimeBase, in));
}
}
@@ -7196,15 +7178,12 @@
}
void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
- DualTimer.writeDualTimerToParcel(out, mTimer, elapsedRealtimeUs);
+ Timer.writeTimerToParcel(out, mTimer, elapsedRealtimeUs);
}
@Override
public Timer getSensorTime() {
- if (mTimer == null) {
- return null;
- }
- return mTimer.getMainTimer();
+ return mTimer;
}
@Override
@@ -8023,7 +8002,7 @@
}
public void readJobSummaryFromParcelLocked(String name, Parcel in) {
- StopwatchTimer timer = mJobStats.instantiateObject();
+ DualTimer timer = mJobStats.instantiateObject();
timer.readSummaryFromParcelLocked(in);
mJobStats.add(name, timer);
}
@@ -8084,14 +8063,14 @@
}
public void noteStartJobLocked(String name, long elapsedRealtimeMs) {
- StopwatchTimer t = mJobStats.startObject(name);
+ DualTimer t = mJobStats.startObject(name);
if (t != null) {
t.startRunningLocked(elapsedRealtimeMs);
}
}
public void noteStopJobLocked(String name, long elapsedRealtimeMs) {
- StopwatchTimer t = mJobStats.stopObject(name);
+ DualTimer t = mJobStats.stopObject(name);
if (t != null) {
t.stopRunningLocked(elapsedRealtimeMs);
}
@@ -9149,7 +9128,7 @@
final Uid uid = mUidStats.valueAt(i);
// Sum the total scan power for all apps.
- totalScanTimeMs += uid.mWifiScanTimer.getMainTimer().getTimeSinceMarkLocked(
+ totalScanTimeMs += uid.mWifiScanTimer.getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
// Sum the total time holding wifi lock for all apps.
@@ -9170,7 +9149,7 @@
for (int i = 0; i < uidStatsSize; i++) {
final Uid uid = mUidStats.valueAt(i);
- long scanTimeSinceMarkMs = uid.mWifiScanTimer.getMainTimer().getTimeSinceMarkLocked(
+ long scanTimeSinceMarkMs = uid.mWifiScanTimer.getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
if (scanTimeSinceMarkMs > 0) {
// Set the new mark so that next time we get new data since this point.
@@ -9444,7 +9423,7 @@
continue;
}
- totalScanTimeMs += u.mBluetoothScanTimer.getMainTimer().getTimeSinceMarkLocked(
+ totalScanTimeMs += u.mBluetoothScanTimer.getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
}
@@ -9465,7 +9444,7 @@
continue;
}
- long scanTimeSinceMarkMs = u.mBluetoothScanTimer.getMainTimer().getTimeSinceMarkLocked(
+ long scanTimeSinceMarkMs = u.mBluetoothScanTimer.getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
if (scanTimeSinceMarkMs > 0) {
// Set the new mark so that next time we get new data since this point.
@@ -11526,7 +11505,7 @@
syncStats.valueAt(is).writeSummaryFromParcelLocked(out, NOWREAL_SYS);
}
- final ArrayMap<String, StopwatchTimer> jobStats = u.mJobStats.getMap();
+ final ArrayMap<String, DualTimer> jobStats = u.mJobStats.getMap();
int NJ = jobStats.size();
out.writeInt(NJ);
for (int ij=0; ij<NJ; ij++) {
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index f4dd5a6..2c8e4e0 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -236,6 +236,29 @@
return false;
}
+ public static boolean contains(@Nullable char[] array, char value) {
+ if (array == null) return false;
+ for (char element : array) {
+ if (element == value) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Test if all {@code check} items are contained in {@code array}.
+ */
+ public static <T> boolean containsAll(@Nullable char[] array, char[] check) {
+ if (check == null) return true;
+ for (char checkItem : check) {
+ if (!contains(array, checkItem)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public static long total(@Nullable long[] array) {
long total = 0;
if (array != null) {
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index da5d04d..33fabfc 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -208,6 +208,7 @@
$(TOP)/system/core/include \
$(TOP)/system/core/libappfuse/include \
$(TOP)/system/media/camera/include \
+ $(TOP)/system/media/private/camera/include \
$(TOP)/system/netd/include \
external/giflib \
external/pdfium/public \
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index 4e68602..3010dc1 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -245,8 +245,7 @@
}
sk_sp<SkData> data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset, asset));
- addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic);
- return true;
+ return addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic);
}
static void FontFamily_addAxisValue(jlong builderPtr, jint tag, jfloat value) {
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index 1a35330..214d97c 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -68,11 +68,18 @@
}
sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
- sk_sp<SkShader> shader = image->makeShader(
- (SkShader::TileMode)tileModeX, (SkShader::TileMode)tileModeY, matrix);
+ sk_sp<SkShader> baseShader = image->makeShader(
+ (SkShader::TileMode)tileModeX, (SkShader::TileMode)tileModeY);
- ThrowIAE_IfNull(env, shader.get());
- return reinterpret_cast<jlong>(shader.release());
+ SkShader* shader;
+ if (matrix) {
+ shader = baseShader->makeWithLocalMatrix(*matrix).release();
+ } else {
+ shader = baseShader.release();
+ }
+
+ ThrowIAE_IfNull(env, shader);
+ return reinterpret_cast<jlong>(shader);
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -95,9 +102,16 @@
#error Need to convert float array to SkScalar array before calling the following function.
#endif
- SkShader* shader = SkGradientShader::MakeLinear(pts,
+ sk_sp<SkShader> baseShader(SkGradientShader::MakeLinear(pts,
reinterpret_cast<const SkColor*>(colorValues), pos, count,
- static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, matrix).release();
+ static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, NULL));
+
+ SkShader* shader;
+ if (matrix) {
+ shader = baseShader->makeWithLocalMatrix(*matrix).release();
+ } else {
+ shader = baseShader.release();
+ }
env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT);
ThrowIAE_IfNull(env, shader);
@@ -116,8 +130,15 @@
colors[0] = color0;
colors[1] = color1;
- SkShader* s = SkGradientShader::MakeLinear(pts, colors, NULL, 2,
- static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, matrix).release();
+ sk_sp<SkShader> baseShader(SkGradientShader::MakeLinear(pts, colors, NULL, 2,
+ static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, NULL));
+
+ SkShader* s;
+ if (matrix) {
+ s = baseShader->makeWithLocalMatrix(*matrix).release();
+ } else {
+ s = baseShader.release();
+ }
ThrowIAE_IfNull(env, s);
return reinterpret_cast<jlong>(s);
@@ -141,9 +162,17 @@
#error Need to convert float array to SkScalar array before calling the following function.
#endif
- SkShader* shader = SkGradientShader::MakeRadial(center, radius,
+ sk_sp<SkShader> baseShader = SkGradientShader::MakeRadial(center, radius,
reinterpret_cast<const SkColor*>(colorValues), pos, count,
- static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, matrix).release();
+ static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, NULL);
+
+ SkShader* shader;
+ if (matrix) {
+ shader = baseShader->makeWithLocalMatrix(*matrix).release();
+ } else {
+ shader = baseShader.release();
+ }
+
env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues),
JNI_ABORT);
@@ -161,10 +190,17 @@
colors[0] = color0;
colors[1] = color1;
- SkShader* s = SkGradientShader::MakeRadial(center, radius, colors, NULL, 2,
- static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, matrix).release();
- ThrowIAE_IfNull(env, s);
- return reinterpret_cast<jlong>(s);
+ sk_sp<SkShader> baseShader = SkGradientShader::MakeRadial(center, radius, colors, NULL, 2,
+ static_cast<SkShader::TileMode>(tileMode), sGradientShaderFlags, NULL);
+
+ SkShader* shader;
+ if (matrix) {
+ shader = baseShader->makeWithLocalMatrix(*matrix).release();
+ } else {
+ shader = baseShader.release();
+ }
+ ThrowIAE_IfNull(env, shader);
+ return reinterpret_cast<jlong>(shader);
}
///////////////////////////////////////////////////////////////////////////////
@@ -182,8 +218,17 @@
#error Need to convert float array to SkScalar array before calling the following function.
#endif
- SkShader* shader = SkGradientShader::MakeSweep(x, y, reinterpret_cast<const SkColor*>(colors),
- pos, count, sGradientShaderFlags, matrix).release();
+ sk_sp<SkShader> baseShader = SkGradientShader::MakeSweep(x, y,
+ reinterpret_cast<const SkColor*>(colors), pos, count,
+ sGradientShaderFlags, NULL);
+
+ SkShader* shader;
+ if (matrix) {
+ shader = baseShader->makeWithLocalMatrix(*matrix).release();
+ } else {
+ shader = baseShader.release();
+ }
+
env->ReleaseIntArrayElements(jcolors, const_cast<jint*>(colors),
JNI_ABORT);
ThrowIAE_IfNull(env, shader);
@@ -196,10 +241,18 @@
SkColor colors[2];
colors[0] = color0;
colors[1] = color1;
- SkShader* s = SkGradientShader::MakeSweep(x, y, colors, NULL, 2,
- sGradientShaderFlags, matrix).release();
- ThrowIAE_IfNull(env, s);
- return reinterpret_cast<jlong>(s);
+
+ sk_sp<SkShader> baseShader = SkGradientShader::MakeSweep(x, y, colors,
+ NULL, 2, sGradientShaderFlags, NULL);
+
+ SkShader* shader;
+ if (matrix) {
+ shader = baseShader->makeWithLocalMatrix(*matrix).release();
+ } else {
+ shader = baseShader.release();
+ }
+ ThrowIAE_IfNull(env, shader);
+ return reinterpret_cast<jlong>(shader);
}
///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index dae4310..520302e 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -282,6 +282,25 @@
return mgr->configureDirectChannel(channelHandle, sensorHandle, rate);
}
+static jint nativeSetOperationParameter(JNIEnv *_env, jclass _this, jlong sensorManager,
+ jint type, jfloatArray floats, jintArray ints) {
+ SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
+ Vector<float> floatVector;
+ Vector<int32_t> int32Vector;
+
+ if (floats != nullptr) {
+ floatVector.resize(_env->GetArrayLength(floats));
+ _env->GetFloatArrayRegion(floats, 0, _env->GetArrayLength(floats), floatVector.editArray());
+ }
+
+ if (ints != nullptr) {
+ int32Vector.resize(_env->GetArrayLength(ints));
+ _env->GetIntArrayRegion(ints, 0, _env->GetArrayLength(ints), int32Vector.editArray());
+ }
+
+ return mgr->setOperationParameter(type, floatVector, int32Vector);
+}
+
//----------------------------------------------------------------------------
class Receiver : public LooperCallback {
@@ -499,6 +518,10 @@
{"nativeConfigDirectChannel",
"(JIII)I",
(void*)nativeConfigDirectChannel },
+
+ {"nativeSetOperationParameter",
+ "(JI[F[I)I",
+ (void*)nativeSetOperationParameter },
};
static const JNINativeMethod gBaseEventQueueMethods[] = {
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index 78a5735..c11ce0f 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -36,6 +36,7 @@
#include <android/hardware/ICameraService.h>
#include <binder/IServiceManager.h>
#include <camera/CameraMetadata.h>
+#include <camera_metadata_hidden.h>
#include <camera/VendorTagDescriptor.h>
#include <nativehelper/ScopedUtfChars.h>
#include <nativehelper/ScopedPrimitiveArray.h>
@@ -162,8 +163,10 @@
extern "C" {
static jobject CameraMetadata_getAllVendorKeys(JNIEnv* env, jobject thiz, jclass keyType);
-static jint CameraMetadata_getTagFromKey(JNIEnv *env, jobject thiz, jstring keyName);
-static jint CameraMetadata_getTypeFromTag(JNIEnv *env, jobject thiz, jint tag);
+static jint CameraMetadata_getTagFromKey(JNIEnv *env, jobject thiz, jstring keyName, jlong vendorId);
+static jint CameraMetadata_getTagFromKeyLocal(JNIEnv *env, jobject thiz, jstring keyName);
+static jint CameraMetadata_getTypeFromTag(JNIEnv *env, jobject thiz, jint tag, jlong vendorId);
+static jint CameraMetadata_getTypeFromTagLocal(JNIEnv *env, jobject thiz, jint tag);
static jint CameraMetadata_setupGlobalVendorTagDescriptor(JNIEnv *env, jobject thiz);
// Less safe access to native pointer. Does NOT throw any Java exceptions if NULL.
@@ -286,7 +289,9 @@
CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, thiz);
if (metadata == NULL) return NULL;
- int tagType = get_camera_metadata_tag_type(tag);
+ const camera_metadata_t *metaBuffer = metadata->getAndLock();
+ int tagType = get_local_camera_metadata_tag_type(tag, metaBuffer);
+ metadata->unlock(metaBuffer);
if (tagType == -1) {
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
"Tag (%d) did not have a type", tag);
@@ -323,7 +328,9 @@
CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, thiz);
if (metadata == NULL) return;
- int tagType = get_camera_metadata_tag_type(tag);
+ const camera_metadata_t *metaBuffer = metadata->getAndLock();
+ int tagType = get_local_camera_metadata_tag_type(tag, metaBuffer);
+ metadata->unlock(metaBuffer);
if (tagType == -1) {
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
"Tag (%d) did not have a type", tag);
@@ -528,14 +535,11 @@
static const JNINativeMethod gCameraMetadataMethods[] = {
// static methods
- { "nativeGetAllVendorKeys",
- "(Ljava/lang/Class;)Ljava/util/ArrayList;",
- (void *)CameraMetadata_getAllVendorKeys},
{ "nativeGetTagFromKey",
- "(Ljava/lang/String;)I",
+ "(Ljava/lang/String;J)I",
(void *)CameraMetadata_getTagFromKey },
{ "nativeGetTypeFromTag",
- "(I)I",
+ "(IJ)I",
(void *)CameraMetadata_getTypeFromTag },
{ "nativeSetupGlobalVendorTagDescriptor",
"()I",
@@ -559,6 +563,12 @@
{ "nativeSwap",
"(L" CAMERA_METADATA_CLASS_NAME ";)V",
(void *)CameraMetadata_swap },
+ { "nativeGetTagFromKeyLocal",
+ "(Ljava/lang/String;)I",
+ (void *)CameraMetadata_getTagFromKeyLocal },
+ { "nativeGetTypeFromTagLocal",
+ "(I)I",
+ (void *)CameraMetadata_getTypeFromTagLocal },
{ "nativeReadValues",
"(I)[B",
(void *)CameraMetadata_readValues },
@@ -568,6 +578,9 @@
{ "nativeDump",
"()V",
(void *)CameraMetadata_dump },
+ { "nativeGetAllVendorKeys",
+ "(Ljava/lang/Class;)Ljava/util/ArrayList;",
+ (void *)CameraMetadata_getAllVendorKeys},
// Parcelable interface
{ "nativeReadFromParcel",
"(Landroid/os/Parcel;)V",
@@ -590,11 +603,11 @@
gMetadataOffsets.mResultKey = MakeGlobalRefOrDie(env, resultKeyClazz);
gMetadataOffsets.mCharacteristicsConstr = GetMethodIDOrDie(env,
gMetadataOffsets.mCharacteristicsKey, "<init>",
- "(Ljava/lang/String;Ljava/lang/Class;)V");
+ "(Ljava/lang/String;Ljava/lang/Class;J)V");
gMetadataOffsets.mRequestConstr = GetMethodIDOrDie(env,
- gMetadataOffsets.mRequestKey, "<init>", "(Ljava/lang/String;Ljava/lang/Class;)V");
+ gMetadataOffsets.mRequestKey, "<init>", "(Ljava/lang/String;Ljava/lang/Class;J)V");
gMetadataOffsets.mResultConstr = GetMethodIDOrDie(env,
- gMetadataOffsets.mResultKey, "<init>", "(Ljava/lang/String;Ljava/lang/Class;)V");
+ gMetadataOffsets.mResultKey, "<init>", "(Ljava/lang/String;Ljava/lang/Class;J)V");
// Store global references for primitive array types used by Keys
jclass byteClazz = FindClassOrDie(env, "[B");
@@ -630,13 +643,76 @@
extern "C" {
-static jobject CameraMetadata_getAllVendorKeys(JNIEnv* env, jobject thiz, jclass keyType) {
+static jint CameraMetadata_getTypeFromTagLocal(JNIEnv *env, jobject thiz, jint tag) {
+ CameraMetadata* metadata = CameraMetadata_getPointerNoThrow(env, thiz);
+ metadata_vendor_id_t vendorId = CAMERA_METADATA_INVALID_VENDOR_ID;
+ if (metadata) {
+ const camera_metadata_t *metaBuffer = metadata->getAndLock();
+ vendorId = get_camera_metadata_vendor_id(metaBuffer);
+ metadata->unlock(metaBuffer);
+ }
+ int tagType = get_local_camera_metadata_tag_type_vendor_id(tag, vendorId);
+ if (tagType == -1) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "Tag (%d) did not have a type", tag);
+ return -1;
+ }
+
+ return tagType;
+}
+
+static jint CameraMetadata_getTagFromKeyLocal(JNIEnv *env, jobject thiz, jstring keyName) {
+ ScopedUtfChars keyScoped(env, keyName);
+ const char *key = keyScoped.c_str();
+ if (key == NULL) {
+ // exception thrown by ScopedUtfChars
+ return 0;
+ }
+ ALOGV("%s (key = '%s')", __FUNCTION__, key);
+
+ uint32_t tag = 0;
+ sp<VendorTagDescriptor> vTags;
+ CameraMetadata* metadata = CameraMetadata_getPointerNoThrow(env, thiz);
+ if (metadata) {
+ sp<VendorTagDescriptorCache> cache = VendorTagDescriptorCache::getGlobalVendorTagCache();
+ if (cache.get()) {
+ const camera_metadata_t *metaBuffer = metadata->getAndLock();
+ metadata_vendor_id_t vendorId = get_camera_metadata_vendor_id(metaBuffer);
+ metadata->unlock(metaBuffer);
+ cache->getVendorTagDescriptor(vendorId, &vTags);
+ }
+ }
+
+ status_t res = CameraMetadata::getTagFromName(key, vTags.get(), &tag);
+ if (res != OK) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "Could not find tag for key '%s')", key);
+ }
+ return tag;
+}
+
+static jobject CameraMetadata_getAllVendorKeys(JNIEnv* env, jobject thiz, jclass keyType) {
+ metadata_vendor_id_t vendorId = CAMERA_METADATA_INVALID_VENDOR_ID;
// Get all vendor tags
sp<VendorTagDescriptor> vTags = VendorTagDescriptor::getGlobalVendorTagDescriptor();
if (vTags.get() == nullptr) {
- // No vendor tags.
- return NULL;
+ sp<VendorTagDescriptorCache> cache = VendorTagDescriptorCache::getGlobalVendorTagCache();
+ if (cache.get() == nullptr) {
+ // No vendor tags.
+ return nullptr;
+ }
+
+ CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, thiz);
+ if (metadata == NULL) return NULL;
+
+ const camera_metadata_t *metaBuffer = metadata->getAndLock();
+ vendorId = get_camera_metadata_vendor_id(metaBuffer);
+ cache->getVendorTagDescriptor(vendorId, &vTags);
+ metadata->unlock(metaBuffer);
+ if (vTags.get() == nullptr) {
+ return nullptr;
+ }
}
int count = vTags->getTagCount();
@@ -714,7 +790,7 @@
return NULL;
}
- jobject key = env->NewObject(keyClazz, keyConstr, name, valueClazz);
+ jobject key = env->NewObject(keyClazz, keyConstr, name, valueClazz, vendorId);
if (env->ExceptionCheck()) {
return NULL;
}
@@ -731,8 +807,8 @@
return arrayList;
}
-static jint CameraMetadata_getTagFromKey(JNIEnv *env, jobject thiz, jstring keyName) {
-
+static jint CameraMetadata_getTagFromKey(JNIEnv *env, jobject thiz, jstring keyName,
+ jlong vendorId) {
ScopedUtfChars keyScoped(env, keyName);
const char *key = keyScoped.c_str();
if (key == NULL) {
@@ -744,6 +820,13 @@
uint32_t tag = 0;
sp<VendorTagDescriptor> vTags =
VendorTagDescriptor::getGlobalVendorTagDescriptor();
+ if (vTags.get() == nullptr) {
+ sp<VendorTagDescriptorCache> cache = VendorTagDescriptorCache::getGlobalVendorTagCache();
+ if (cache.get() != nullptr) {
+ cache->getVendorTagDescriptor(vendorId, &vTags);
+ }
+ }
+
status_t res = CameraMetadata::getTagFromName(key, vTags.get(), &tag);
if (res != OK) {
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
@@ -752,8 +835,8 @@
return tag;
}
-static jint CameraMetadata_getTypeFromTag(JNIEnv *env, jobject thiz, jint tag) {
- int tagType = get_camera_metadata_tag_type(tag);
+static jint CameraMetadata_getTypeFromTag(JNIEnv *env, jobject thiz, jint tag, jlong vendorId) {
+ int tagType = get_local_camera_metadata_tag_type_vendor_id(tag, vendorId);
if (tagType == -1) {
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
"Tag (%d) did not have a type", tag);
@@ -787,8 +870,24 @@
__FUNCTION__, res.toString8().string());
return res.serviceSpecificErrorCode();
}
+ if (0 < desc->getTagCount()) {
+ err = VendorTagDescriptor::setAsGlobalVendorTagDescriptor(desc);
+ } else {
+ sp<VendorTagDescriptorCache> cache = new VendorTagDescriptorCache();
+ binder::Status res = cameraService->getCameraVendorTagCache(/*out*/cache.get());
+ if (res.serviceSpecificErrorCode() == hardware::ICameraService::ERROR_DISCONNECTED) {
+ // No camera module available, not an error on devices with no cameras
+ VendorTagDescriptorCache::clearGlobalVendorTagCache();
+ return OK;
+ } else if (!res.isOk()) {
+ VendorTagDescriptorCache::clearGlobalVendorTagCache();
+ ALOGE("%s: Failed to setup vendor tag cache: %s",
+ __FUNCTION__, res.toString8().string());
+ return res.serviceSpecificErrorCode();
+ }
- err = VendorTagDescriptor::setAsGlobalVendorTagDescriptor(desc);
+ err = VendorTagDescriptorCache::setAsGlobalVendorTagCache(cache);
+ }
if (err != OK) {
return hardware::ICameraService::ERROR_INVALID_OPERATION;
diff --git a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
index 80f9d57..7121194 100644
--- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
+++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
@@ -182,7 +182,7 @@
err = native_window_dequeue_buffer_and_wait(anw.get(), &anb);
if (err != NO_ERROR) return err;
- sp<GraphicBuffer> buf(new GraphicBuffer(anb, /*keepOwnership*/false));
+ sp<GraphicBuffer> buf(GraphicBuffer::from(anb));
uint32_t grallocBufWidth = buf->getWidth();
uint32_t grallocBufHeight = buf->getHeight();
uint32_t grallocBufStride = buf->getStride();
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0a50048..313041e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -528,6 +528,7 @@
<protected-broadcast android:name="com.android.internal.autofill.action.REQUEST_AUTOFILL" />
<protected-broadcast android:name="android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED" />
<protected-broadcast android:name="com.android.server.wm.ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION" />
+ <protected-broadcast android:name="android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED" />
<protected-broadcast android:name="android.content.pm.action.SESSION_COMMITTED" />
<protected-broadcast android:name="android.os.action.USER_RESTRICTIONS_CHANGED" />
@@ -913,7 +914,7 @@
android:permissionGroup="android.permission-group.PHONE"
android:label="@string/permlab_answerPhoneCalls"
android:description="@string/permdesc_answerPhoneCalls"
- android:protectionLevel="dangerous" />
+ android:protectionLevel="dangerous|runtime" />
<!-- ====================================================================== -->
@@ -3129,15 +3130,6 @@
<permission android:name="android.permission.BIND_CHOOSER_TARGET_SERVICE"
android:protectionLevel="signature" />
- <!-- @SystemApi Must be required by services that extend
- {@link android.service.resolver.ResolverRankerService}, to ensure that only the system can
- bind to them.
- <p>Protection level: signature
- @hide
- -->
- <permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE"
- android:protectionLevel="signature" />
-
<!-- Must be required by a {@link
android.service.notification.ConditionProviderService},
to ensure that only the system can bind to it.
@@ -3649,14 +3641,6 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
- <service android:name="com.android.internal.app.LRResolverRankerService"
- android:permission="android.permission.BIND_RESOLVER_RANKER_SERVICE"
- android:exported="false"
- android:priority="-1" >
- <intent-filter>
- <action android:name="android.service.resolver.ResolverRankerService" />
- </intent-filter>
- </service>
</application>
</manifest>
diff --git a/core/res/res/drawable/autofilled_highlight.xml b/core/res/res/drawable/autofilled_highlight.xml
new file mode 100644
index 0000000..c7aacb9
--- /dev/null
+++ b/core/res/res/drawable/autofilled_highlight.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="4dp"
+ android:insetRight="4dp"
+ android:insetBottom="4dp"
+ android:insetTop="4dp">
+ <shape>
+ <solid android:color="@color/autofilled_highlight" />
+ </shape>
+</inset>
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index a165621..5a2bf4e 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -17,27 +17,24 @@
<NotificationHeaderView
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:theme="@style/Theme.Material.Notification"
android:id="@+id/notification_header"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="@dimen/notification_header_height"
android:clipChildren="false"
- android:paddingTop="@dimen/notification_header_padding_top"
- android:paddingBottom="@dimen/notification_header_padding_bottom"
- android:layout_marginBottom="5dp"
- android:paddingStart="@dimen/notification_content_margin_start"
- android:paddingEnd="16dp">
+ style="?attr/notificationHeaderStyle">
<com.android.internal.widget.CachingIconView
android:id="@+id/icon"
- android:layout_width="@dimen/notification_header_icon_size"
- android:layout_height="@dimen/notification_header_icon_size"
+ android:layout_width="?attr/notificationHeaderIconSize"
+ android:layout_height="?attr/notificationHeaderIconSize"
android:layout_marginEnd="3dp"
/>
<TextView
android:id="@+id/app_name_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
android:layout_marginStart="3dp"
android:layout_marginEnd="2dp"
android:singleLine="true"
@@ -46,7 +43,7 @@
android:id="@+id/header_text_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:text="@string/notification_header_divider_symbol"
@@ -55,7 +52,7 @@
android:id="@+id/header_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:visibility="gone"
@@ -64,7 +61,7 @@
android:id="@+id/time_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+ android:textAppearance="?attr/notificationHeaderTextAppearance"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:text="@string/notification_header_divider_symbol"
diff --git a/core/res/res/layout/notification_template_material_ambient.xml b/core/res/res/layout/notification_template_material_ambient.xml
index e2c68b5..026bc6e 100644
--- a/core/res/res/layout/notification_template_material_ambient.xml
+++ b/core/res/res/layout/notification_template_material_ambient.xml
@@ -20,8 +20,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="ambient"
+ android:paddingStart="@dimen/notification_extra_margin_ambient"
+ android:paddingEnd="@dimen/notification_extra_margin_ambient"
>
- <include layout="@layout/notification_template_header" />
+ <include layout="@layout/notification_template_header"
+ android:theme="@style/Theme.Material.Notification.Ambient" />
<LinearLayout
android:id="@+id/notification_action_list_margin_target"
@@ -52,7 +55,7 @@
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:textSize="20sp"
- android:textColor="#e6fafafa"
+ android:textColor="#ffffffff"
/>
<TextView android:id="@+id/text"
android:layout_width="match_parent"
@@ -63,7 +66,7 @@
android:gravity="top"
android:visibility="gone"
android:textSize="16sp"
- android:textColor="#ccfafafa"
+ android:textColor="#eeffffff"
android:layout_marginTop="4dp"
/>
</LinearLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index ee73b69..d26d952 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7739,13 +7739,6 @@
<attr name="settingsActivity" />
</declare-styleable>
- <!-- TODO(b/35956626): temporary until clients change to AutofillService -->
- <declare-styleable name="AutoFillService">
- <!-- Fully qualified class name of an activity that allows the user to modify
- the settings for this service. -->
- <attr name="settingsActivity" />
- </declare-styleable>
-
<!-- =============================== -->
<!-- Contacts meta-data attributes -->
<!-- =============================== -->
@@ -8635,5 +8628,12 @@
<attr name="stackFromEnd" format="boolean" />
</declare-styleable>
+ <!-- @hide -->
+ <declare-styleable name="NotificationTheme">
+ <attr name="notificationHeaderStyle" format="reference" />
+ <attr name="notificationHeaderTextAppearance" format="reference" />
+ <attr name="notificationHeaderIconSize" format="dimension" />
+ </declare-styleable>
+
<attr name="lockPatternStyle" format="reference" />
</resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index ed5a42b..3e4b66d7 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -245,6 +245,10 @@
<!-- Additional flag from base permission type: this permission can be granted to ephemeral
apps -->
<flag name="ephemeral" value="0x1000" />
+ <!-- Additional flag from base permission type: this permission can only be granted to apps
+ that target runtime permissions ({@link android.os.Build.VERSION_CODES#M} and above)
+ -->
+ <flag name="runtime" value="0x2000" />
</attr>
<!-- Flags indicating more context for a permission group. -->
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index f9fd57c..937fc6f 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -150,6 +150,7 @@
<color name="keyguard_avatar_frame_pressed_color">#ff35b5e5</color>
<color name="accessibility_focus_highlight">#bf39b500</color>
+ <color name="autofilled_highlight">#4dffeb3b</color>
<color name="system_notification_accent_color">#ff607D8B</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 85ecaff..221e308 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2822,4 +2822,11 @@
<!-- Whether the device supports quick settings and its associated APIs -->
<bool name="config_quickSettingsSupported">true</bool>
+
+ <!-- The component name, flattened to a string, for the default autofill service
+ to enabled for an user. This service must be trusted, as it can be activated
+ without explicit consent of the user. If no autofill service with the
+ specified name exists on the device, autofill will be disabled by default.
+ -->
+ <string name="config_defaultAutofillService" translatable="false"></string>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 1129647..c5316c6 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -165,6 +165,9 @@
-->
<dimen name="notification_content_plus_picture_margin_end">72dp</dimen>
+ <!-- The additional margin on the sides of the ambient view. -->
+ <dimen name="notification_extra_margin_ambient">16dp</dimen>
+
<!-- The height of the notification action list -->
<dimen name="notification_action_list_height">56dp</dimen>
@@ -188,6 +191,9 @@
<!-- size (width and height) of the icon in the notification header -->
<dimen name="notification_header_icon_size">18dp</dimen>
+ <!-- size (width and height) of the icon in the notification header -->
+ <dimen name="notification_header_icon_size_ambient">20dp</dimen>
+
<!-- Height of a small notification in the status bar -->
<dimen name="notification_min_height">92dp</dimen>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 624eb59..1d1fd5e 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2825,6 +2825,10 @@
<public name="autofill" />
</public-group>
+ <public-group type="drawable" first-id="0x010800b4">
+ <public name="autofilled_highlight" />
+ </public-group>
+
<!-- ===============================================================
DO NOT ADD UN-GROUPED ITEMS HERE
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 831cf89..459b48f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -20,8 +20,10 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Suffix added to a number to signify size in bytes. -->
<string name="byteShort">B</string>
- <!-- Suffix added to a number to signify size in kilobytes (1000 bytes). -->
- <string name="kilobyteShort">KB</string>
+ <!-- Suffix added to a number to signify size in kilobytes (1000 bytes).
+ If you retain the Latin script for the localization, please use the lowercase
+ 'k', as it signifies 1000 bytes as opposed to 1024 bytes. -->
+ <string name="kilobyteShort">kB</string>
<!-- Suffix added to a number to signify size in megabytes. -->
<string name="megabyteShort">MB</string>
<!-- Suffix added to a number to signify size in gigabytes. -->
@@ -3172,7 +3174,7 @@
<string name="alert_windows_notification_channel_name"><xliff:g id="name" example="Google Maps">%s</xliff:g> displaying over other apps</string>
<!-- Notification title when an application is displaying ui on-top of other apps
[CHAR LIMIT=30] -->
- <string name="alert_windows_notification_title"><xliff:g id="name" example="Google Maps">%s</xliff:g> is displaying over other apps.</string>
+ <string name="alert_windows_notification_title"><xliff:g id="name" example="Google Maps">%s</xliff:g> is displaying over other apps</string>
<!-- Notification body when an application is displaying ui on-top of other apps
[CHAR LIMIT=NONE] -->
<string name="alert_windows_notification_message">If you don’t want <xliff:g id="name" example="Google Maps">%s</xliff:g> to use this feature, tap to open settings and turn it off.</string>
@@ -4600,6 +4602,10 @@
<string name="autofill_save_type_address">address</string>
<!-- Label for the type of data being saved for autofill when it represents a credit card [CHAR LIMIT=NONE] -->
<string name="autofill_save_type_credit_card">credit card</string>
+ <!-- Label for the type of data being saved for autofill when it represents an username [CHAR LIMIT=NONE] -->
+ <string name="autofill_save_type_username">username</string>
+ <!-- Label for the type of data being saved for autofill when it represents an email address [CHAR LIMIT=NONE] -->
+ <string name="autofill_save_type_email_address">email address</string>
<!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for earthquake -->
<string name="etws_primary_default_message_earthquake">Stay calm and seek shelter nearby.</string>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 8f061a3..ec16611 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -488,6 +488,10 @@
<style name="TextAppearance.Material.Notification.Time" parent="TextAppearance.Material.Notification.Info" />
+ <style name="TextAppearance.Material.Notification.Info.Ambient">
+ <item name="textSize">@dimen/notification_text_size</item>
+ </style>
+
<style name="TextAppearance.Material.Notification.Emphasis">
<item name="textColor">#66000000</item>
</style>
@@ -1283,4 +1287,12 @@
<style name="DialogWindowTitle.Material.Light" />
+ <style name="Notification.Header" parent="">
+ <item name="paddingTop">@dimen/notification_header_padding_top</item>
+ <item name="paddingBottom">@dimen/notification_header_padding_bottom</item>
+ <item name="layout_marginBottom">5dp</item>
+ <item name="paddingStart">@dimen/notification_content_margin_start</item>
+ <item name="paddingEnd">16dp</item>
+ </style>
+
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ae05a69..428db87 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1333,6 +1333,7 @@
<java-symbol type="drawable" name="ic_sim_card_multi_48px_clr" />
<java-symbol type="drawable" name="stat_notify_mmcc_indication_icn" />
+ <java-symbol type="drawable" name="autofilled_highlight"/>
<java-symbol type="drawable" name="ic_account_circle" />
<java-symbol type="color" name="user_icon_1" />
@@ -2883,6 +2884,8 @@
<java-symbol type="string" name="autofill_save_type_password" />
<java-symbol type="string" name="autofill_save_type_address" />
<java-symbol type="string" name="autofill_save_type_credit_card" />
+ <java-symbol type="string" name="autofill_save_type_username" />
+ <java-symbol type="string" name="autofill_save_type_email_address" />
<!-- Accessibility fingerprint gestures -->
<java-symbol type="string" name="capability_title_canCaptureFingerprintGestures" />
@@ -2944,6 +2947,7 @@
<java-symbol type="string" name="notification_channel_alerts" />
<java-symbol type="string" name="notification_channel_retail_mode" />
<java-symbol type="string" name="notification_channel_usb" />
+ <java-symbol type="string" name="config_defaultAutofillService" />
<!-- ETWS primary messages -->
<java-symbol type="string" name="etws_primary_default_message_earthquake" />
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 008c817..9dafa7a 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -1321,6 +1321,19 @@
<item name="windowNoTitle">true</item>
</style>
+ <!-- Theme for inflating notifications -->
+ <style name="Theme.Material.Notification" parent="">
+ <item name="notificationHeaderStyle">@style/Notification.Header</item>
+ <item name="notificationHeaderTextAppearance">@style/TextAppearance.Material.Notification.Info</item>
+ <item name="notificationHeaderIconSize">@dimen/notification_header_icon_size</item>
+ </style>
+
+ <!-- Theme for inflating ambient notification -->
+ <style name="Theme.Material.Notification.Ambient">
+ <item name="notificationHeaderTextAppearance">@style/TextAppearance.Material.Notification.Info.Ambient</item>
+ <item name="notificationHeaderIconSize">@dimen/notification_header_icon_size_ambient</item>
+ </style>
+
<!-- Default theme for Settings and activities launched from Settings. -->
<style name="Theme.Material.Settings" parent="Theme.Material.Light.LightStatusBar">
<item name="colorPrimary">@color/primary_material_settings_light</item>
diff --git a/core/res/res/xml/time_zones_by_country.xml b/core/res/res/xml/time_zones_by_country.xml
index 6c1ce44..22bfea1 100644
--- a/core/res/res/xml/time_zones_by_country.xml
+++ b/core/res/res/xml/time_zones_by_country.xml
@@ -31,11 +31,11 @@
<!-- ANTIGUA AND BARBUDA, -4:00 -->
- <timezone code="ag">America/Port_of_Spain</timezone>
+ <timezone code="ag">America/Antigua</timezone>
<!-- ANGUILLA, -4:00 -->
- <timezone code="ai">America/Port_of_Spain</timezone>
+ <timezone code="ai">America/Anguilla</timezone>
<!-- ALBANIA, 1:00 -->
@@ -47,11 +47,11 @@
<!-- ANGOLA, 1:00 -->
- <timezone code="ao">Africa/Lagos</timezone>
+ <timezone code="ao">Africa/Luanda</timezone>
<!-- ANTARCTICA, 12:00 -->
- <timezone code="aq">Pacific/Auckland</timezone>
+ <timezone code="aq">Antarctica/McMurdo</timezone>
<!-- ANTARCTICA, 10:00 -->
@@ -144,11 +144,11 @@
<!-- ARUBA, -4:00 -->
- <timezone code="aw">America/Curacao</timezone>
+ <timezone code="aw">America/Aruba</timezone>
<!-- ALAND ISLANDS, 2:00 -->
- <timezone code="ax">Europe/Helsinki</timezone>
+ <timezone code="ax">Europe/Mariehamn</timezone>
<!-- AZERBAIJAN, 4:00 -->
@@ -156,7 +156,7 @@
<!-- BOSNIA AND HERZEGOVINA, 1:00 -->
- <timezone code="ba">Europe/Belgrade</timezone>
+ <timezone code="ba">Europe/Sarajevo</timezone>
<!-- BARBADOS, -4:00 -->
@@ -172,7 +172,7 @@
<!-- BURKINA FASO, 0:00 -->
- <timezone code="bf">Africa/Abidjan</timezone>
+ <timezone code="bf">Africa/Ouagadougou</timezone>
<!-- BULGARIA, 2:00 -->
@@ -180,19 +180,19 @@
<!-- BAHRAIN, 3:00 -->
- <timezone code="bh">Asia/Qatar</timezone>
+ <timezone code="bh">Asia/Bahrain</timezone>
<!-- BURUNDI, 2:00 -->
- <timezone code="bi">Africa/Maputo</timezone>
+ <timezone code="bi">Africa/Bujumbura</timezone>
<!-- BENIN, 1:00 -->
- <timezone code="bj">Africa/Lagos</timezone>
+ <timezone code="bj">Africa/Porto-Novo</timezone>
<!-- Saint Barthélemy, -4:00 -->
- <timezone code="bl">America/Port_of_Spain</timezone>
+ <timezone code="bl">America/St_Barthelemy</timezone>
<!-- BERMUDA, -4:00 -->
@@ -208,7 +208,7 @@
<!-- Caribbean Netherlands, -4:00 -->
- <timezone code="bq">America/Curacao</timezone>
+ <timezone code="bq">America/Kralendijk</timezone>
<!-- BRAZIL, -2:00 -->
@@ -248,7 +248,7 @@
<!-- BOTSWANA, 2:00 -->
- <timezone code="bw">Africa/Maputo</timezone>
+ <timezone code="bw">Africa/Gaborone</timezone>
<!-- BELARUS, 3:00 -->
@@ -310,19 +310,19 @@
<!-- CONGO, THE DEMOCRATIC REPUBLIC OF THE, 2:00 -->
- <timezone code="cd">Africa/Maputo</timezone>
+ <timezone code="cd">Africa/Lubumbashi</timezone>
<!-- CONGO, THE DEMOCRATIC REPUBLIC OF THE, 1:00 -->
- <timezone code="cd">Africa/Lagos</timezone>
+ <timezone code="cd">Africa/Kinshasa</timezone>
<!-- CENTRAL AFRICAN REPUBLIC, 1:00 -->
- <timezone code="cf">Africa/Lagos</timezone>
+ <timezone code="cf">Africa/Bangui</timezone>
<!-- CONGO, 1:00 -->
- <timezone code="cg">Africa/Lagos</timezone>
+ <timezone code="cg">Africa/Brazzaville</timezone>
<!-- SWITZERLAND, 1:00 -->
@@ -350,7 +350,7 @@
<!-- CAMEROON, 1:00 -->
- <timezone code="cm">Africa/Lagos</timezone>
+ <timezone code="cm">Africa/Douala</timezone>
<!-- CHINA, 8:00 -->
@@ -388,6 +388,10 @@
<timezone code="cy">Asia/Nicosia</timezone>
+ <!-- CYPRUS, 3:00 -->
+
+ <timezone code="cy">Asia/Famagusta</timezone>
+
<!-- CZECH REPUBLIC, 1:00 -->
<timezone code="cz">Europe/Prague</timezone>
@@ -395,11 +399,11 @@
<!-- GERMANY, 1:00 -->
<timezone code="de">Europe/Berlin</timezone>
- <timezone code="de">Europe/Zurich</timezone>
+ <timezone code="de">Europe/Busingen</timezone>
<!-- DJIBOUTI, 3:00 -->
- <timezone code="dj">Africa/Nairobi</timezone>
+ <timezone code="dj">Africa/Djibouti</timezone>
<!-- DENMARK, 1:00 -->
@@ -407,7 +411,7 @@
<!-- DOMINICA, -4:00 -->
- <timezone code="dm">America/Port_of_Spain</timezone>
+ <timezone code="dm">America/Dominica</timezone>
<!-- DOMINICAN REPUBLIC, -4:00 -->
@@ -439,7 +443,7 @@
<!-- ERITREA, 3:00 -->
- <timezone code="er">Africa/Nairobi</timezone>
+ <timezone code="er">Africa/Asmara</timezone>
<!-- SPAIN, 1:00 -->
@@ -452,7 +456,7 @@
<!-- ETHIOPIA, 3:00 -->
- <timezone code="et">Africa/Nairobi</timezone>
+ <timezone code="et">Africa/Addis_Ababa</timezone>
<!-- FINLAND, 2:00 -->
@@ -468,7 +472,7 @@
<!-- MICRONESIA, FEDERATED STATES OF, 11:00 -->
- <timezone code="fm">Pacific/Ponape</timezone>
+ <timezone code="fm">Pacific/Pohnpei</timezone>
<timezone code="fm">Pacific/Kosrae</timezone>
<!-- MICRONESIA, FEDERATED STATES OF, 10:00 -->
@@ -485,7 +489,7 @@
<!-- GABON, 1:00 -->
- <timezone code="ga">Africa/Lagos</timezone>
+ <timezone code="ga">Africa/Libreville</timezone>
<!-- UNITED KINGDOM, 0:00 -->
@@ -493,7 +497,7 @@
<!-- GRENADA, -4:00 -->
- <timezone code="gd">America/Port_of_Spain</timezone>
+ <timezone code="gd">America/Grenada</timezone>
<!-- GEORGIA, 4:00 -->
@@ -505,7 +509,7 @@
<!-- GUERNSEY, 0:00 -->
- <timezone code="gg">Europe/London</timezone>
+ <timezone code="gg">Europe/Guernsey</timezone>
<!-- GHANA, 0:00 -->
@@ -533,19 +537,19 @@
<!-- GAMBIA, 0:00 -->
- <timezone code="gm">Africa/Abidjan</timezone>
+ <timezone code="gm">Africa/Banjul</timezone>
<!-- GUINEA, 0:00 -->
- <timezone code="gn">Africa/Abidjan</timezone>
+ <timezone code="gn">Africa/Conakry</timezone>
<!-- GUADELOUPE, -4:00 -->
- <timezone code="gp">America/Port_of_Spain</timezone>
+ <timezone code="gp">America/Guadeloupe</timezone>
<!-- EQUATORIAL GUINEA, 1:00 -->
- <timezone code="gq">Africa/Lagos</timezone>
+ <timezone code="gq">Africa/Malabo</timezone>
<!-- GREECE, 2:00 -->
@@ -581,7 +585,7 @@
<!-- CROATIA, 1:00 -->
- <timezone code="hr">Europe/Belgrade</timezone>
+ <timezone code="hr">Europe/Zagreb</timezone>
<!-- HAITI, -5:00 -->
@@ -614,7 +618,7 @@
<!-- ISLE OF MAN, 0:00 -->
- <timezone code="im">Europe/London</timezone>
+ <timezone code="im">Europe/Isle_of_Man</timezone>
<!-- INDIA, 5:30 -->
@@ -642,7 +646,7 @@
<!-- JERSEY, 0:00 -->
- <timezone code="je">Europe/London</timezone>
+ <timezone code="je">Europe/Jersey</timezone>
<!-- JAMAICA, -5:00 -->
@@ -666,7 +670,7 @@
<!-- CAMBODIA, 7:00 -->
- <timezone code="kh">Asia/Bangkok</timezone>
+ <timezone code="kh">Asia/Phnom_Penh</timezone>
<!-- KIRIBATI, 14:00 -->
@@ -682,11 +686,11 @@
<!-- COMOROS, 3:00 -->
- <timezone code="km">Africa/Nairobi</timezone>
+ <timezone code="km">Indian/Comoro</timezone>
<!-- SAINT KITTS AND NEVIS, -4:00 -->
- <timezone code="kn">America/Port_of_Spain</timezone>
+ <timezone code="kn">America/St_Kitts</timezone>
<!-- KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF, 8:30 -->
@@ -698,11 +702,11 @@
<!-- KUWAIT, 3:00 -->
- <timezone code="kw">Asia/Riyadh</timezone>
+ <timezone code="kw">Asia/Kuwait</timezone>
<!-- CAYMAN ISLANDS, -5:00 -->
- <timezone code="ky">America/Panama</timezone>
+ <timezone code="ky">America/Cayman</timezone>
<!-- KAZAKHSTAN, 6:00 -->
@@ -714,10 +718,11 @@
<timezone code="kz">Asia/Aqtau</timezone>
<timezone code="kz">Asia/Oral</timezone>
<timezone code="kz">Asia/Aqtobe</timezone>
+ <timezone code="kz">Asia/Atyrau</timezone>
<!-- LAO PEOPLE'S DEMOCRATIC REPUBLIC, 7:00 -->
- <timezone code="la">Asia/Bangkok</timezone>
+ <timezone code="la">Asia/Vientiane</timezone>
<!-- LEBANON, 2:00 -->
@@ -725,11 +730,11 @@
<!-- SAINT LUCIA, -4:00 -->
- <timezone code="lc">America/Port_of_Spain</timezone>
+ <timezone code="lc">America/St_Lucia</timezone>
<!-- LIECHTENSTEIN, 1:00 -->
- <timezone code="li">Europe/Zurich</timezone>
+ <timezone code="li">Europe/Vaduz</timezone>
<!-- SRI LANKA, 5:30 -->
@@ -741,7 +746,7 @@
<!-- LESOTHO, 2:00 -->
- <timezone code="ls">Africa/Johannesburg</timezone>
+ <timezone code="ls">Africa/Maseru</timezone>
<!-- LITHUANIA, 2:00 -->
@@ -773,15 +778,15 @@
<!-- MONTENEGRO, 1:00 -->
- <timezone code="me">Europe/Belgrade</timezone>
+ <timezone code="me">Europe/Podgorica</timezone>
<!-- Collectivity of Saint Martin, -4:00 -->
- <timezone code="mf">America/Port_of_Spain</timezone>
+ <timezone code="mf">America/Marigot</timezone>
<!-- MADAGASCAR, 3:00 -->
- <timezone code="mg">Africa/Nairobi</timezone>
+ <timezone code="mg">Indian/Antananarivo</timezone>
<!-- MARSHALL ISLANDS, 12:00 -->
@@ -790,11 +795,11 @@
<!-- MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF, 1:00 -->
- <timezone code="mk">Europe/Belgrade</timezone>
+ <timezone code="mk">Europe/Skopje</timezone>
<!-- MALI, 0:00 -->
- <timezone code="ml">Africa/Abidjan</timezone>
+ <timezone code="ml">Africa/Bamako</timezone>
<!-- MYANMAR, 6:30 -->
@@ -815,7 +820,7 @@
<!-- NORTHERN MARIANA ISLANDS, 10:00 -->
- <timezone code="mp">Pacific/Guam</timezone>
+ <timezone code="mp">Pacific/Saipan</timezone>
<!-- MARTINIQUE, -4:00 -->
@@ -823,11 +828,11 @@
<!-- MAURITANIA, 0:00 -->
- <timezone code="mr">Africa/Abidjan</timezone>
+ <timezone code="mr">Africa/Nouakchott</timezone>
<!-- MONTSERRAT, -4:00 -->
- <timezone code="ms">America/Port_of_Spain</timezone>
+ <timezone code="ms">America/Montserrat</timezone>
<!-- MALTA, 1:00 -->
@@ -843,7 +848,7 @@
<!-- MALAWI, 2:00 -->
- <timezone code="mw">Africa/Maputo</timezone>
+ <timezone code="mw">Africa/Blantyre</timezone>
<!-- MEXICO, -6:00 -->
@@ -887,7 +892,7 @@
<!-- NIGER, 1:00 -->
- <timezone code="ne">Africa/Lagos</timezone>
+ <timezone code="ne">Africa/Niamey</timezone>
<!-- NORFOLK ISLAND, 11:30 -->
@@ -911,7 +916,7 @@
<!-- NEPAL, 5:45 -->
- <timezone code="np">Asia/Katmandu</timezone>
+ <timezone code="np">Asia/Kathmandu</timezone>
<!-- NAURU, 12:00 -->
@@ -931,7 +936,7 @@
<!-- OMAN, 4:00 -->
- <timezone code="om">Asia/Dubai</timezone>
+ <timezone code="om">Asia/Muscat</timezone>
<!-- PANAMA, -5:00 -->
@@ -1070,6 +1075,7 @@
<timezone code="ru">Europe/Samara</timezone>
<timezone code="ru">Europe/Astrakhan</timezone>
<timezone code="ru">Europe/Ulyanovsk</timezone>
+ <timezone code="ru">Europe/Saratov</timezone>
<!-- RUSSIAN FEDERATION, 3:00 -->
@@ -1084,7 +1090,7 @@
<!-- RWANDA, 2:00 -->
- <timezone code="rw">Africa/Maputo</timezone>
+ <timezone code="rw">Africa/Kigali</timezone>
<!-- SAUDI ARABIA, 3:00 -->
@@ -1112,35 +1118,35 @@
<!-- SAINT HELENA, 0:00 -->
- <timezone code="sh">Africa/Abidjan</timezone>
+ <timezone code="sh">Atlantic/St_Helena</timezone>
<!-- SLOVENIA, 1:00 -->
- <timezone code="si">Europe/Belgrade</timezone>
+ <timezone code="si">Europe/Ljubljana</timezone>
<!-- SVALBARD AND JAN MAYEN, 1:00 -->
- <timezone code="sj">Europe/Oslo</timezone>
+ <timezone code="sj">Arctic/Longyearbyen</timezone>
<!-- SLOVAKIA, 1:00 -->
- <timezone code="sk">Europe/Prague</timezone>
+ <timezone code="sk">Europe/Bratislava</timezone>
<!-- SIERRA LEONE, 0:00 -->
- <timezone code="sl">Africa/Abidjan</timezone>
+ <timezone code="sl">Africa/Freetown</timezone>
<!-- SAN MARINO, 1:00 -->
- <timezone code="sm">Europe/Rome</timezone>
+ <timezone code="sm">Europe/San_Marino</timezone>
<!-- SENEGAL, 0:00 -->
- <timezone code="sn">Africa/Abidjan</timezone>
+ <timezone code="sn">Africa/Dakar</timezone>
<!-- SOMALIA, 3:00 -->
- <timezone code="so">Africa/Nairobi</timezone>
+ <timezone code="so">Africa/Mogadishu</timezone>
<!-- SURINAME, -3:00 -->
@@ -1148,11 +1154,11 @@
<!-- South Sudan, 3:00 -->
- <timezone code="ss">Africa/Khartoum</timezone>
+ <timezone code="ss">Africa/Juba</timezone>
<!-- SAO TOME AND PRINCIPE, 0:00 -->
- <timezone code="st">Africa/Abidjan</timezone>
+ <timezone code="st">Africa/Sao_Tome</timezone>
<!-- EL SALVADOR, -6:00 -->
@@ -1160,7 +1166,7 @@
<!-- Sint Maarten, -4:00 -->
- <timezone code="sx">America/Curacao</timezone>
+ <timezone code="sx">America/Lower_Princes</timezone>
<!-- SYRIAN ARAB REPUBLIC, 2:00 -->
@@ -1168,7 +1174,7 @@
<!-- SWAZILAND, 2:00 -->
- <timezone code="sz">Africa/Johannesburg</timezone>
+ <timezone code="sz">Africa/Mbabane</timezone>
<!-- TURKS AND CAICOS ISLANDS, -4:00 -->
@@ -1182,13 +1188,9 @@
<timezone code="tf">Indian/Kerguelen</timezone>
- <!-- FRENCH SOUTHERN TERRITORIES, 4:00 -->
-
- <timezone code="tf">Indian/Reunion</timezone>
-
<!-- TOGO, 0:00 -->
- <timezone code="tg">Africa/Abidjan</timezone>
+ <timezone code="tg">Africa/Lome</timezone>
<!-- THAILAND, 7:00 -->
@@ -1236,7 +1238,7 @@
<!-- TANZANIA, UNITED REPUBLIC OF, 3:00 -->
- <timezone code="tz">Africa/Nairobi</timezone>
+ <timezone code="tz">Africa/Dar_es_Salaam</timezone>
<!-- UKRAINE, 2:00 -->
@@ -1246,19 +1248,15 @@
<!-- UGANDA, 3:00 -->
- <timezone code="ug">Africa/Nairobi</timezone>
+ <timezone code="ug">Africa/Kampala</timezone>
<!-- UNITED STATES MINOR OUTLYING ISLANDS, 12:00 -->
<timezone code="um">Pacific/Wake</timezone>
- <!-- UNITED STATES MINOR OUTLYING ISLANDS, -10:00 -->
-
- <timezone code="um">Pacific/Honolulu</timezone>
-
<!-- UNITED STATES MINOR OUTLYING ISLANDS, -11:00 -->
- <timezone code="um">Pacific/Pago_Pago</timezone>
+ <timezone code="um">Pacific/Midway</timezone>
<!-- UNITED STATES, -5:00 -->
@@ -1318,11 +1316,11 @@
<!-- HOLY SEE (VATICAN CITY STATE), 1:00 -->
- <timezone code="va">Europe/Rome</timezone>
+ <timezone code="va">Europe/Vatican</timezone>
<!-- SAINT VINCENT AND THE GRENADINES, -4:00 -->
- <timezone code="vc">America/Port_of_Spain</timezone>
+ <timezone code="vc">America/St_Vincent</timezone>
<!-- VENEZUELA, -4:00 -->
@@ -1330,16 +1328,15 @@
<!-- VIRGIN ISLANDS, BRITISH, -4:00 -->
- <timezone code="vg">America/Port_of_Spain</timezone>
+ <timezone code="vg">America/Tortola</timezone>
<!-- VIRGIN ISLANDS, U.S., -4:00 -->
- <timezone code="vi">America/Port_of_Spain</timezone>
+ <timezone code="vi">America/St_Thomas</timezone>
<!-- VIET NAM, 7:00 -->
<timezone code="vn">Asia/Ho_Chi_Minh</timezone>
- <timezone code="vn">Asia/Bangkok</timezone>
<!-- VANUATU, 11:00 -->
@@ -1355,11 +1352,11 @@
<!-- YEMEN, 3:00 -->
- <timezone code="ye">Asia/Riyadh</timezone>
+ <timezone code="ye">Asia/Aden</timezone>
<!-- MAYOTTE, 3:00 -->
- <timezone code="yt">Africa/Nairobi</timezone>
+ <timezone code="yt">Indian/Mayotte</timezone>
<!-- SOUTH AFRICA, 2:00 -->
@@ -1367,9 +1364,9 @@
<!-- ZAMBIA, 2:00 -->
- <timezone code="zm">Africa/Maputo</timezone>
+ <timezone code="zm">Africa/Lusaka</timezone>
<!-- ZIMBABWE, 2:00 -->
- <timezone code="zw">Africa/Maputo</timezone>
+ <timezone code="zw">Africa/Harare</timezone>
</timezones>
diff --git a/core/tests/coretests/src/android/provider/FontsContractTest.java b/core/tests/coretests/src/android/provider/FontsContractTest.java
index 6820e92..1dd3ef6 100644
--- a/core/tests/coretests/src/android/provider/FontsContractTest.java
+++ b/core/tests/coretests/src/android/provider/FontsContractTest.java
@@ -94,7 +94,8 @@
FontResult fontResult = resultList.get(0);
assertEquals(TestFontsProvider.TTC_INDEX, fontResult.getTtcIndex());
assertEquals(TestFontsProvider.VARIATION_SETTINGS, fontResult.getFontVariationSettings());
- assertEquals(TestFontsProvider.STYLE, fontResult.getStyle());
+ assertEquals(TestFontsProvider.NORMAL_WEIGHT, fontResult.getWeight());
+ assertEquals(TestFontsProvider.ITALIC, fontResult.getItalic());
assertNotNull(fontResult.getFileDescriptor());
}
@@ -115,7 +116,8 @@
FontResult fontResult = resultList.get(0);
assertEquals(0, fontResult.getTtcIndex());
assertNull(fontResult.getFontVariationSettings());
- assertEquals(Typeface.NORMAL, fontResult.getStyle());
+ assertEquals(400, fontResult.getWeight());
+ assertFalse(fontResult.getItalic());
assertNotNull(fontResult.getFileDescriptor());
}
@@ -146,10 +148,10 @@
public void testGetFontFromProvider_resultFontNotFoundSecondRow() {
MatrixCursor cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID,
FontsContract.Columns.TTC_INDEX, FontsContract.Columns.VARIATION_SETTINGS,
- FontsContract.Columns.STYLE, FontsContract.Columns.RESULT_CODE });
- cursor.addRow(new Object[] { 1, 0, null, Typeface.NORMAL,
- FontsContract.Columns.RESULT_CODE_OK});
- cursor.addRow(new Object[] { 1, 0, null, Typeface.NORMAL,
+ FontsContract.Columns.WEIGHT, FontsContract.Columns.ITALIC,
+ FontsContract.Columns.RESULT_CODE });
+ cursor.addRow(new Object[] { 1, 0, null, 400, 0, FontsContract.Columns.RESULT_CODE_OK});
+ cursor.addRow(new Object[] { 1, 0, null, 400, 0,
FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND});
mProvider.setCustomCursor(cursor);
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
@@ -160,13 +162,12 @@
public void testGetFontFromProvider_resultFontNotFoundOtherRow() {
MatrixCursor cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID,
FontsContract.Columns.TTC_INDEX, FontsContract.Columns.VARIATION_SETTINGS,
- FontsContract.Columns.STYLE, FontsContract.Columns.RESULT_CODE });
- cursor.addRow(new Object[] { 1, 0, null, Typeface.NORMAL,
- FontsContract.Columns.RESULT_CODE_OK});
- cursor.addRow(new Object[] { 1, 0, null, Typeface.NORMAL,
+ FontsContract.Columns.WEIGHT, FontsContract.Columns.ITALIC,
+ FontsContract.Columns.RESULT_CODE });
+ cursor.addRow(new Object[] { 1, 0, null, 400, 0, FontsContract.Columns.RESULT_CODE_OK});
+ cursor.addRow(new Object[] { 1, 0, null, 400, 0,
FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND});
- cursor.addRow(new Object[] { 1, 0, null, Typeface.NORMAL,
- FontsContract.Columns.RESULT_CODE_OK});
+ cursor.addRow(new Object[] { 1, 0, null, 400, 0, FontsContract.Columns.RESULT_CODE_OK});
mProvider.setCustomCursor(cursor);
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
@@ -176,10 +177,10 @@
public void testGetFontFromProvider_resultCodeIsNegativeNumber() {
MatrixCursor cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID,
FontsContract.Columns.TTC_INDEX, FontsContract.Columns.VARIATION_SETTINGS,
- FontsContract.Columns.STYLE, FontsContract.Columns.RESULT_CODE });
- cursor.addRow(new Object[] { 1, 0, null, Typeface.NORMAL,
- FontsContract.Columns.RESULT_CODE_OK});
- cursor.addRow(new Object[] { 1, 0, null, Typeface.NORMAL, -5});
+ FontsContract.Columns.WEIGHT, FontsContract.Columns.ITALIC,
+ FontsContract.Columns.RESULT_CODE });
+ cursor.addRow(new Object[] { 1, 0, null, 400, 0, FontsContract.Columns.RESULT_CODE_OK});
+ cursor.addRow(new Object[] { 1, 0, null, 400, 0, -5});
mProvider.setCustomCursor(cursor);
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
diff --git a/core/tests/coretests/src/android/provider/TestFontsProvider.java b/core/tests/coretests/src/android/provider/TestFontsProvider.java
index 13f5318..46906df 100644
--- a/core/tests/coretests/src/android/provider/TestFontsProvider.java
+++ b/core/tests/coretests/src/android/provider/TestFontsProvider.java
@@ -37,7 +37,8 @@
static final String AUTHORITY = "android.provider.TestFontsProvider";
static final int TTC_INDEX = 2;
static final String VARIATION_SETTINGS = "'wdth' 1";
- static final int STYLE = Typeface.BOLD;
+ static final int NORMAL_WEIGHT = 400;
+ static final boolean ITALIC = false;
private ParcelFileDescriptor mPfd;
private boolean mReturnAllFields = true;
@@ -81,8 +82,9 @@
if (mReturnAllFields) {
cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID,
FontsContract.Columns.TTC_INDEX, FontsContract.Columns.VARIATION_SETTINGS,
- FontsContract.Columns.STYLE, FontsContract.Columns.RESULT_CODE });
- cursor.addRow(new Object[] { 1, TTC_INDEX, VARIATION_SETTINGS, STYLE, mResultCode });
+ FontsContract.Columns.WEIGHT, FontsContract.Columns.ITALIC,
+ FontsContract.Columns.RESULT_CODE });
+ cursor.addRow(new Object[] { 1, TTC_INDEX, VARIATION_SETTINGS, 400, 0, mResultCode });
} else {
cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID });
cursor.addRow(new Object[] { 1 });
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
index 6b52b98..8283335 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
@@ -21,6 +21,7 @@
import android.os.BatteryStats;
import android.os.WorkSource;
import android.support.test.filters.SmallTest;
+import android.util.ArrayMap;
import junit.framework.TestCase;
@@ -187,4 +188,65 @@
assertEquals((305 - 202) * 1000, actualTime);
assertEquals((305 - 254) * 1000, bgTime);
}
+
+ @SmallTest
+ public void testJob() throws Exception {
+ final MockClocks clocks = new MockClocks();
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ final String jobName = "job_name";
+ long curr = 0; // realtime in us
+
+ // On battery
+ curr = 1000 * (clocks.realtime = clocks.uptime = 100);
+ bi.updateTimeBasesLocked(true, false, curr, curr); // on battery
+ // App in foreground
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+
+ // Start timer
+ curr = 1000 * (clocks.realtime = clocks.uptime = 151);
+ bi.noteJobStartLocked(jobName, UID);
+
+ // Stop timer
+ curr = 1000 * (clocks.realtime = clocks.uptime = 161);
+ bi.noteJobFinishLocked(jobName, UID);
+
+ // Start timer
+ curr = 1000 * (clocks.realtime = clocks.uptime = 202);
+ bi.noteJobStartLocked(jobName, UID);
+
+ // Move to background
+ curr = 1000 * (clocks.realtime = clocks.uptime = 254);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+ // Off battery
+ curr = 1000 * (clocks.realtime = clocks.uptime = 305);
+ bi.updateTimeBasesLocked(false, false, curr, curr); // off battery
+
+ // Stop timer
+ curr = 1000 * (clocks.realtime = clocks.uptime = 409);
+ bi.noteJobFinishLocked(jobName, UID);
+
+ // Test
+ curr = 1000 * (clocks.realtime = clocks.uptime = 657);
+ final ArrayMap<String, ? extends BatteryStats.Timer> jobs =
+ bi.getUidStats().get(UID).getJobStats();
+ assertEquals(1, jobs.size());
+ BatteryStats.Timer timer = jobs.valueAt(0);
+ BatteryStats.Timer bgTimer = timer.getSubTimer();
+ long time = timer.getTotalTimeLocked(curr, STATS_SINCE_CHARGED);
+ int count = timer.getCountLocked(STATS_SINCE_CHARGED);
+ int bgCount = bgTimer.getCountLocked(STATS_SINCE_CHARGED);
+ long bgTime = bgTimer.getTotalTimeLocked(curr, STATS_SINCE_CHARGED);
+ assertEquals((161 - 151 + 305 - 202) * 1000, time);
+ assertEquals(2, count);
+ assertEquals(1, bgCount);
+ assertEquals((305 - 254) * 1000, bgTime);
+
+ // Test that a second job is separate.
+ curr = 1000 * (clocks.realtime = clocks.uptime = 3000);
+ final String jobName2 = "second_job";
+ bi.noteJobStartLocked(jobName2, UID);
+ assertEquals(2, bi.getUidStats().get(UID).getJobStats().size());
+ bi.noteJobFinishLocked(jobName2, UID);
+ }
}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 560d29f..5afe5e9 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -357,8 +357,8 @@
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;
+ int weight = result.getWeight();
+ int italic = result.getItalic() ? Builder.ITALIC : Builder.NORMAL;
FontVariationAxis[] axes = null;
try {
axes = FontVariationAxis.fromFontVariationSettings(
@@ -366,8 +366,8 @@
} catch (FontVariationAxis.InvalidFormatException e) {
// TODO: Nice to pass FontVariationAxis[] directly instead of string.
}
- if (!fontFamily.addFontFromBuffer(fontBuffer, result.getTtcIndex(), axes, weight,
- (style & ITALIC) == 0 ? Builder.NORMAL : Builder.ITALIC)) {
+ if (!fontFamily.addFontFromBuffer(fontBuffer, result.getTtcIndex(),
+ axes, weight, italic)) {
Log.e(TAG, "Error creating font " + request.getQuery());
callback.onTypefaceRequestFailed(
FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
diff --git a/graphics/java/android/graphics/fonts/FontResult.java b/graphics/java/android/graphics/fonts/FontResult.java
index 3ef99fd..20e736e 100644
--- a/graphics/java/android/graphics/fonts/FontResult.java
+++ b/graphics/java/android/graphics/fonts/FontResult.java
@@ -35,7 +35,8 @@
private final ParcelFileDescriptor mFileDescriptor;
private final int mTtcIndex;
private final String mFontVariationSettings;
- private final int mStyle;
+ private final int mWeight;
+ private final boolean mItalic;
/**
* Creates a FontResult with all the information needed about a provided font.
@@ -45,16 +46,16 @@
* 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}
+ * @param weight An integer that indicates the font weight.
+ * @param italic A boolean that indicates the font is italic style or not.
*/
public FontResult(@NonNull ParcelFileDescriptor fileDescriptor, int ttcIndex,
- @Nullable String fontVariationSettings, int style) {
+ @Nullable String fontVariationSettings, int weight, boolean italic) {
mFileDescriptor = Preconditions.checkNotNull(fileDescriptor);
mTtcIndex = ttcIndex;
mFontVariationSettings = fontVariationSettings;
- mStyle = style;
+ mWeight = weight;
+ mItalic = italic;
}
public ParcelFileDescriptor getFileDescriptor() {
@@ -69,8 +70,12 @@
return mFontVariationSettings;
}
- public int getStyle() {
- return mStyle;
+ public int getWeight() {
+ return mWeight;
+ }
+
+ public boolean getItalic() {
+ return mItalic;
}
@Override
@@ -83,14 +88,16 @@
dest.writeParcelable(mFileDescriptor, flags);
dest.writeInt(mTtcIndex);
dest.writeString(mFontVariationSettings);
- dest.writeInt(mStyle);
+ dest.writeInt(mWeight);
+ dest.writeBoolean(mItalic);
}
private FontResult(Parcel in) {
mFileDescriptor = in.readParcelable(null);
mTtcIndex = in.readInt();
mFontVariationSettings = in.readString();
- mStyle = in.readInt();
+ mWeight = in.readInt();
+ mItalic = in.readBoolean();
}
public static final Parcelable.Creator<FontResult> CREATOR =
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
index 2ead5c5..d26eb59 100644
--- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
+++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
@@ -162,7 +162,7 @@
SkAutoCanvasRestore acr(canvas, true);
SkMatrix shadowMatrix;
- mat4 hwuiMatrix(caster->getRecordedMatrix());
+ mat4 hwuiMatrix;
// TODO we don't pass the optional boolean to treat it as a 4x4 matrix
caster->getRenderNode()->applyViewPropertyTransforms(hwuiMatrix);
hwuiMatrix.copyTo(shadowMatrix);
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 652954b..686d06f 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -941,3 +941,67 @@
EXPECT_EQ(2, canvas.mDrawCounter);
}
+
+TEST(ReorderBarrierDrawable, testShadowMatrix) {
+ static const int CANVAS_WIDTH = 100;
+ static const int CANVAS_HEIGHT = 100;
+ static const float TRANSLATE_X = 11.0f;
+ static const float TRANSLATE_Y = 22.0f;
+ static const float CASTER_X = 40.0f;
+ static const float CASTER_Y = 40.0f;
+ static const float CASTER_WIDTH = 20.0f;
+ static const float CASTER_HEIGHT = 20.0f;
+
+
+ class ShadowTestCanvas : public SkCanvas {
+ public:
+ ShadowTestCanvas(int width, int height) : SkCanvas(width, height) {}
+ int getIndex() { return mDrawCounter; }
+
+ virtual void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
+ // expect to draw 2 RenderNodeDrawable, 1 StartReorderBarrierDrawable,
+ // 1 EndReorderBarrierDrawable
+ mDrawCounter++;
+ SkCanvas::onDrawDrawable(drawable, matrix);
+ }
+
+ virtual void didTranslate(SkScalar dx, SkScalar dy) override {
+ mDrawCounter++;
+ EXPECT_EQ(dx, TRANSLATE_X);
+ EXPECT_EQ(dy, TRANSLATE_Y);
+ }
+
+ virtual void didConcat(const SkMatrix& matrix) override {
+ // This function is invoked by EndReorderBarrierDrawable::drawShadow to apply shadow
+ // matrix.
+ mDrawCounter++;
+ EXPECT_EQ(SkMatrix::MakeTrans(CASTER_X, CASTER_Y), matrix);
+ EXPECT_EQ(SkMatrix::MakeTrans(CASTER_X+TRANSLATE_X, CASTER_Y+TRANSLATE_Y),
+ getTotalMatrix());
+ }
+ protected:
+ int mDrawCounter = 0;
+ };
+
+ auto parent = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+ [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ canvas.translate(TRANSLATE_X, TRANSLATE_Y);
+ canvas.insertReorderBarrier(true);
+
+ auto node = TestUtils::createSkiaNode(CASTER_X, CASTER_Y, CASTER_X + CASTER_WIDTH,
+ CASTER_Y + CASTER_HEIGHT,
+ [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ props.setElevation(42);
+ props.mutableOutline().setRoundRect(0, 0, 20, 20, 5, 1);
+ props.mutableOutline().setShouldClip(true);
+ });
+ canvas.drawRenderNode(node.get());
+ canvas.insertReorderBarrier(false);
+ });
+
+ //create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
+ ShadowTestCanvas canvas(CANVAS_WIDTH, CANVAS_HEIGHT);
+ RenderNodeDrawable drawable(parent.get(), &canvas, false);
+ canvas.drawDrawable(&drawable);
+ EXPECT_EQ(6, canvas.getIndex());
+}
\ No newline at end of file
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 2723826..97dd933 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -753,9 +753,12 @@
* @param init container-specific data, its meaning is interpreted based on the
* mime type provided in the mimeType parameter. It could contain, for example,
* the content ID, key ID or other data obtained from the content metadata that is
- * required in generating the key request. init may be null when keyType is
- * KEY_TYPE_RELEASE.
- * @param mimeType identifies the mime type of the content
+ * required in generating the key request. May be null when keyType is
+ * KEY_TYPE_RELEASE or if the request is a renewal, i.e. not the first key
+ * request for the session.
+ * @param mimeType identifies the mime type of the content. May be null if the
+ * keyType is KEY_TYPE_RELEASE or if the request is a renewal, i.e. not the
+ * first key request for the session.
* @param keyType specifes the type of the request. The request may be to acquire
* keys for streaming or offline content, or to release previously acquired
* keys, which are identified by a keySetId.
@@ -779,13 +782,17 @@
* response is for an offline key request, a keySetId is returned that can be
* used to later restore the keys to a new session with the method
* {@link #restoreKeys}.
- * When the response is for a streaming or release request, null is returned.
+ * When the response is for a streaming or release request, an empty byte array
+ * is returned.
*
* @param scope may be a sessionId or keySetId depending on the type of the
* response. Scope should be set to the sessionId when the response is for either
* streaming or offline key requests. Scope should be set to the keySetId when
* the response is for a release request.
* @param response the byte array response from the server
+ * @return If the response is for an offline request, the keySetId for the offline
+ * keys will be returned. If the response is for a streaming or release request
+ * an empty byte array will be returned.
*
* @throws NotProvisionedException if the response indicates that
* reprovisioning is required
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 23bf3d6..431d5d8 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -34,6 +34,7 @@
libutils \
libbinder \
libmedia \
+ libmediametrics \
libmediadrm \
libmidi \
libskia \
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index d5d9fc9..0149d76 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -342,7 +342,7 @@
}
// New GraphicBuffer object doesn't own the handle, thus the native buffer
// won't be freed when this object is destroyed.
- sp<GraphicBuffer> buffer(new GraphicBuffer(anb, /*keepOwnership*/false));
+ sp<GraphicBuffer> buffer(GraphicBuffer::from(anb));
// Note that:
// 1. No need to lock buffer now, will only lock it when the first getPlanes() is called.
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
index cd0e587..54442b3 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
@@ -154,18 +154,18 @@
@SmallTest
public void testGetTypeFromTag() {
assertEquals(TYPE_BYTE,
- CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_MODE));
+ CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_MODE, Long.MAX_VALUE));
assertEquals(TYPE_RATIONAL,
- CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_TRANSFORM));
+ CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_TRANSFORM, Long.MAX_VALUE));
assertEquals(TYPE_FLOAT,
- CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_GAINS));
+ CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_GAINS, Long.MAX_VALUE));
assertEquals(TYPE_BYTE,
- CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_ANTIBANDING_MODE));
+ CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_ANTIBANDING_MODE, Long.MAX_VALUE));
assertEquals(TYPE_INT32,
- CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION));
+ CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION, Long.MAX_VALUE));
try {
- CameraMetadataNative.getNativeType(0xDEADF00D);
+ CameraMetadataNative.getNativeType(0xDEADF00D, Long.MAX_VALUE);
fail("No type should exist for invalid tag 0xDEADF00D");
} catch(IllegalArgumentException e) {
}
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index f9342b7..fa5c3ff 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -6,6 +6,7 @@
<string name="no_data_notification_id">Your mobile data has been deactivated</string>
<string name="portal_notification_detail">Tap to visit the %s website</string>
<string name="no_data_notification_detail">Please contact your service provider %s</string>
+ <string name="mobile_data_status_notification_channel_name">Mobile data status</string>
<string name="action_bar_label">Sign in to mobile network</string>
<string name="ssl_error_warning">The network you’re trying to join has security issues.</string>
<string name="ssl_error_example">For example, the login page may not belong to the organization shown.</string>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
index 73ff3a9..7fd1601 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
@@ -16,6 +16,7 @@
package com.android.carrierdefaultapp;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
@@ -35,6 +36,7 @@
private static final String PORTAL_NOTIFICATION_TAG = "CarrierDefault.Portal.Notification";
private static final String NO_DATA_NOTIFICATION_TAG = "CarrierDefault.NoData.Notification";
+ private static final String NOTIFICATION_CHANNEL_ID_MOBILE_DATA_STATUS = "mobile_data_status";
private static final int PORTAL_NOTIFICATION_ID = 0;
private static final int NO_DATA_NOTIFICATION_ID = 1;
private static boolean ENABLE = true;
@@ -150,9 +152,18 @@
private static Notification getNotification(Context context, int titleId, int textId,
PendingIntent pendingIntent) {
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
+ final NotificationManager notificationManager = context.getSystemService(
+ NotificationManager.class);
final Resources resources = context.getResources();
final Bundle extras = Bundle.forPair(Notification.EXTRA_SUBSTITUTE_APP_NAME,
resources.getString(R.string.android_system_label));
+ /* Creates the notification channel and registers it with NotificationManager. If a channel
+ * with the same ID is already registered, NotificationManager will ignore this call.
+ */
+ notificationManager.createNotificationChannel(new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID_MOBILE_DATA_STATUS,
+ resources.getString(R.string.mobile_data_status_notification_channel_name),
+ NotificationManager.IMPORTANCE_DEFAULT));
Notification.Builder builder = new Notification.Builder(context)
.setContentTitle(resources.getString(titleId))
.setContentText(String.format(resources.getString(textId),
@@ -167,7 +178,8 @@
.setLocalOnly(true)
.setWhen(System.currentTimeMillis())
.setShowWhen(false)
- .setExtras(extras);
+ .setExtras(extras)
+ .setChannel(NOTIFICATION_CHANNEL_ID_MOBILE_DATA_STATUS);
if (pendingIntent != null) {
builder.setContentIntent(pendingIntent);
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 2e6ed69b..63850ae 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -149,6 +149,12 @@
<!-- Description of printer info icon. [CHAR LIMIT=50] -->
<string name="printer_info_desc">More information about this printer</string>
+ <!-- Label for the notification channel that contains print jobs without problems. [CHAR LIMIT=40] -->
+ <string name="notification_channel_progress">Running print jobs</string>
+
+ <!-- Label for the notification channel that contains print jobs with problems. [CHAR LIMIT=40] -->
+ <string name="notification_channel_failure">Failed print jobs</string>
+
<!-- Notification that we could not create a file name for the printed PDF. [CHAR LIMIT=50] -->
<string name="could_not_create_file">Could not create file</string>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index cd1d540..9d737e0 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -20,13 +20,12 @@
import android.annotation.Nullable;
import android.app.Notification;
import android.app.Notification.Action;
-import android.app.Notification.InboxStyle;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.AsyncTask;
@@ -57,14 +56,14 @@
public static final String LOG_TAG = "NotificationController";
+ private static final String NOTIFICATION_CHANNEL_PROGRESS = "PRINT_PROGRESS";
+ private static final String NOTIFICATION_CHANNEL_FAILURES = "PRINT_FAILURES";
+
private static final String INTENT_ACTION_CANCEL_PRINTJOB = "INTENT_ACTION_CANCEL_PRINTJOB";
private static final String INTENT_ACTION_RESTART_PRINTJOB = "INTENT_ACTION_RESTART_PRINTJOB";
private static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID";
- private static final String PRINT_JOB_NOTIFICATION_GROUP_KEY = "PRINT_JOB_NOTIFICATIONS";
- private static final String PRINT_JOB_NOTIFICATION_SUMMARY = "PRINT_JOB_NOTIFICATIONS_SUMMARY";
-
private final Context mContext;
private final NotificationManager mNotificationManager;
@@ -78,6 +77,15 @@
mNotificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mNotifications = new ArraySet<>(0);
+
+ mNotificationManager.createNotificationChannel(
+ new NotificationChannel(NOTIFICATION_CHANNEL_PROGRESS,
+ context.getString(R.string.notification_channel_progress),
+ NotificationManager.IMPORTANCE_LOW));
+ mNotificationManager.createNotificationChannel(
+ new NotificationChannel(NOTIFICATION_CHANNEL_FAILURES,
+ context.getString(R.string.notification_channel_failure),
+ NotificationManager.IMPORTANCE_DEFAULT));
}
public void onUpdateNotifications(List<PrintJobInfo> printJobs) {
@@ -104,13 +112,6 @@
final int numPrintJobs = printJobs.size();
- // Create summary notification
- if (numPrintJobs > 1) {
- createStackedNotification(printJobs);
- } else {
- mNotificationManager.cancel(PRINT_JOB_NOTIFICATION_SUMMARY, 0);
- }
-
// Create per print job notification
for (int i = 0; i < numPrintJobs; i++) {
PrintJobInfo printJob = printJobs.get(i);
@@ -178,16 +179,16 @@
*/
private void createNotification(@NonNull PrintJobInfo printJob, @Nullable Action firstAction,
@Nullable Action secondAction) {
- Notification.Builder builder = new Notification.Builder(mContext)
+ Notification.Builder builder = new Notification.Builder(mContext, computeChannel(printJob))
.setContentIntent(createContentIntent(printJob.getId()))
.setSmallIcon(computeNotificationIcon(printJob))
.setContentTitle(computeNotificationTitle(printJob))
.setWhen(System.currentTimeMillis())
.setOngoing(true)
.setShowWhen(true)
+ .setOnlyAlertOnce(true)
.setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .setGroup(PRINT_JOB_NOTIFICATION_GROUP_KEY);
+ com.android.internal.R.color.system_notification_accent_color));
if (firstAction != null) {
builder.addAction(firstAction);
@@ -238,43 +239,6 @@
createNotification(printJob, null, null);
}
- private void createStackedNotification(List<PrintJobInfo> printJobs) {
- Notification.Builder builder = new Notification.Builder(mContext)
- .setContentIntent(createContentIntent(null))
- .setWhen(System.currentTimeMillis())
- .setOngoing(true)
- .setShowWhen(true)
- .setGroup(PRINT_JOB_NOTIFICATION_GROUP_KEY)
- .setGroupSummary(true);
-
- final int printJobCount = printJobs.size();
-
- InboxStyle inboxStyle = new InboxStyle();
-
- int icon = com.android.internal.R.drawable.ic_print;
- for (int i = printJobCount - 1; i>= 0; i--) {
- PrintJobInfo printJob = printJobs.get(i);
-
- inboxStyle.addLine(computeNotificationTitle(printJob));
-
- // if any print job is in an error state show an error icon for the summary
- if (printJob.getState() == PrintJobInfo.STATE_FAILED
- || printJob.getState() == PrintJobInfo.STATE_BLOCKED) {
- icon = com.android.internal.R.drawable.ic_print_error;
- }
- }
-
- builder.setSmallIcon(icon);
- builder.setLargeIcon(
- ((BitmapDrawable) mContext.getResources().getDrawable(icon, null)).getBitmap());
- builder.setNumber(printJobCount);
- builder.setStyle(inboxStyle);
- builder.setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
-
- mNotificationManager.notify(PRINT_JOB_NOTIFICATION_SUMMARY, 0, builder.build());
- }
-
private String computeNotificationTitle(PrintJobInfo printJob) {
switch (printJob.getState()) {
case PrintJobInfo.STATE_FAILED: {
@@ -359,6 +323,22 @@
}
}
+ private static String computeChannel(PrintJobInfo printJob) {
+ if (printJob.isCancelling()) {
+ return NOTIFICATION_CHANNEL_PROGRESS;
+ }
+
+ switch (printJob.getState()) {
+ case PrintJobInfo.STATE_FAILED:
+ case PrintJobInfo.STATE_BLOCKED: {
+ return NOTIFICATION_CHANNEL_FAILURES;
+ }
+ default: {
+ return NOTIFICATION_CHANNEL_PROGRESS;
+ }
+ }
+ }
+
public static final class NotificationBroadcastReceiver extends BroadcastReceiver {
@SuppressWarnings("hiding")
private static final String LOG_TAG = "NotificationBroadcastReceiver";
diff --git a/packages/SettingsLib/Android.mk b/packages/SettingsLib/Android.mk
index 67ef40a..1ad4fea 100644
--- a/packages/SettingsLib/Android.mk
+++ b/packages/SettingsLib/Android.mk
@@ -6,6 +6,7 @@
LOCAL_MODULE := SettingsLib
LOCAL_SHARED_ANDROID_LIBRARIES := \
+ android-support-annotations \
android-support-v4 \
android-support-v7-recyclerview \
android-support-v7-preference \
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f14d0d1..ad985c7 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -928,4 +928,18 @@
[CHAR LIMIT=35] -->
<string name="use_system_language_to_select_input_method_subtypes">Use system languages</string>
+ <!-- Toast that settings for an application is failed to open. -->
+ <string name="failed_to_open_app_settings_toast">Failed to open settings for <xliff:g id="spell_application_name">%1$s</xliff:g></string>
+
+ <!-- Warning message about security implications of enabling an input method, displayed as a dialog
+ message when the user selects to enable an IME. -->
+ <string name="ime_security_warning">This input method may be able to collect
+ all the text you type, including personal data like passwords and credit
+ card numbers. It comes from the app
+ <xliff:g id="ime_application_name">%1$s</xliff:g>.
+ Use this input method?</string>
+
+ <!-- [CHAR LIMIT=NONE] Dialog body explaining that the app just selected by the user will not work after a reboot until until after the user enters their credentials, such as a PIN or password. -->
+ <string name="direct_boot_unaware_dialog_message">Note: After a reboot, this app can\'t start until you unlock your phone</string>
+
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
index 77f2e19..a1c8de5 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -122,9 +122,6 @@
return true;
}
}
- for (BluetoothDevice src : srcs) {
- mService.disconnect(src);
- }
}
return mService.connect(device);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
index 9b699bc..169aac9 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
@@ -132,11 +132,6 @@
return true;
}
}
- // Handsfree HF only supports one source connection and hence it is OK to disconnect
- // the only connected device here.
- for (BluetoothDevice src : srcs) {
- mService.disconnect(src);
- }
}
return mService.connect(device);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
index a7621fc..6efa468 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
@@ -115,10 +115,10 @@
public boolean connect(BluetoothDevice device) {
if (mService == null) return false;
List<BluetoothDevice> connectedDevices = getConnectedDevices();
- if (connectedDevices != null) {
- for (BluetoothDevice connectedDevice : connectedDevices) {
- mService.disconnect(connectedDevice);
- }
+ if (connectedDevices != null && connectedDevices.contains(device)) {
+ // Connect to same device, Ignore it
+ Log.d(TAG,"Ignoring Connect");
+ return true;
}
return mService.connect(device);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
index 72a3b3a..bd37abe 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -144,9 +144,6 @@
return true;
}
}
- for (BluetoothDevice src : srcs) {
- mService.disconnect(src);
- }
}
Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress());
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 4bfca9b..474de90 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -211,7 +211,16 @@
if (preferLongName) {
displayName = getZoneLongName(timeZoneNames, tz, now);
} else {
- displayName = timeZoneNames.getExemplarLocationName(tz.getID());
+ // Canonicalize the zone ID for ICU. It will only return valid strings for zone IDs
+ // that match ICUs zone IDs (which are similar but not guaranteed the same as those
+ // in timezones.xml). timezones.xml and related files uses the IANA IDs. ICU IDs are
+ // stable and IANA IDs have changed over time so they have drifted.
+ // See http://bugs.icu-project.org/trac/ticket/13070 / http://b/36469833.
+ String canonicalZoneId = android.icu.util.TimeZone.getCanonicalID(tz.getID());
+ if (canonicalZoneId == null) {
+ canonicalZoneId = tz.getID();
+ }
+ displayName = timeZoneNames.getExemplarLocationName(canonicalZoneId);
if (displayName == null || displayName.isEmpty()) {
// getZoneExemplarLocation can return null. Fall back to the long name.
displayName = getZoneLongName(timeZoneNames, tz, now);
@@ -325,4 +334,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/PrivateStorageInfo.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/PrivateStorageInfo.java
index 40abb6c..88f133c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/PrivateStorageInfo.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/PrivateStorageInfo.java
@@ -41,8 +41,8 @@
long privateTotalBytes = 0;
for (VolumeInfo info : sm.getVolumes()) {
if (info.getType() == VolumeInfo.TYPE_PRIVATE && info.isMountedReadable()) {
- privateTotalBytes += stats.getTotalBytes(info.getFsUuid());
- privateFreeBytes += stats.getFreeBytes(info.getFsUuid());
+ privateTotalBytes += sm.getTotalBytes(stats, info);
+ privateFreeBytes += sm.getFreeBytes(stats, info);
}
}
return new PrivateStorageInfo(privateFreeBytes, privateTotalBytes);
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageManagerVolumeProvider.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageManagerVolumeProvider.java
index 320494c..11060e6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageManagerVolumeProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageManagerVolumeProvider.java
@@ -16,6 +16,7 @@
package com.android.settingslib.deviceinfo;
+import android.app.usage.StorageStatsManager;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
@@ -46,4 +47,14 @@
public VolumeInfo findEmulatedForPrivate(VolumeInfo privateVolume) {
return mStorageManager.findEmulatedForPrivate(privateVolume);
}
+
+ @Override
+ public long getTotalBytes(StorageStatsManager stats, VolumeInfo volume) {
+ return stats.getTotalBytes(volume.getFsUuid());
+ }
+
+ @Override
+ public long getFreeBytes(StorageStatsManager stats, VolumeInfo volume) {
+ return stats.getFreeBytes(volume.getFsUuid());
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageVolumeProvider.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageVolumeProvider.java
index 646c42f..e5d85d1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageVolumeProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageVolumeProvider.java
@@ -16,6 +16,7 @@
package com.android.settingslib.deviceinfo;
+import android.app.usage.StorageStatsManager;
import android.os.storage.VolumeInfo;
import java.util.List;
@@ -39,4 +40,18 @@
* Returns the emulated volume for a given private volume.
*/
VolumeInfo findEmulatedForPrivate(VolumeInfo privateVolume);
+
+ /**
+ * Returns the total bytes for a given storage volume.
+ *
+ * @pre The volume is a private volume and is readable.
+ */
+ long getTotalBytes(StorageStatsManager stats, VolumeInfo volume);
+
+ /**
+ * Returns the free bytes for a given storage volume.
+ *
+ * @pre The volume is a private volume and is readable.
+ */
+ long getFreeBytes(StorageStatsManager stats, VolumeInfo volume);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
new file mode 100755
index 0000000..1bbc878b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
@@ -0,0 +1,300 @@
+/*
+ * 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.inputmethod;
+
+import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.UserHandle;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
+import android.support.v7.preference.Preference.OnPreferenceClickListener;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.widget.Toast;
+
+import com.android.internal.inputmethod.InputMethodUtils;
+import com.android.settingslib.R;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedSwitchPreference;
+
+import java.text.Collator;
+import java.util.List;
+
+/**
+ * Input method preference.
+ *
+ * This preference represents an IME. It is used for two purposes. 1) An instance with a switch
+ * is used to enable or disable the IME. 2) An instance without a switch is used to invoke the
+ * setting activity of the IME.
+ */
+public class InputMethodPreference extends RestrictedSwitchPreference implements OnPreferenceClickListener,
+ OnPreferenceChangeListener {
+ private static final String TAG = InputMethodPreference.class.getSimpleName();
+ private static final String EMPTY_TEXT = "";
+ private static final int NO_WIDGET = 0;
+
+ public interface OnSavePreferenceListener {
+ /**
+ * Called when this preference needs to be saved its state.
+ *
+ * Note that this preference is non-persistent and needs explicitly to be saved its state.
+ * Because changing one IME state may change other IMEs' state, this is a place to update
+ * other IMEs' state as well.
+ *
+ * @param pref This preference.
+ */
+ void onSaveInputMethodPreference(InputMethodPreference pref);
+ }
+
+ private final InputMethodInfo mImi;
+ private final boolean mHasPriorityInSorting;
+ private final OnSavePreferenceListener mOnSaveListener;
+ private final InputMethodSettingValuesWrapper mInputMethodSettingValues;
+ private final boolean mIsAllowedByOrganization;
+
+ private AlertDialog mDialog = null;
+
+ /**
+ * A preference entry of an input method.
+ *
+ * @param context The Context this is associated with.
+ * @param imi The {@link InputMethodInfo} of this preference.
+ * @param isImeEnabler true if this preference is the IME enabler that has enable/disable
+ * switches for all available IMEs, not the list of enabled IMEs.
+ * @param isAllowedByOrganization false if the IME has been disabled by a device or profile
+ * owner.
+ * @param onSaveListener The listener called when this preference has been changed and needs
+ * to save the state to shared preference.
+ */
+ public InputMethodPreference(final Context context, final InputMethodInfo imi,
+ final boolean isImeEnabler, final boolean isAllowedByOrganization,
+ final OnSavePreferenceListener onSaveListener) {
+ super(context);
+ setPersistent(false);
+ mImi = imi;
+ mIsAllowedByOrganization = isAllowedByOrganization;
+ mOnSaveListener = onSaveListener;
+ if (!isImeEnabler) {
+ // Remove switch widget.
+ setWidgetLayoutResource(NO_WIDGET);
+ }
+ // Disable on/off switch texts.
+ setSwitchTextOn(EMPTY_TEXT);
+ setSwitchTextOff(EMPTY_TEXT);
+ setKey(imi.getId());
+ setTitle(imi.loadLabel(context.getPackageManager()));
+ final String settingsActivity = imi.getSettingsActivity();
+ if (TextUtils.isEmpty(settingsActivity)) {
+ setIntent(null);
+ } else {
+ // Set an intent to invoke settings activity of an input method.
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(imi.getPackageName(), settingsActivity);
+ setIntent(intent);
+ }
+ mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(context);
+ mHasPriorityInSorting = InputMethodUtils.isSystemIme(imi)
+ && mInputMethodSettingValues.isValidSystemNonAuxAsciiCapableIme(imi, context);
+ setOnPreferenceClickListener(this);
+ setOnPreferenceChangeListener(this);
+ }
+
+ public InputMethodInfo getInputMethodInfo() {
+ return mImi;
+ }
+
+ private boolean isImeEnabler() {
+ // If this {@link SwitchPreference} doesn't have a widget layout, we explicitly hide the
+ // switch widget at constructor.
+ return getWidgetLayoutResource() != NO_WIDGET;
+ }
+
+ @Override
+ public boolean onPreferenceChange(final Preference preference, final Object newValue) {
+ // Always returns false to prevent default behavior.
+ // See {@link TwoStatePreference#onClick()}.
+ if (!isImeEnabler()) {
+ // Prevent disabling an IME because this preference is for invoking a settings activity.
+ return false;
+ }
+ if (isChecked()) {
+ // Disable this IME.
+ setCheckedInternal(false);
+ return false;
+ }
+ if (InputMethodUtils.isSystemIme(mImi)) {
+ // Enable a system IME. No need to show a security warning dialog,
+ // but we might need to prompt if it's not Direct Boot aware.
+ // TV doesn't doesn't need to worry about this, but other platforms should show
+ // a warning.
+ if (mImi.getServiceInfo().directBootAware || isTv()) {
+ setCheckedInternal(true);
+ } else if (!isTv()){
+ showDirectBootWarnDialog();
+ }
+ } else {
+ // Once security is confirmed, we might prompt if the IME isn't
+ // Direct Boot aware.
+ showSecurityWarnDialog();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onPreferenceClick(final Preference preference) {
+ // Always returns true to prevent invoking an intent without catching exceptions.
+ // See {@link Preference#performClick(PreferenceScreen)}/
+ if (isImeEnabler()) {
+ // Prevent invoking a settings activity because this preference is for enabling and
+ // disabling an input method.
+ return true;
+ }
+ final Context context = getContext();
+ try {
+ final Intent intent = getIntent();
+ if (intent != null) {
+ // Invoke a settings activity of an input method.
+ context.startActivity(intent);
+ }
+ } catch (final ActivityNotFoundException e) {
+ Log.d(TAG, "IME's Settings Activity Not Found", e);
+ final String message = context.getString(
+ R.string.failed_to_open_app_settings_toast,
+ mImi.loadLabel(context.getPackageManager()));
+ Toast.makeText(context, message, Toast.LENGTH_LONG).show();
+ }
+ return true;
+ }
+
+ public void updatePreferenceViews() {
+ final boolean isAlwaysChecked = mInputMethodSettingValues.isAlwaysCheckedIme(
+ mImi, getContext());
+ // When this preference has a switch and an input method should be always enabled,
+ // this preference should be disabled to prevent accidentally disabling an input method.
+ // This preference should also be disabled in case the admin does not allow this input
+ // method.
+ if (isAlwaysChecked && isImeEnabler()) {
+ setDisabledByAdmin(null);
+ setEnabled(false);
+ } else if (!mIsAllowedByOrganization) {
+ EnforcedAdmin admin =
+ RestrictedLockUtils.checkIfInputMethodDisallowed(getContext(),
+ mImi.getPackageName(), UserHandle.myUserId());
+ setDisabledByAdmin(admin);
+ } else {
+ setEnabled(true);
+ }
+ setChecked(mInputMethodSettingValues.isEnabledImi(mImi));
+ if (!isDisabledByAdmin()) {
+ setSummary(getSummaryString());
+ }
+ }
+
+ private InputMethodManager getInputMethodManager() {
+ return (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ }
+
+ private String getSummaryString() {
+ final InputMethodManager imm = getInputMethodManager();
+ final List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(mImi, true);
+ return InputMethodAndSubtypeUtil.getSubtypeLocaleNameListAsSentence(
+ subtypes, getContext(), mImi);
+ }
+
+ private void setCheckedInternal(boolean checked) {
+ super.setChecked(checked);
+ mOnSaveListener.onSaveInputMethodPreference(InputMethodPreference.this);
+ notifyChanged();
+ }
+
+ private void showSecurityWarnDialog() {
+ if (mDialog != null && mDialog.isShowing()) {
+ mDialog.dismiss();
+ }
+ final Context context = getContext();
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setCancelable(true /* cancelable */);
+ builder.setTitle(android.R.string.dialog_alert_title);
+ final CharSequence label = mImi.getServiceInfo().applicationInfo.loadLabel(
+ context.getPackageManager());
+ builder.setMessage(context.getString(R.string.ime_security_warning, label));
+ builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ // The user confirmed to enable a 3rd party IME, but we might
+ // need to prompt if it's not Direct Boot aware.
+ // TV doesn't doesn't need to worry about this, but other platforms should show
+ // a warning.
+ if (mImi.getServiceInfo().directBootAware || isTv()) {
+ setCheckedInternal(true);
+ } else {
+ showDirectBootWarnDialog();
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
+ // The user canceled to enable a 3rd party IME.
+ setCheckedInternal(false);
+ });
+ mDialog = builder.create();
+ mDialog.show();
+ }
+
+ private boolean isTv() {
+ return (getContext().getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION;
+ }
+
+ private void showDirectBootWarnDialog() {
+ if (mDialog != null && mDialog.isShowing()) {
+ mDialog.dismiss();
+ }
+ final Context context = getContext();
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setCancelable(true /* cancelable */);
+ builder.setMessage(context.getText(R.string.direct_boot_unaware_dialog_message));
+ builder.setPositiveButton(android.R.string.ok, (dialog, which) -> setCheckedInternal(true));
+ builder.setNegativeButton(android.R.string.cancel,
+ (dialog, which) -> setCheckedInternal(false));
+ mDialog = builder.create();
+ mDialog.show();
+ }
+
+ public int compareTo(final InputMethodPreference rhs, final Collator collator) {
+ if (this == rhs) {
+ return 0;
+ }
+ if (mHasPriorityInSorting == rhs.mHasPriorityInSorting) {
+ final CharSequence t0 = getTitle();
+ final CharSequence t1 = rhs.getTitle();
+ if (TextUtils.isEmpty(t0)) {
+ return 1;
+ }
+ if (TextUtils.isEmpty(t1)) {
+ return -1;
+ }
+ return collator.compare(t0.toString(), t1.toString());
+ }
+ // Prefer always checked system IMEs
+ return mHasPriorityInSorting ? -1 : 1;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 1ea4183..901848a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -149,6 +149,7 @@
private int mRankingScore = Integer.MIN_VALUE;
private int mBadge = NetworkBadging.BADGING_NONE;
+ private boolean mIsScoredNetworkMetered = false;
// used to co-relate internal vs returned accesspoint.
int mId;
@@ -248,6 +249,7 @@
this.mScanResultCache.putAll(that.mScanResultCache);
this.mId = that.mId;
this.mBadge = that.mBadge;
+ this.mIsScoredNetworkMetered = that.mIsScoredNetworkMetered;
this.mRankingScore = that.mRankingScore;
}
@@ -336,16 +338,32 @@
builder.append(",level=").append(getLevel());
builder.append(",rankingScore=").append(mRankingScore);
builder.append(",badge=").append(mBadge);
+ builder.append(",metered=").append(isMetered());
return builder.append(')').toString();
}
/**
+ * Updates the AccessPoint rankingScore, metering, and badge, returning true if the data has
+ * changed.
+ *
+ * @param scoreCache The score cache to use to retrieve scores.
+ * @param scoringUiEnabled Whether to show scoring and badging UI.
+ */
+ boolean update(WifiNetworkScoreCache scoreCache, boolean scoringUiEnabled) {
+ boolean scoreChanged = false;
+ if (scoringUiEnabled) {
+ scoreChanged = updateScores(scoreCache);
+ }
+ return updateMetered(scoreCache) || scoreChanged;
+ }
+
+ /**
* Updates the AccessPoint rankingScore and badge, returning true if the data has changed.
*
* @param scoreCache The score cache to use to retrieve scores.
*/
- boolean updateScores(WifiNetworkScoreCache scoreCache) {
+ private boolean updateScores(WifiNetworkScoreCache scoreCache) {
int oldBadge = mBadge;
int oldRankingScore = mRankingScore;
mBadge = NetworkBadging.BADGING_NONE;
@@ -366,6 +384,23 @@
return (oldBadge != mBadge || oldRankingScore != mRankingScore);
}
+ /**
+ * Updates the AccessPoint's metering based on {@link ScoredNetwork#meteredHint}, returning
+ * true if the metering changed.
+ */
+ private boolean updateMetered(WifiNetworkScoreCache scoreCache) {
+ boolean oldMetering = mIsScoredNetworkMetered;
+ mIsScoredNetworkMetered = false;
+ for (ScanResult result : mScanResultCache.values()) {
+ ScoredNetwork score = scoreCache.getScoredNetwork(result);
+ if (score == null) {
+ continue;
+ }
+ mIsScoredNetworkMetered |= score.meteredHint;
+ }
+ return oldMetering == mIsScoredNetworkMetered;
+ }
+
private void evictOldScanResults() {
long nowMs = SystemClock.elapsedRealtime();
for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) {
@@ -474,6 +509,17 @@
mSeen = seen;
}
+ /**
+ * Returns if the network is marked metered. Metering can be marked through its config in
+ * {@link WifiConfiguration}, after connection in {@link WifiInfo}, or from a score config in
+ * {@link ScoredNetwork}.
+ */
+ public boolean isMetered() {
+ return mIsScoredNetworkMetered
+ || (mConfig != null && mConfig.meteredHint)
+ || (mInfo != null && mInfo.getMeteredHint());
+ }
+
public NetworkInfo getNetworkInfo() {
return mNetworkInfo;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index 8f8167e..e82bf81 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -183,7 +183,7 @@
}
if (mAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
mFrictionSld.setState(STATE_SECURED);
- } else if (mAccessPoint.getConfig() != null && mAccessPoint.getConfig().meteredHint) {
+ } else if (mAccessPoint.isMetered()) {
mFrictionSld.setState(STATE_METERED);
}
Drawable drawable = mFrictionSld.getCurrent();
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 50f294c..fc8c42c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -543,11 +543,9 @@
}
}
- if (mNetworkScoringUiEnabled) {
- requestScoresForNetworkKeys(scoresToRequest);
- for (AccessPoint ap : accessPoints) {
- ap.updateScores(mScoreCache);
- }
+ requestScoresForNetworkKeys(scoresToRequest);
+ for (AccessPoint ap : accessPoints) {
+ ap.update(mScoreCache, mNetworkScoringUiEnabled);
}
// Pre-sort accessPoints to speed preference insertion
@@ -648,7 +646,7 @@
if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
reorder = true;
}
- if (mNetworkScoringUiEnabled && ap.updateScores(mScoreCache)) {
+ if (ap.update(mScoreCache, mNetworkScoringUiEnabled)) {
reorder = true;
}
}
@@ -659,15 +657,11 @@
}
/**
- * Update all the internal access points rankingScores and badge.
+ * Update all the internal access points rankingScores, badge and metering.
*
* <p>Will trigger a resort and notify listeners of changes if applicable.
*/
private void updateNetworkScores() {
- if (!mNetworkScoringUiEnabled) {
- return;
- }
-
// Lock required to prevent accidental copying of AccessPoint states while the modification
// is in progress. see #copyAndNotifyListeners
long before = System.currentTimeMillis();
@@ -679,7 +673,7 @@
boolean reorder = false;
for (int i = 0; i < mInternalAccessPoints.size(); i++) {
- if (mInternalAccessPoints.get(i).updateScores(mScoreCache)) {
+ if (mInternalAccessPoints.get(i).update(mScoreCache, mNetworkScoringUiEnabled)) {
reorder = true;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
index d3bdeb7..ab7c6d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
@@ -17,6 +17,7 @@
import android.content.Context;
import android.os.Looper;
+import android.support.annotation.Keep;
/**
* Factory method used to inject WifiTracker instances.
@@ -26,14 +27,7 @@
private static WifiTracker sTestingWifiTracker;
- public static void enableTestingMode() {
- sTestingMode = true;
- }
-
- public static void disableTestingMode() {
- sTestingMode = false;
- }
-
+ @Keep
public static void setTestingWifiTracker(WifiTracker tracker) {
sTestingWifiTracker = tracker;
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 762d9f8..3e01b34 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -17,15 +17,19 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
+import android.net.NetworkKey;
+import android.net.ScoredNetwork;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiNetworkScoreCache;
import android.net.wifi.WifiSsid;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.pps.HomeSp;
@@ -36,10 +40,11 @@
import android.support.test.runner.AndroidJUnit4;
import android.text.SpannableString;
import android.text.style.TtsSpan;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collections;
@@ -50,9 +55,11 @@
private static final String TEST_SSID = "test_ssid";
private Context mContext;
+ @Mock private WifiNetworkScoreCache mWifiNetworkScoreCache;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getTargetContext();
}
@@ -74,6 +81,7 @@
@Test
public void testCopyAccessPoint_dataShouldMatch() {
WifiConfiguration configuration = createWifiConfiguration();
+ configuration.meteredHint = true;
NetworkInfo networkInfo =
new NetworkInfo(ConnectivityManager.TYPE_WIFI, 2, "WIFI", "WIFI_SUBTYPE");
@@ -88,6 +96,7 @@
assertThat(originalAccessPoint.getBssid()).isEqualTo(copy.getBssid());
assertThat(originalAccessPoint.getConfig()).isEqualTo(copy.getConfig());
assertThat(originalAccessPoint.getSecurity()).isEqualTo(copy.getSecurity());
+ assertThat(originalAccessPoint.isMetered()).isEqualTo(copy.isMetered());
assertThat(originalAccessPoint.compareTo(copy) == 0).isTrue();
}
@@ -230,6 +239,55 @@
assertTrue(ap.isPasspointConfig());
}
+ @Test
+ public void testIsMetered_returnTrueWhenWifiConfigurationIsMetered() {
+ WifiConfiguration configuration = createWifiConfiguration();
+ configuration.meteredHint = true;
+
+ NetworkInfo networkInfo =
+ new NetworkInfo(ConnectivityManager.TYPE_WIFI, 2, "WIFI", "WIFI_SUBTYPE");
+ AccessPoint accessPoint = new AccessPoint(mContext, configuration);
+ WifiInfo wifiInfo = new WifiInfo();
+ wifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(configuration.SSID));
+ wifiInfo.setBSSID(configuration.BSSID);
+ wifiInfo.setNetworkId(configuration.networkId);
+ accessPoint.update(configuration, wifiInfo, networkInfo);
+
+ assertTrue(accessPoint.isMetered());
+ };
+
+ @Test
+ public void testIsMetered_returnTrueWhenWifiInfoIsMetered() {
+ WifiConfiguration configuration = createWifiConfiguration();
+
+ NetworkInfo networkInfo =
+ new NetworkInfo(ConnectivityManager.TYPE_WIFI, 2, "WIFI", "WIFI_SUBTYPE");
+ AccessPoint accessPoint = new AccessPoint(mContext, configuration);
+ WifiInfo wifiInfo = new WifiInfo();
+ wifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(configuration.SSID));
+ wifiInfo.setBSSID(configuration.BSSID);
+ wifiInfo.setNetworkId(configuration.networkId);
+ wifiInfo.setMeteredHint(true);
+ accessPoint.update(configuration, wifiInfo, networkInfo);
+
+ assertTrue(accessPoint.isMetered());
+ };
+
+ @Test
+ public void testIsMetered_returnTrueWhenScoredNetworkIsMetered() {
+ AccessPoint ap = createAccessPointWithScanResultCache();
+
+ when(mWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
+ .thenReturn(
+ new ScoredNetwork(
+ null /* NetworkKey */,
+ null /* rssiCurve */,
+ true /* metered */));
+ ap.update(mWifiNetworkScoreCache, false /* scoringUiEnabled */);
+
+ assertTrue(ap.isMetered());
+ };
+
private AccessPoint createAccessPointWithScanResultCache() {
Bundle bundle = new Bundle();
ArrayList<ScanResult> scanResults = new ArrayList<>();
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index 02deb44..b71915f 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
@@ -302,7 +303,7 @@
new ScoredNetwork(
NETWORK_KEY_2,
mockCurve2,
- false /* meteredHint */,
+ true /* meteredHint */,
attr2);
WifiNetworkScoreCache scoreCache = mScoreCacheCaptor.getValue();
@@ -515,6 +516,23 @@
}
@Test
+ public void scoreCacheUpdateMeteredShouldUpdateAccessPointMetering()
+ throws InterruptedException {
+ WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
+ updateScoresAndWaitForAccessPointsChangedCallback();
+
+ List<AccessPoint> aps = tracker.getAccessPoints();
+
+ for (AccessPoint ap : aps) {
+ if (ap.getSsidStr().equals(SSID_1)) {
+ assertFalse(ap.isMetered());
+ } else if (ap.getSsidStr().equals(SSID_2)) {
+ assertTrue(ap.isMetered());
+ }
+ }
+ }
+
+ @Test
public void noBadgesShouldBeInsertedIntoAccessPointWhenScoringUiDisabled()
throws InterruptedException {
Settings.Global.putInt(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 0d0ddf2..169a034 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -994,6 +994,15 @@
return false;
}
+ private PackageInfo getCallingPackageInfo(int userId) {
+ try {
+ return mPackageManager.getPackageInfo(getCallingPackage(),
+ PackageManager.GET_SIGNATURES, userId);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Package " + getCallingPackage() + " doesn't exist");
+ }
+ }
+
private Cursor getAllSecureSettings(int userId, String[] projection) {
if (DEBUG) {
Slog.v(LOG_TAG, "getAllSecureSettings(" + userId + ")");
@@ -1002,6 +1011,13 @@
// Resolve the userId on whose behalf the call is made.
final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId);
+ // The relevant "calling package" userId will be the owning userId for some
+ // profiles, and we can't do the lookup inside our [lock held] loop, so work out
+ // up front who the effective "new SSAID" user ID for that settings name will be.
+ final int ssaidUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId,
+ Settings.Secure.ANDROID_ID);
+ final PackageInfo ssaidCallingPkg = getCallingPackageInfo(ssaidUserId);
+
synchronized (mLock) {
List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_SECURE, callingUserId);
@@ -1026,7 +1042,7 @@
// SETTINGS_FILE_SSAID, unless accessed by a system process.
final Setting setting;
if (isNewSsaidSetting(name)) {
- setting = getSsaidSettingLocked(owningUserId);
+ setting = getSsaidSettingLocked(ssaidCallingPkg, owningUserId);
} else {
setting = mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SECURE, owningUserId,
name);
@@ -1060,14 +1076,17 @@
return settings != null ? settings.getNullSetting() : null;
}
- // Get the value.
- synchronized (mLock) {
- // As of Android O, the SSAID is read from an app-specific entry in table
- // SETTINGS_FILE_SSAID, unless accessed by a system process.
- if (isNewSsaidSetting(name)) {
- return getSsaidSettingLocked(owningUserId);
+ // As of Android O, the SSAID is read from an app-specific entry in table
+ // SETTINGS_FILE_SSAID, unless accessed by a system process.
+ if (isNewSsaidSetting(name)) {
+ PackageInfo callingPkg = getCallingPackageInfo(owningUserId);
+ synchronized (mLock) {
+ return getSsaidSettingLocked(callingPkg, owningUserId);
}
+ }
+ // Not the SSAID; do a straight lookup
+ synchronized (mLock) {
return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SECURE,
owningUserId, name);
}
@@ -1078,7 +1097,7 @@
&& UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID;
}
- private Setting getSsaidSettingLocked(int owningUserId) {
+ private Setting getSsaidSettingLocked(PackageInfo callingPkg, int owningUserId) {
// Get uid of caller (key) used to store ssaid value
String name = Integer.toString(
UserHandle.getUid(owningUserId, UserHandle.getAppId(Binder.getCallingUid())));
@@ -1093,7 +1112,7 @@
// Lazy initialize ssaid if not yet present in ssaid table.
if (ssaid == null || ssaid.isNull() || ssaid.getValue() == null) {
- return mSettingsRegistry.generateSsaidLocked(getCallingPackage(), owningUserId);
+ return mSettingsRegistry.generateSsaidLocked(callingPkg, owningUserId);
}
return ssaid;
@@ -2070,15 +2089,7 @@
return ByteBuffer.allocate(4).putInt(data.length).array();
}
- public Setting generateSsaidLocked(String packageName, int userId) {
- final PackageInfo packageInfo;
- try {
- packageInfo = mPackageManager.getPackageInfo(packageName,
- PackageManager.GET_SIGNATURES, userId);
- } catch (RemoteException e) {
- throw new IllegalStateException("Package info doesn't exist");
- }
-
+ public Setting generateSsaidLocked(PackageInfo callingPkg, int userId) {
// Read the user's key from the ssaid table.
Setting userKeySetting = getSettingLocked(SETTINGS_TYPE_SSAID, userId, SSAID_USER_KEY);
if (userKeySetting == null || userKeySetting.isNull()
@@ -2113,11 +2124,12 @@
}
// Mac the package name and each of the signatures.
- byte[] packageNameBytes = packageInfo.packageName.getBytes(StandardCharsets.UTF_8);
+ final String packageName = callingPkg.packageName;
+ byte[] packageNameBytes = packageName.getBytes(StandardCharsets.UTF_8);
m.update(getLengthPrefix(packageNameBytes), 0, 4);
m.update(packageNameBytes);
- for (int i = 0; i < packageInfo.signatures.length; i++) {
- byte[] sig = packageInfo.signatures[i].toByteArray();
+ for (int i = 0; i < callingPkg.signatures.length; i++) {
+ byte[] sig = callingPkg.signatures[i].toByteArray();
m.update(getLengthPrefix(sig), 0, 4);
m.update(sig);
}
@@ -2127,7 +2139,7 @@
.toLowerCase(Locale.US);
// Save the ssaid in the ssaid table.
- final String uid = Integer.toString(packageInfo.applicationInfo.uid);
+ final String uid = Integer.toString(callingPkg.applicationInfo.uid);
final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID, userId);
final boolean success = ssaidSettings.insertSettingLocked(uid, ssaid, null, true,
packageName);
@@ -2798,7 +2810,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 143;
+ private static final int SETTINGS_VERSION = 144;
private final int mUserId;
@@ -3324,7 +3336,7 @@
}
if (currentVersion == 141) {
- // Version 141: We added the notion of a default and whether the system set
+ // Version 142: We added the notion of a default and whether the system set
// the setting. This is used for resetting the internal state and we need
// to make sure this value is updated for the existing settings, otherwise
// we would delete system set settings while they should stay unmodified.
@@ -3344,7 +3356,7 @@
}
if (currentVersion == 142) {
- // Version 142: Set a default value for Wi-Fi wakeup feature.
+ // Version 143: Set a default value for Wi-Fi wakeup feature.
if (userId == UserHandle.USER_SYSTEM) {
final SettingsState globalSettings = getGlobalSettingsLocked();
Setting currentSetting = globalSettings.getSettingLocked(
@@ -3361,6 +3373,27 @@
currentVersion = 143;
}
+ if (currentVersion == 143) {
+ // Version 144: Set a default value for Autofill service.
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ final Setting currentSetting = secureSettings
+ .getSettingLocked(Settings.Secure.AUTOFILL_SERVICE);
+ if (currentSetting.isNull()) {
+ final String defaultValue = getContext().getResources().getString(
+ com.android.internal.R.string.config_defaultAutofillService);
+ if (defaultValue != null) {
+ Slog.d(LOG_TAG, "Setting [" + defaultValue + "] as Autofill Service "
+ + "for user " + userId);
+ secureSettings.insertSettingLocked(Settings.Secure.AUTOFILL_SERVICE,
+ defaultValue, null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ }
+
+ currentVersion = 144;
+ }
+
+ // vXXX: Add new settings above this point.
+
if (currentVersion != newVersion) {
Slog.wtf("SettingsProvider", "warning: upgrading settings database to version "
+ newVersion + " left it at "
@@ -3372,8 +3405,6 @@
}
}
- // vXXX: Add new settings above this point.
-
// Return the current version.
return currentVersion;
}
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 195eb9b..ff22ffb 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -97,6 +97,15 @@
android:layout_height="wrap_content"
android:text="@string/notification_channel_disabled"
style="@style/TextAppearance.NotificationInfo.Secondary" />
+ <!-- Optional link to app. Only appears if the channel is not disabled -->
+ <TextView
+ android:id="@+id/app_settings"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:ellipsize="end"
+ android:maxLines="1"
+ style="@style/TextAppearance.NotificationInfo.Secondary.Link"/>
</LinearLayout>
<!-- Ban Channel Switch -->
<Switch
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a8cf3da..43aeaa3 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1472,6 +1472,9 @@
<!-- Notification: Control panel: Label for button that launches notification settings. Used
when this app has only defined a single channel for notifications. -->
<string name="notification_more_settings">More settings</string>
+ <!-- Notification: Control panel: Label for a link that launches notification settings in the
+ app that sent the notification. -->
+ <string name="notification_app_settings">Customize: <xliff:g id="sub_category" example="Work chats">%1$s</xliff:g></string>
<!-- Notification: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] -->
<string name="notification_done">Done</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index d6abda6..c9479b8 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -370,6 +370,10 @@
<item name="android:textColor">?android:attr/colorError</item>
</style>
+ <style name="TextAppearance.NotificationInfo.Secondary.Link">
+ <item name="android:textColor">?android:attr/colorAccent</item>
+ </style>
+
<style name="TextAppearance.NotificationInfo.Button">
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textSize">14sp</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 2655837..80b4da8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -34,9 +34,11 @@
import android.widget.TextClock;
import android.widget.TextView;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ChargingView;
+import java.util.Arrays;
import java.util.Locale;
public class KeyguardStatusView extends GridLayout {
@@ -53,6 +55,10 @@
private ViewGroup mClockContainer;
private ChargingView mBatteryDoze;
+ private View[] mVisibleInDoze;
+ private boolean mPulsing;
+ private boolean mDark;
+
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@Override
@@ -117,6 +123,7 @@
mClockView.setShowCurrentUserTime(true);
mOwnerInfo = (TextView) findViewById(R.id.owner_info);
mBatteryDoze = (ChargingView) findViewById(R.id.battery_doze);
+ mVisibleInDoze = new View[]{mBatteryDoze, mClockView};
boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
setEnableMarquee(shouldMarquee);
@@ -273,14 +280,28 @@
}
public void setDark(boolean dark) {
+ mDark = dark;
+
final int N = mClockContainer.getChildCount();
for (int i = 0; i < N; i++) {
View child = mClockContainer.getChildAt(i);
- if (child == mClockView || child == mBatteryDoze) {
+ if (ArrayUtils.contains(mVisibleInDoze, child)) {
continue;
}
child.setAlpha(dark ? 0 : 1);
}
+ updateDozeVisibleViews();
mBatteryDoze.setDark(dark);
}
+
+ public void setPulsing(boolean pulsing) {
+ mPulsing = pulsing;
+ updateDozeVisibleViews();
+ }
+
+ private void updateDozeVisibleViews() {
+ for (View child : mVisibleInDoze) {
+ child.setAlpha(mDark && mPulsing ? 0.5f : 1);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index bcf1957..4e7cf72 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -86,7 +86,10 @@
private static final float DISABLED_ACTION_ALPHA = 0.54f;
private boolean mMenuVisible;
+ private boolean mAllowMenuTimeout = true;
+
private final List<RemoteAction> mActions = new ArrayList<>();
+
private View mViewRoot;
private Drawable mBackgroundDrawable;
private View mMenuContainer;
@@ -190,7 +193,9 @@
@Override
public void onUserInteraction() {
- repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
+ if (mAllowMenuTimeout) {
+ repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
+ }
}
@Override
@@ -255,6 +260,7 @@
}
private void showMenu(Rect stackBounds, Rect movementBounds, boolean allowMenuTimeout) {
+ mAllowMenuTimeout = allowMenuTimeout;
if (!mMenuVisible) {
updateActionViews(stackBounds);
if (mMenuContainerAnimator != null) {
@@ -262,7 +268,6 @@
}
notifyMenuVisibility(true);
updateExpandButtonFromBounds(stackBounds, movementBounds);
- setDecorViewVisibility(true);
mMenuContainerAnimator = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
mMenuContainer.getAlpha(), 1f);
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
@@ -307,11 +312,15 @@
if (animationFinishedRunnable != null) {
animationFinishedRunnable.run();
}
- setDecorViewVisibility(false);
+
+ finish();
}
});
mMenuContainerAnimator.addUpdateListener(mMenuBgUpdateListener);
mMenuContainerAnimator.start();
+ } else {
+ // If the menu is not visible, just finish now
+ finish();
}
}
@@ -427,7 +436,6 @@
alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
}
mBackgroundDrawable.setAlpha(alpha);
- setDecorViewVisibility(alpha > 0);
}
private void notifyRegisterInputConsumer() {
@@ -504,16 +512,4 @@
v.removeCallbacks(mFinishRunnable);
v.postDelayed(mFinishRunnable, delay);
}
-
- /**
- * Sets the visibility of the root view of the window to disable drawing and touches for the
- * activity. This differs from {@link Activity#setVisible(boolean)} in that it does not set
- * the internal mVisibleFromClient state.
- */
- private void setDecorViewVisibility(boolean visible) {
- final View decorView = getWindow().getDecorView();
- if (decorView != null) {
- decorView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index bcaa395..875fb14 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -50,6 +50,7 @@
public class PipMenuActivityController {
private static final String TAG = "PipMenuActController";
+ private static final boolean DEBUG = false;
public static final String EXTRA_CONTROLLER_MESSENGER = "messenger";
public static final String EXTRA_ACTIONS = "actions";
@@ -195,6 +196,10 @@
* Updates the appearance of the menu and scrim on top of the PiP while dismissing.
*/
public void setDismissFraction(float fraction) {
+ if (DEBUG) {
+ Log.d(TAG, "setDismissFraction() hasActivity=" + (mToActivityMessenger != null)
+ + " fraction=" + fraction);
+ }
if (mToActivityMessenger != null) {
mTmpDismissFractionData.clear();
mTmpDismissFractionData.putFloat(EXTRA_DISMISS_FRACTION, fraction);
@@ -216,6 +221,9 @@
* Shows the menu activity.
*/
public void showMenu(Rect stackBounds, Rect movementBounds, boolean allowMenuTimeout) {
+ if (DEBUG) {
+ Log.d(TAG, "showMenu() hasActivity=" + (mToActivityMessenger != null));
+ }
if (mToActivityMessenger != null) {
Bundle data = new Bundle();
data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds);
@@ -238,6 +246,9 @@
* Pokes the menu, indicating that the user is interacting with it.
*/
public void pokeMenu() {
+ if (DEBUG) {
+ Log.d(TAG, "pokeMenu() hasActivity=" + (mToActivityMessenger != null));
+ }
if (mToActivityMessenger != null) {
Message m = Message.obtain();
m.what = PipMenuActivity.MESSAGE_POKE_MENU;
@@ -253,6 +264,9 @@
* Hides the menu activity.
*/
public void hideMenu() {
+ if (DEBUG) {
+ Log.d(TAG, "hideMenu() hasActivity=" + (mToActivityMessenger != null));
+ }
if (mToActivityMessenger != null) {
Message m = Message.obtain();
m.what = PipMenuActivity.MESSAGE_HIDE_MENU;
@@ -365,6 +379,10 @@
* Handles changes in menu visibility.
*/
private void onMenuVisibilityChanged(boolean visible, boolean resize) {
+ if (DEBUG) {
+ Log.d(TAG, "onMenuVisibilityChanged() mMenuVisible=" + mMenuVisible
+ + " menuVisible=" + visible + " resize=" + resize);
+ }
if (visible) {
mInputConsumerController.unregisterInputConsumer();
} else {
@@ -389,6 +407,7 @@
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
pw.println(innerPrefix + "mMenuVisible=" + mMenuVisible);
+ pw.println(innerPrefix + "mToActivityMessenger=" + mToActivityMessenger);
pw.println(innerPrefix + "mListeners=" + mListeners.size());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index a14a712..fb8574d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -23,6 +23,7 @@
import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN;
import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
@@ -253,7 +254,7 @@
* Flings the PiP to the closest snap target.
*/
Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds,
- AnimatorUpdateListener listener) {
+ AnimatorUpdateListener updateListener, AnimatorListener listener) {
cancelAnimations();
Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
velocityX, velocityY);
@@ -263,8 +264,11 @@
mFlingAnimationUtils.apply(mBoundsAnimator, 0,
distanceBetweenRectOffsets(mBounds, toBounds),
velocity);
- if (listener != null) {
- mBoundsAnimator.addUpdateListener(listener);
+ if (updateListener != null) {
+ mBoundsAnimator.addUpdateListener(updateListener);
+ }
+ if (listener != null){
+ mBoundsAnimator.addListener(listener);
}
mBoundsAnimator.start();
}
@@ -274,14 +278,18 @@
/**
* Animates the PiP to the closest snap target.
*/
- Rect animateToClosestSnapTarget(Rect movementBounds, AnimatorUpdateListener listener) {
+ Rect animateToClosestSnapTarget(Rect movementBounds, AnimatorUpdateListener updateListener,
+ AnimatorListener listener) {
cancelAnimations();
Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds);
if (!mBounds.equals(toBounds)) {
mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, SNAP_STACK_DURATION,
FAST_OUT_SLOW_IN, mUpdateBoundsListener);
- if (listener != null) {
- mBoundsAnimator.addUpdateListener(listener);
+ if (updateListener != null) {
+ mBoundsAnimator.addUpdateListener(updateListener);
+ }
+ if (listener != null){
+ mBoundsAnimator.addListener(listener);
}
mBoundsAnimator.start();
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index d68836c..161bdac 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -16,6 +16,8 @@
package com.android.systemui.pip.phone;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.IActivityManager;
@@ -391,7 +393,10 @@
final float distance = bounds.bottom - target;
fraction = Math.min(distance / bounds.height(), 1f);
}
- mMenuController.setDismissFraction(fraction);
+ if (Float.compare(fraction, 0f) != 0 || mMenuController.isMenuVisible()) {
+ // Update if the fraction > 0, or if fraction == 0 and the menu was already visible
+ mMenuController.setDismissFraction(fraction);
+ }
}
}
@@ -611,22 +616,34 @@
setMinimizedStateInternal(false);
}
- // If the menu is still visible, and we aren't minimized, then just poke the menu
- // so that it will timeout after the user stops touching it
+ AnimatorListenerAdapter postAnimationCallback = null;
if (mMenuController.isMenuVisible()) {
+ // If the menu is still visible, and we aren't minimized, then just poke the
+ // menu so that it will timeout after the user stops touching it
mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds,
true /* allowMenuTimeout */);
+ } else {
+ // If the menu is not visible, then we can still be showing the activity for the
+ // dismiss overlay, so just finish it after the animation completes
+ postAnimationCallback = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mMenuController.hideMenu();
+ }
+ };
}
if (isFling) {
mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds,
- mUpdateScrimListener);
+ mUpdateScrimListener, postAnimationCallback);
} else {
- mMotionHelper.animateToClosestSnapTarget(mMovementBounds, mUpdateScrimListener);
+ mMotionHelper.animateToClosestSnapTarget(mMovementBounds, mUpdateScrimListener,
+ postAnimationCallback);
}
} else if (mIsMinimized) {
// This was a tap, so no longer minimized
- mMotionHelper.animateToClosestSnapTarget(mMovementBounds, null /* listener */);
+ mMotionHelper.animateToClosestSnapTarget(mMovementBounds, null /* updateListener */,
+ null /* animatorListener */);
setMinimizedStateInternal(false);
} else if (!mIsMenuVisible) {
mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 9efe224..d2a2919 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -242,7 +242,7 @@
mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
mDetailSettingsButton.setOnClickListener(v -> {
Dependency.get(MetricsLogger.class).action(ACTION_QS_MORE_SETTINGS,
- mDetailAdapter.getMetricsCategory());
+ adapter.getMetricsCategory());
Dependency.get(ActivityStarter.class)
.postStartActivityDismissingKeyguard(settingsIntent, 0);
});
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index dc666e9..311f8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -524,7 +524,7 @@
* changes.
*/
public void onTaskDataLoaded() {
- if (mTask.icon != null) {
+ if (mTask != null && mTask.icon != null) {
mIconView.setImageDrawable(mTask.icon);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 21a0dc9..b4b1cd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -16,45 +16,34 @@
package com.android.systemui.statusbar;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+
import android.app.INotificationManager;
+import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
-import android.app.NotificationManager;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
+import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
-import android.os.Handler;
import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
-import android.view.ViewAnimationUtils;
-import android.view.ViewGroup;
-import android.view.View.OnClickListener;
-import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
-import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.NotificationGuts.OnSettingsClickListener;
-import com.android.systemui.statusbar.stack.StackStateAnimator;
import java.lang.IllegalArgumentException;
import java.util.List;
@@ -71,12 +60,16 @@
private int mAppUid;
private List<NotificationChannel> mNotificationChannels;
private NotificationChannel mSingleNotificationChannel;
+ private StatusBarNotification mSbn;
private int mStartingUserImportance;
private TextView mNumChannelsView;
private View mChannelDisabledView;
+ private TextView mSettingsLinkView;
private Switch mChannelEnabledSwitch;
private CheckSaveListener mCheckSaveListener;
+ private OnAppSettingsClickListener mAppSettingsClickListener;
+ private PackageManager mPm;
private NotificationGuts mGutsContainer;
@@ -95,11 +88,18 @@
void onClick(View v, NotificationChannel channel, int appUid);
}
+ public interface OnAppSettingsClickListener {
+ void onClick(View v, Intent intent);
+ }
+
public void bindNotification(final PackageManager pm,
final INotificationManager iNotificationManager,
final String pkg,
final List<NotificationChannel> notificationChannels,
+ int startingUserImportance,
+ final StatusBarNotification sbn,
OnSettingsClickListener onSettingsClick,
+ OnAppSettingsClickListener onAppSettingsClick,
OnClickListener onDoneClick,
CheckSaveListener checkSaveListener,
final Set<String> nonBlockablePkgs)
@@ -108,16 +108,21 @@
mPkg = pkg;
mNotificationChannels = notificationChannels;
mCheckSaveListener = checkSaveListener;
+ mSbn = sbn;
+ mPm = pm;
+ mAppSettingsClickListener = onAppSettingsClick;
boolean isSingleDefaultChannel = false;
+ mStartingUserImportance = startingUserImportance;
if (mNotificationChannels.isEmpty()) {
throw new IllegalArgumentException("bindNotification requires at least one channel");
- } else if (mNotificationChannels.size() == 1) {
- mSingleNotificationChannel = mNotificationChannels.get(0);
- mStartingUserImportance = mSingleNotificationChannel.getImportance();
- isSingleDefaultChannel = mSingleNotificationChannel.getId()
- .equals(NotificationChannel.DEFAULT_CHANNEL_ID);
- } else {
- mSingleNotificationChannel = null;
+ } else {
+ if (mNotificationChannels.size() == 1) {
+ mSingleNotificationChannel = mNotificationChannels.get(0);
+ isSingleDefaultChannel = mSingleNotificationChannel.getId()
+ .equals(NotificationChannel.DEFAULT_CHANNEL_ID);
+ } else {
+ mSingleNotificationChannel = null;
+ }
}
String appName = mPkg;
@@ -228,6 +233,7 @@
// Settings button.
final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
if (mAppUid >= 0 && onSettingsClick != null) {
+ settingsButton.setVisibility(View.VISIBLE);
final int appUidF = mAppUid;
settingsButton.setOnClickListener(
(View view) -> {
@@ -247,6 +253,9 @@
final TextView doneButton = (TextView) findViewById(R.id.done);
doneButton.setText(R.string.notification_done);
doneButton.setOnClickListener(onDoneClick);
+
+ // Optional settings link
+ updateAppSettingsLink();
}
private boolean hasImportanceChanged() {
@@ -275,7 +284,7 @@
private int getSelectedImportance() {
if (!mChannelEnabledSwitch.isChecked()) {
- return NotificationManager.IMPORTANCE_NONE;
+ return IMPORTANCE_NONE;
} else {
return mStartingUserImportance;
}
@@ -285,7 +294,7 @@
// Enabled Switch
mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
mChannelEnabledSwitch.setChecked(
- mStartingUserImportance != NotificationManager.IMPORTANCE_NONE);
+ mStartingUserImportance != IMPORTANCE_NONE);
final boolean visible = !nonBlockable && mSingleNotificationChannel != null;
mChannelEnabledSwitch.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
@@ -295,12 +304,13 @@
mGutsContainer.resetFalsingCheck();
}
updateSecondaryText();
+ updateAppSettingsLink();
});
}
private void updateSecondaryText() {
final boolean disabled = mSingleNotificationChannel != null &&
- getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
+ getSelectedImportance() == IMPORTANCE_NONE;
if (disabled) {
mChannelDisabledView.setVisibility(View.VISIBLE);
mNumChannelsView.setVisibility(View.GONE);
@@ -310,6 +320,45 @@
}
}
+ private void updateAppSettingsLink() {
+ mSettingsLinkView = findViewById(R.id.app_settings);
+ Intent settingsIntent = getAppSettingsIntent(mPm, mPkg, mSingleNotificationChannel,
+ mSbn.getId(), mSbn.getTag());
+ if (settingsIntent != null && getSelectedImportance() != IMPORTANCE_NONE
+ && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
+ mSettingsLinkView.setVisibility(View.VISIBLE);
+ mSettingsLinkView.setText(mContext.getString(R.string.notification_app_settings,
+ mSbn.getNotification().getSettingsText()));
+ mSettingsLinkView.setOnClickListener((View view) -> {
+ mAppSettingsClickListener.onClick(view, settingsIntent);
+ });
+ } else {
+ mSettingsLinkView.setVisibility(View.GONE);
+ }
+ }
+
+ private Intent getAppSettingsIntent(PackageManager pm, String packageName,
+ NotificationChannel channel, int id, String tag) {
+ Intent intent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
+ .setPackage(packageName);
+ final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
+ intent,
+ PackageManager.MATCH_DEFAULT_ONLY
+ );
+ if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
+ return null;
+ }
+ final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
+ intent.setClassName(activityInfo.packageName, activityInfo.name);
+ if (channel != null) {
+ intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
+ }
+ intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
+ intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
+ return intent;
+ }
+
@Override
public void setGutsParent(NotificationGuts guts) {
mGutsContainer = guts;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
index 5055dda..bb82b60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -120,6 +120,9 @@
@Override
public void createMenu(ViewGroup parent) {
mParent = (ExpandableNotificationRow) parent;
+ if (mMenuContainer != null) {
+ mMenuContainer.removeAllViews();
+ }
mMenuContainer = new FrameLayout(mContext);
for (int i = 0; i < mMenuItems.size(); i++) {
addMenuView(mMenuItems.get(i), mMenuContainer);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 8da17fa..715dc82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -23,7 +23,6 @@
import android.view.View;
import android.view.ViewGroup;
-import com.android.internal.widget.CachingIconView;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
@@ -127,12 +126,8 @@
super.setDark(dark, fade, delay);
if (mDark == dark) return;
mDark = dark;
- if (fade) {
- mViewInvertHelper.fade(dark, delay);
- } else {
- mViewInvertHelper.update(dark);
- }
- mShelfIcons.setAmbient(dark);
+ mShelfIcons.setDark(dark, fade, delay);
+ updateInteractiveness();
}
@Override
@@ -167,7 +162,7 @@
openedAmount = Math.min(1.0f, openedAmount);
mShelfState.openedAmount = openedAmount;
mShelfState.clipTopAmount = 0;
- mShelfState.alpha = 1.0f;
+ mShelfState.alpha = mAmbientState.isPulsing() ? 0 : 1;
mShelfState.belowSpeedBump = mAmbientState.getSpeedBumpIndex() == 0;
mShelfState.shadowAlpha = 1.0f;
mShelfState.hideSensitive = false;
@@ -439,7 +434,8 @@
iconState.scaleY = 1.0f;
iconState.hidden = false;
}
- if (row.isAboveShelf() || (!row.isInShelf() && isLastChild && row.areGutsExposed())) {
+ if (row.isAboveShelf() || (!row.isInShelf() && (isLastChild && row.areGutsExposed()
+ || row.getTranslationZ() > mAmbientState.getBaseZHeight()))) {
iconState.hidden = true;
}
int shelfColor = icon.getStaticDrawableColor();
@@ -576,7 +572,8 @@
}
private void updateInteractiveness() {
- mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf;
+ mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf
+ && !mDark;
setClickable(mInteractive);
setFocusable(mInteractive);
setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
@@ -602,6 +599,11 @@
}
@Override
+ public boolean hasOverlappingRendering() {
+ return false; // Shelf only uses alpha for transitions where the difference can't be seen.
+ }
+
+ @Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
updateRelativeOffset();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index b9c8a78..92bfae9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -27,6 +27,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -46,6 +47,7 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
import com.android.systemui.statusbar.notification.NotificationUtils;
import java.text.NumberFormat;
@@ -99,7 +101,6 @@
private int mDensity;
private float mIconScale = 1.0f;
private final Paint mDotPaint = new Paint();
- private boolean mDotVisible;
private float mDotRadius;
private int mStaticDotRadius;
private int mVisibleState = STATE_ICON;
@@ -110,6 +111,8 @@
private OnVisibilityChangedListener mOnVisibilityChangedListener;
private int mDrawableColor;
private int mIconColor;
+ private int mDecorColor;
+ private float mDarkAmount;
private ValueAnimator mColorAnimator;
private int mCurrentSetColor = NO_COLOR;
private int mAnimationStartColor = NO_COLOR;
@@ -119,6 +122,7 @@
animation.getAnimatedFraction());
setColorInternal(newColor);
};
+ private final NotificationIconDozeHelper mDozer;
public StatusBarIconView(Context context, String slot, Notification notification) {
this(context, slot, notification, false);
@@ -127,6 +131,7 @@
public StatusBarIconView(Context context, String slot, Notification notification,
boolean blocked) {
super(context);
+ mDozer = new NotificationIconDozeHelper(context);
mBlocked = blocked;
mSlot = slot;
mNumberPain = new Paint();
@@ -190,6 +195,7 @@
public StatusBarIconView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mDozer = new NotificationIconDozeHelper(context);
mBlocked = false;
mAlwaysScaleIcon = true;
updateIconScale();
@@ -466,7 +472,19 @@
* to the drawable.
*/
public void setDecorColor(int iconTint) {
- mDotPaint.setColor(iconTint);
+ mDecorColor = iconTint;
+ updateDecorColor();
+ }
+
+ private void updateDecorColor() {
+ int color = NotificationUtils.interpolateColors(mDecorColor, Color.WHITE, mDarkAmount);
+ if (mDotPaint.getColor() != color) {
+ mDotPaint.setColor(color);
+
+ if (mDotAppearAmount != 0) {
+ invalidate();
+ }
+ }
}
/**
@@ -477,6 +495,7 @@
mDrawableColor = color;
setColorInternal(color);
mIconColor = color;
+ mDozer.setColor(color);
}
private void setColorInternal(int color) {
@@ -649,6 +668,14 @@
mOnVisibilityChangedListener = listener;
}
+ public void setDark(boolean dark, boolean fade, long delay) {
+ mDozer.setImageDark(this, dark, fade, delay, mIconColor == NO_COLOR);
+ mDozer.setIntensityDark(f -> {
+ mDarkAmount = f;
+ updateDecorColor();
+ }, dark, fade, delay);
+ }
+
public interface OnVisibilityChangedListener {
void onVisibilityChanged(int newVisibility);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index 3efa29f..bca4b43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -18,7 +18,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
+import android.content.Context;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.view.View;
@@ -38,8 +38,8 @@
private boolean mIsLegacy;
private int mLegacyColor;
- protected NotificationCustomViewWrapper(View view, ExpandableNotificationRow row) {
- super(view, row);
+ protected NotificationCustomViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
+ super(ctx, view, row);
mInvertHelper = new ViewInvertHelper(view, NotificationPanelView.DOZE_ANIMATION_DURATION);
mLegacyColor = row.getContext().getColor(R.color.notification_legacy_background_color);
}
@@ -67,13 +67,11 @@
}
protected void fadeGrayscale(final boolean dark, long delay) {
- startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- updateGrayscaleMatrix((float) animation.getAnimatedValue());
- mGreyPaint.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
- mView.setLayerPaint(mGreyPaint);
- }
+ getDozer().startIntensityAnimation(animation -> {
+ getDozer().updateGrayscaleMatrix((float) animation.getAnimatedValue());
+ mGreyPaint.setColorFilter(
+ new ColorMatrixColorFilter(getDozer().getGrayscaleColorMatrix()));
+ mView.setLayerPaint(mGreyPaint);
}, dark, delay, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -86,9 +84,9 @@
protected void updateGrayscale(boolean dark) {
if (dark) {
- updateGrayscaleMatrix(1f);
+ getDozer().updateGrayscaleMatrix(1f);
mGreyPaint.setColorFilter(
- new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+ new ColorMatrixColorFilter(getDozer().getGrayscaleColorMatrix()));
mView.setLayerPaint(mGreyPaint);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
new file mode 100644
index 0000000..d592c5f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
@@ -0,0 +1,92 @@
+/*
+ * 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.notification;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.widget.ImageView;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+
+import java.util.function.Consumer;
+
+public class NotificationDozeHelper {
+ private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix();
+
+ public void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ updateGrayscaleMatrix((float) animation.getAnimatedValue());
+ target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+ }
+ }, dark, delay, new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!dark) {
+ target.setColorFilter(null);
+ }
+ }
+ });
+ }
+
+ public void updateGrayscale(ImageView target, boolean dark) {
+ if (dark) {
+ updateGrayscaleMatrix(1f);
+ target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+ } else {
+ target.setColorFilter(null);
+ }
+ }
+
+ public void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
+ boolean dark, long delay, Animator.AnimatorListener listener) {
+ float startIntensity = dark ? 0f : 1f;
+ float endIntensity = dark ? 1f : 0f;
+ ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
+ animator.addUpdateListener(updateListener);
+ animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
+ animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ animator.setStartDelay(delay);
+ if (listener != null) {
+ animator.addListener(listener);
+ }
+ animator.start();
+ }
+
+ public void setIntensityDark(Consumer<Float> listener, boolean dark,
+ boolean animate, long delay) {
+ if (animate) {
+ startIntensityAnimation(a -> listener.accept((Float) a.getAnimatedValue()), dark, delay,
+ null /* listener */);
+ } else {
+ listener.accept(dark ? 1f : 0f);
+ }
+ }
+
+ public void updateGrayscaleMatrix(float intensity) {
+ mGrayscaleColorMatrix.setSaturation(1 - intensity);
+ }
+
+ public ColorMatrix getGrayscaleColorMatrix() {
+ return mGrayscaleColorMatrix;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index 38e4ec1..1ffc944 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -16,17 +16,10 @@
package com.android.systemui.statusbar.notification;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.app.Notification;
import android.content.Context;
-import android.graphics.Color;
import android.graphics.ColorFilter;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
-import android.graphics.drawable.Drawable;
import android.util.ArraySet;
import android.view.NotificationHeaderView;
import android.view.View;
@@ -37,7 +30,6 @@
import android.widget.TextView;
import com.android.systemui.Interpolators;
-import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
@@ -55,10 +47,6 @@
private static final Interpolator LOW_PRIORITY_HEADER_CLOSE
= new PathInterpolator(0.4f, 0f, 0.7f, 1f);
- private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
- 0, PorterDuff.Mode.SRC_ATOP);
- private final int mIconDarkAlpha;
- private final int mIconDarkColor = 0xffffffff;
protected final ViewInvertHelper mInvertHelper;
protected final ViewTransformationHelper mTransformationHelper;
@@ -74,8 +62,7 @@
private boolean mTransformLowPriorityTitle;
protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
- super(view, row);
- mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
+ super(ctx, view, row);
mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION);
mTransformationHelper = new ViewTransformationHelper();
@@ -108,6 +95,16 @@
updateInvertHelper();
}
+ @Override
+ protected NotificationDozeHelper createDozer(Context ctx) {
+ return new NotificationIconDozeHelper(ctx);
+ }
+
+ @Override
+ protected NotificationIconDozeHelper getDozer() {
+ return (NotificationIconDozeHelper) super.getDozer();
+ }
+
protected void resolveHeaderViews() {
mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
mHeaderText = (TextView) mView.findViewById(com.android.internal.R.id.header_text);
@@ -116,6 +113,7 @@
mColor = resolveColor(mExpandButton);
mNotificationHeader = (NotificationHeaderView) mView.findViewById(
com.android.internal.R.id.notification_header);
+ getDozer().setColor(mColor);
}
private int resolveColor(ImageView icon) {
@@ -223,90 +221,8 @@
// It also may lead to bugs where the icon isn't correctly greyed out.
boolean hadColorFilter = mNotificationHeader.getOriginalIconColor()
!= NotificationHeaderView.NO_COLOR;
- if (fade) {
- if (hadColorFilter) {
- fadeIconColorFilter(mIcon, dark, delay);
- fadeIconAlpha(mIcon, dark, delay);
- } else {
- fadeGrayscale(mIcon, dark, delay);
- }
- } else {
- if (hadColorFilter) {
- updateIconColorFilter(mIcon, dark);
- updateIconAlpha(mIcon, dark);
- } else {
- updateGrayscale(mIcon, dark);
- }
- }
- }
- }
- private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) {
- startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- updateIconColorFilter(target, (Float) animation.getAnimatedValue());
- }
- }, dark, delay, null /* listener */);
- }
-
- private void fadeIconAlpha(final ImageView target, boolean dark, long delay) {
- startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float t = (float) animation.getAnimatedValue();
- target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t));
- }
- }, dark, delay, null /* listener */);
- }
-
- protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
- startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- updateGrayscaleMatrix((float) animation.getAnimatedValue());
- target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
- }
- }, dark, delay, new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (!dark) {
- target.setColorFilter(null);
- }
- }
- });
- }
-
- private void updateIconColorFilter(ImageView target, boolean dark) {
- updateIconColorFilter(target, dark ? 1f : 0f);
- }
-
- private void updateIconColorFilter(ImageView target, float intensity) {
- int color = interpolateColor(mColor, mIconDarkColor, intensity);
- mIconColorFilter.setColor(color);
- Drawable iconDrawable = target.getDrawable();
-
- // Also, the notification might have been modified during the animation, so background
- // might be null here.
- if (iconDrawable != null) {
- Drawable d = iconDrawable.mutate();
- // DrawableContainer ignores the color filter if it's already set, so clear it first to
- // get it set and invalidated properly.
- d.setColorFilter(null);
- d.setColorFilter(mIconColorFilter);
- }
- }
-
- private void updateIconAlpha(ImageView target, boolean dark) {
- target.setImageAlpha(dark ? mIconDarkAlpha : 255);
- }
-
- protected void updateGrayscale(ImageView target, boolean dark) {
- if (dark) {
- updateGrayscaleMatrix(1f);
- target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
- } else {
- target.setColorFilter(null);
+ getDozer().setImageDark(mIcon, dark, fade, delay, !hadColorFilter);
}
}
@@ -316,22 +232,6 @@
mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
}
- private static int interpolateColor(int source, int target, float t) {
- int aSource = Color.alpha(source);
- int rSource = Color.red(source);
- int gSource = Color.green(source);
- int bSource = Color.blue(source);
- int aTarget = Color.alpha(target);
- int rTarget = Color.red(target);
- int gTarget = Color.green(target);
- int bTarget = Color.blue(target);
- return Color.argb(
- (int) (aSource * (1f - t) + aTarget * t),
- (int) (rSource * (1f - t) + rTarget * t),
- (int) (gSource * (1f - t) + gTarget * t),
- (int) (bSource * (1f - t) + bTarget * t));
- }
-
@Override
public NotificationHeaderView getNotificationHeader() {
return mNotificationHeader;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java
new file mode 100644
index 0000000..9f79ef2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java
@@ -0,0 +1,101 @@
+/*
+ * 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.notification;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+
+public class NotificationIconDozeHelper extends NotificationDozeHelper {
+
+ private final int mImageDarkAlpha;
+ private final int mImageDarkColor = 0xffffffff;
+ private final PorterDuffColorFilter mImageColorFilter = new PorterDuffColorFilter(
+ 0, PorterDuff.Mode.SRC_ATOP);
+
+ private int mColor = Color.BLACK;
+
+ public NotificationIconDozeHelper(Context ctx) {
+ mImageDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
+ }
+
+ public void setColor(int color) {
+ mColor = color;
+ }
+
+ public void setImageDark(ImageView target, boolean dark, boolean fade, long delay,
+ boolean useGrayscale) {
+ if (fade) {
+ if (!useGrayscale) {
+ fadeImageColorFilter(target, dark, delay);
+ fadeImageAlpha(target, dark, delay);
+ } else {
+ fadeGrayscale(target, dark, delay);
+ }
+ } else {
+ if (!useGrayscale) {
+ updateImageColorFilter(target, dark);
+ updateImageAlpha(target, dark);
+ } else {
+ updateGrayscale(target, dark);
+ }
+ }
+ }
+
+ private void fadeImageColorFilter(final ImageView target, boolean dark, long delay) {
+ startIntensityAnimation(animation -> {
+ updateImageColorFilter(target, (Float) animation.getAnimatedValue());
+ }, dark, delay, null /* listener */);
+ }
+
+ private void fadeImageAlpha(final ImageView target, boolean dark, long delay) {
+ startIntensityAnimation(animation -> {
+ float t = (float) animation.getAnimatedValue();
+ target.setImageAlpha((int) (255 * (1f - t) + mImageDarkAlpha * t));
+ }, dark, delay, null /* listener */);
+ }
+
+ private void updateImageColorFilter(ImageView target, boolean dark) {
+ updateImageColorFilter(target, dark ? 1f : 0f);
+ }
+
+ private void updateImageColorFilter(ImageView target, float intensity) {
+ int color = NotificationUtils.interpolateColors(mColor, mImageDarkColor, intensity);
+ mImageColorFilter.setColor(color);
+ Drawable imageDrawable = target.getDrawable();
+
+ // Also, the notification might have been modified during the animation, so background
+ // might be null here.
+ if (imageDrawable != null) {
+ Drawable d = imageDrawable.mutate();
+ // DrawableContainer ignores the color filter if it's already set, so clear it first to
+ // get it set and invalidated properly.
+ d.setColorFilter(null);
+ d.setColorFilter(mImageColorFilter);
+ }
+ }
+
+ private void updateImageAlpha(ImageView target, boolean dark) {
+ target.setImageAlpha(dark ? mImageDarkAlpha : 255);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index 846d03a..f0b6b2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.service.notification.StatusBarNotification;
@@ -46,7 +45,8 @@
private int mContentHeight;
private int mMinHeightHint;
- protected NotificationTemplateViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
+ protected NotificationTemplateViewWrapper(Context ctx, View view,
+ ExpandableNotificationRow row) {
super(ctx, view, row);
mTransformationHelper.setCustomTransformation(
new ViewTransformationHelper.CustomTransformation() {
@@ -154,16 +154,20 @@
// This also clears the existing types
super.updateTransformedTypes();
if (mTitle != null) {
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, mTitle);
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
+ mTitle);
}
if (mText != null) {
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT, mText);
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
+ mText);
}
if (mPicture != null) {
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE, mPicture);
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE,
+ mPicture);
}
if (mProgressBar != null) {
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS, mProgressBar);
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS,
+ mProgressBar);
}
}
@@ -173,7 +177,7 @@
return;
}
super.setDark(dark, fade, delay);
- setPictureGrayscale(dark, fade, delay);
+ setPictureDark(dark, fade, delay);
setProgressBarDark(dark, fade, delay);
}
@@ -188,12 +192,9 @@
}
private void fadeProgressDark(final ProgressBar target, final boolean dark, long delay) {
- startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float t = (float) animation.getAnimatedValue();
- updateProgressDark(target, t);
- }
+ getDozer().startIntensityAnimation(animation -> {
+ float t = (float) animation.getAnimatedValue();
+ updateProgressDark(target, t);
}, dark, delay, null /* listener */);
}
@@ -207,13 +208,9 @@
updateProgressDark(target, dark ? 1f : 0f);
}
- protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
+ private void setPictureDark(boolean dark, boolean fade, long delay) {
if (mPicture != null) {
- if (fade) {
- fadeGrayscale(mPicture, grayscale, delay);
- } else {
- updateGrayscale(mPicture, grayscale);
- }
+ getDozer().setImageDark(mPicture, dark, fade, delay, true /* useGrayscale */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index c85e8d8..f4db9a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -16,24 +16,17 @@
package com.android.systemui.statusbar.notification;
-import android.animation.Animator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
-import android.graphics.ColorMatrix;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.service.notification.StatusBarNotification;
import android.support.v4.graphics.ColorUtils;
import android.view.NotificationHeaderView;
import android.view.View;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
/**
* Wraps the actual notification content view; used to implement behaviors which are different for
@@ -41,14 +34,14 @@
*/
public abstract class NotificationViewWrapper implements TransformableView {
- protected final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix();
protected final View mView;
protected final ExpandableNotificationRow mRow;
+ private final NotificationDozeHelper mDozer;
+
protected boolean mDark;
private int mBackgroundColor = 0;
protected boolean mShouldInvertDark;
protected boolean mDarkInitialized = false;
- private boolean mForcedInvisible;
public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) {
if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
@@ -65,13 +58,22 @@
} else if (v instanceof NotificationHeaderView) {
return new NotificationHeaderViewWrapper(ctx, v, row);
} else {
- return new NotificationCustomViewWrapper(v, row);
+ return new NotificationCustomViewWrapper(ctx, v, row);
}
}
- protected NotificationViewWrapper(View view, ExpandableNotificationRow row) {
+ protected NotificationViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
mView = view;
mRow = row;
+ mDozer = createDozer(ctx);
+ }
+
+ protected NotificationDozeHelper createDozer(Context ctx) {
+ return new NotificationDozeHelper();
+ }
+
+ protected NotificationDozeHelper getDozer() {
+ return mDozer;
}
/**
@@ -112,26 +114,6 @@
|| ColorUtils.calculateLuminance(backgroundColor) > 0.5;
}
-
- protected void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
- boolean dark, long delay, Animator.AnimatorListener listener) {
- float startIntensity = dark ? 0f : 1f;
- float endIntensity = dark ? 1f : 0f;
- ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
- animator.addUpdateListener(updateListener);
- animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
- animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- animator.setStartDelay(delay);
- if (listener != null) {
- animator.addListener(listener);
- }
- animator.start();
- }
-
- protected void updateGrayscaleMatrix(float intensity) {
- mGrayscaleColorMatrix.setSaturation(1 - intensity);
- }
-
/**
* Update the appearance of the expand button.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 3706dc8..dee15d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -95,7 +95,7 @@
private int mActualLayoutWidth = NO_VALUE;
private float mActualPaddingEnd = NO_VALUE;
private float mActualPaddingStart = NO_VALUE;
- private boolean mCentered;
+ private boolean mDark;
private boolean mChangingViewPositions;
private int mAddAnimationStartIndex = -1;
private int mCannedAnimationStartIndex = -1;
@@ -183,6 +183,9 @@
mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
}
}
+ if (mDark && child instanceof StatusBarIconView) {
+ ((StatusBarIconView) child).setDark(mDark, false, 0);
+ }
}
@Override
@@ -312,7 +315,8 @@
numDots++;
}
}
- if (mCentered && translationX < getLayoutEnd()) {
+ boolean center = mDark;
+ if (center && translationX < getLayoutEnd()) {
float delta = (getLayoutEnd() - translationX) / 2;
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
@@ -390,9 +394,15 @@
mChangingViewPositions = changingViewPositions;
}
- public void setAmbient(boolean ambient) {
- mCentered = ambient;
+ public void setDark(boolean dark, boolean fade, long delay) {
+ mDark = dark;
mDisallowNextAnimation = true;
+ for (int i = 0; i < getChildCount(); i++) {
+ View view = getChildAt(i);
+ if (view instanceof StatusBarIconView) {
+ ((StatusBarIconView) view).setDark(dark, fade, delay);
+ }
+ }
}
public IconState getIconState(StatusBarIconView icon) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index c24a2a0..307a8c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -2472,4 +2472,8 @@
public void setNoVisibleNotifications(boolean noNotifications) {
mNoVisibleNotifications = noNotifications;
}
+
+ public void setPulsing(boolean pulsing) {
+ mKeyguardStatusView.setPulsing(pulsing);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 9a71ed7..9304de5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -48,8 +48,10 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -89,7 +91,6 @@
import android.provider.Settings;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
-import android.support.annotation.VisibleForTesting;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
@@ -242,7 +243,6 @@
import com.android.systemui.SwipeHelper;
import com.android.systemui.SystemUI;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.recents.Recents;
import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.statusbar.stack.StackStateAnimator;
@@ -5012,19 +5012,25 @@
@Override
public void onPulseStarted() {
callback.onPulseStarted();
- if (!mHeadsUpManager.getAllEntries().isEmpty()) {
+ Collection<HeadsUpManager.HeadsUpEntry> pulsingEntries =
+ mHeadsUpManager.getAllEntries();
+ if (!pulsingEntries.isEmpty()) {
// Only pulse the stack scroller if there's actually something to show.
// Otherwise just show the always-on screen.
- mStackScroller.setPulsing(true);
- mVisualStabilityManager.setPulsing(true);
+ setPulsing(pulsingEntries);
}
}
@Override
public void onPulseFinished() {
callback.onPulseFinished();
- mStackScroller.setPulsing(false);
- mVisualStabilityManager.setPulsing(false);
+ setPulsing(null);
+ }
+
+ private void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) {
+ mStackScroller.setPulsing(pulsing);
+ mNotificationPanel.setPulsing(pulsing != null);
+ mVisualStabilityManager.setPulsing(pulsing != null);
}
}, reason);
}
@@ -5697,7 +5703,7 @@
.findViewById(com.android.internal.R.id.media_actions) != null;
}
- // The (i) button in the guts that links to the system notification settings for that app
+ // The button in the guts that links to the system notification settings for that app
private void startAppNotificationSettingsActivity(String packageName, final int appUid,
final NotificationChannel channel) {
final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
@@ -5767,16 +5773,27 @@
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
final String pkg = sbn.getPackageName();
NotificationInfo info = (NotificationInfo) gutsView;
- final NotificationInfo.OnSettingsClickListener onSettingsClick = (View v,
- NotificationChannel channel, int appUid) -> {
- mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO);
+ // Settings link is only valid for notifications that specify a user, unless this is the
+ // system user.
+ NotificationInfo.OnSettingsClickListener onSettingsClick = null;
+ if (!userHandle.equals(UserHandle.ALL) || mCurrentUserId == UserHandle.USER_SYSTEM) {
+ onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
+ mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO);
+ guts.resetFalsingCheck();
+ startAppNotificationSettingsActivity(pkg, appUid, channel);
+ };
+ }
+ final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v,
+ Intent intent) -> {
+ mMetricsLogger.action(MetricsEvent.ACTION_APP_NOTE_SETTINGS);
guts.resetFalsingCheck();
- startAppNotificationSettingsActivity(pkg, appUid, channel);
+ startNotificationGutsIntent(intent, sbn.getUid());
};
final View.OnClickListener onDoneClick = (View v) -> {
saveAndCloseNotificationMenu(info, row, guts, v);
};
- final NotificationInfo.CheckSaveListener checkSaveListener = (Runnable saveImportance) -> {
+ final NotificationInfo.CheckSaveListener checkSaveListener =
+ (Runnable saveImportance) -> {
// If the user has security enabled, show challenge if the setting is changed.
if (isLockscreenPublicMode(userHandle.getIdentifier())
&& (mState == StatusBarState.KEYGUARD
@@ -5809,7 +5826,9 @@
}
try {
info.bindNotification(pmUser, iNotificationManager, pkg, new ArrayList(channels),
- onSettingsClick, onDoneClick, checkSaveListener, mNonBlockablePkgs);
+ row.getEntry().channel.getImportance(), sbn, onSettingsClick,
+ onAppSettingsClick, onDoneClick, checkSaveListener,
+ mNonBlockablePkgs);
} catch (RemoteException e) {
Log.e(TAG, e.toString());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index fe83dc4..b2b23a55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -59,6 +59,7 @@
private boolean mPanelTracking;
private boolean mExpansionChanging;
private boolean mPanelFullWidth;
+ private boolean mPulsing;
public AmbientState(Context context) {
reload(context);
@@ -285,6 +286,14 @@
mPanelTracking = panelTracking;
}
+ public boolean isPulsing() {
+ return mPulsing;
+ }
+
+ public void setPulsing(boolean pulsing) {
+ mPulsing = pulsing;
+ }
+
public boolean isPanelTracking() {
return mPanelTracking;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 7d2d0df..15fcb38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -72,6 +72,7 @@
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationGuts;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StackScrollerDecorView;
@@ -86,6 +87,7 @@
import com.android.systemui.statusbar.policy.ScrollAdapter;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
@@ -331,7 +333,7 @@
}
};
private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
- private boolean mPulsing;
+ private Collection<HeadsUpManager.HeadsUpEntry> mPulsing;
private boolean mDrawBackgroundAsSrc;
private boolean mFadingOut;
private boolean mParentNotFullyVisible;
@@ -1917,15 +1919,19 @@
int numShownItems = 0;
boolean finish = false;
int maxDisplayedNotifications = mAmbientState.isDark()
- ? (mPulsing ? 1 : 0)
+ ? (isPulsing() ? 1 : 0)
: mMaxDisplayedNotifications;
for (int i = 0; i < getChildCount(); i++) {
ExpandableView expandableView = (ExpandableView) getChildAt(i);
if (expandableView.getVisibility() != View.GONE
&& !expandableView.hasNoContentHeight()) {
- if (maxDisplayedNotifications != -1
- && numShownItems >= maxDisplayedNotifications) {
+ boolean limitReached = maxDisplayedNotifications != -1
+ && numShownItems >= maxDisplayedNotifications;
+ boolean notificationOnAmbientThatIsNotPulsing = isPulsing()
+ && expandableView instanceof ExpandableNotificationRow
+ && !isPulsing(((ExpandableNotificationRow) expandableView).getEntry());
+ if (limitReached || notificationOnAmbientThatIsNotPulsing) {
expandableView = mShelf;
finish = true;
}
@@ -1971,6 +1977,19 @@
mAmbientState.setLayoutMaxHeight(mContentHeight);
}
+ private boolean isPulsing(NotificationData.Entry entry) {
+ for (HeadsUpManager.HeadsUpEntry e : mPulsing) {
+ if (e.entry == entry) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isPulsing() {
+ return mPulsing != null;
+ }
+
private void updateScrollability() {
boolean scrollable = getScrollRange() > 0;
if (scrollable != mScrollable) {
@@ -2784,7 +2803,7 @@
}
private void updateNotificationAnimationStates() {
- boolean running = mAnimationsEnabled || mPulsing;
+ boolean running = mAnimationsEnabled || isPulsing();
mShelf.setAnimationsEnabled(running);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
@@ -2795,7 +2814,7 @@
}
private void updateAnimationState(View child) {
- updateAnimationState((mAnimationsEnabled || mPulsing)
+ updateAnimationState((mAnimationsEnabled || isPulsing())
&& (mIsExpanded || isPinnedHeadsUp(child)), child);
}
@@ -4055,14 +4074,16 @@
return mIsExpanded;
}
- public void setPulsing(boolean pulsing) {
- if (mPulsing == pulsing) {
+ public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) {
+ if (mPulsing == null && pulsing == null) {
return;
}
mPulsing = pulsing;
+ mAmbientState.setPulsing(isPulsing());
updateNotificationAnimationStates();
updateContentHeight();
notifyHeightChangeListener(mShelf);
+ requestChildrenUpdate();
}
public void setFadingOut(boolean fadingOut) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index c8659fb..5b594be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -190,7 +190,9 @@
view.setScaleY(scaleY);
}
- boolean becomesInvisible = this.alpha == 0.0f || (this.hidden && !isAnimating(view));
+ int oldVisibility = view.getVisibility();
+ boolean becomesInvisible = this.alpha == 0.0f
+ || (this.hidden && (!isAnimating(view) || oldVisibility != View.VISIBLE));
boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA);
if (animatingAlpha) {
updateAlphaAnimation(view);
@@ -212,7 +214,6 @@
}
// apply visibility
- int oldVisibility = view.getVisibility();
int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
if (newVisibility != oldVisibility) {
if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
index c67cccc..8609eeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
@@ -100,4 +100,10 @@
ViewUtils.detachView(mQsDetail);
mTestableLooper.processAllMessages();
}
+
+ @Test
+ public void testNullAdapterClick() {
+ mQsDetail.setupDetailFooter(mock(DetailAdapter.class));
+ mQsDetail.findViewById(android.R.id.button2).performClick();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index 21930a3..2bb7f3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -18,9 +18,9 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
@@ -33,13 +33,18 @@
import static org.mockito.Mockito.when;
import android.app.INotificationManager;
+import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -56,6 +61,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -74,6 +81,7 @@
private final PackageManager mMockPackageManager = mock(PackageManager.class);
private NotificationChannel mNotificationChannel;
private NotificationChannel mDefaultNotificationChannel;
+ private StatusBarNotification mSbn;
@Before
public void setUp() throws Exception {
@@ -101,6 +109,8 @@
mDefaultNotificationChannel = new NotificationChannel(
NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME,
NotificationManager.IMPORTANCE_LOW);
+ mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, 0, 0,
+ new Notification(), UserHandle.CURRENT, null, 0);
}
private CharSequence getStringById(int resId) {
@@ -135,7 +145,9 @@
public void testBindNotification_SetsTextApplicationName() throws Exception {
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, null);
final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.pkgname);
assertTrue(textView.getText().toString().contains("App Name"));
}
@@ -146,7 +158,9 @@
when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class)))
.thenReturn(iconDrawable);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, null);
final ImageView iconView = (ImageView) mNotificationInfo.findViewById(R.id.pkgicon);
assertEquals(iconDrawable, iconView.getDrawable());
}
@@ -154,7 +168,9 @@
@Test
public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, null);
final TextView groupNameView = (TextView) mNotificationInfo.findViewById(R.id.group_name);
assertEquals(View.GONE, groupNameView.getVisibility());
final TextView groupDividerView =
@@ -171,7 +187,9 @@
eq("test_group_id"), eq(TEST_PACKAGE_NAME), anyInt()))
.thenReturn(notificationChannelGroup);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, null);
final TextView groupNameView = (TextView) mNotificationInfo.findViewById(R.id.group_name);
assertEquals(View.VISIBLE, groupNameView.getVisibility());
assertEquals("Test Group Name", groupNameView.getText());
@@ -183,7 +201,9 @@
@Test
public void testBindNotification_SetsTextChannelName() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, null);
final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(TEST_CHANNEL_NAME, textView.getText());
}
@@ -193,10 +213,11 @@
final CountDownLatch latch = new CountDownLatch(1);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn,
(View v, NotificationChannel c, int appUid) -> {
- assertEquals(mNotificationChannel, c);
- latch.countDown();
- }, null, null, null);
+ assertEquals(mNotificationChannel, c);
+ latch.countDown();
+ }, null, null, null, null);
final TextView settingsButton =
(TextView) mNotificationInfo.findViewById(R.id.more_settings);
@@ -206,15 +227,40 @@
}
@Test
+ public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() throws Exception {
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null, null, null);
+ final TextView settingsButton =
+ (TextView) mNotificationInfo.findViewById(R.id.more_settings);
+ assertTrue(settingsButton.getVisibility() != View.VISIBLE);
+ }
+
+ @Test
+ public void testBindNotification_SettingsButtonReappersAfterSecondBind() throws Exception {
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null, null, null);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn,
+ (View v, NotificationChannel c, int appUid) -> {}, null, null, null, null);
+ final TextView settingsButton =
+ (TextView) mNotificationInfo.findViewById(R.id.more_settings);
+ assertEquals(View.VISIBLE, settingsButton.getVisibility());
+ }
+
+ @Test
public void testOnClickListenerPassesNullChannelForBundle() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME,
Arrays.asList(mNotificationChannel, mDefaultNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn,
(View v, NotificationChannel c, int appUid) -> {
- assertEquals(null, c);
- latch.countDown();
- }, null, null, null);
+ assertEquals(null, c);
+ latch.countDown();
+ }, null, null, null, null);
final TextView settingsButton =
(TextView) mNotificationInfo.findViewById(R.id.more_settings);
@@ -227,7 +273,9 @@
public void testBindNotification_SettingsTextWithOneChannel() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
- (View v, NotificationChannel c, int appUid) -> {}, null, null, null);
+ mNotificationChannel.getImportance(), mSbn,
+ (View v, NotificationChannel c, int appUid) -> {
+ }, null, null, null, null);
final TextView settingsButton =
(TextView) mNotificationInfo.findViewById(R.id.more_settings);
assertEquals(getStringById(R.string.notification_more_settings), settingsButton.getText());
@@ -239,7 +287,9 @@
eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(2);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
- (View v, NotificationChannel c, int appUid) -> {}, null, null, null);
+ mNotificationChannel.getImportance(), mSbn,
+ (View v, NotificationChannel c, int appUid) -> {
+ }, null, null, null, null);
final TextView settingsButton =
(TextView) mNotificationInfo.findViewById(R.id.more_settings);
assertEquals(getStringById(R.string.notification_all_categories), settingsButton.getText());
@@ -249,8 +299,11 @@
public void testBindNotification_SetsOnClickListenerForDone() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null,
- (View v) -> { latch.countDown(); },
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null,
+ null, (View v) -> {
+ latch.countDown();
+ },
null, null);
final TextView doneButton = (TextView) mNotificationInfo.findViewById(R.id.done);
@@ -263,7 +316,8 @@
public void testBindNotification_NumChannelsTextUniqueWhenDefaultChannel() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, Arrays.asList(mDefaultNotificationChannel),
- null, null, null, null);
+ mNotificationChannel.getImportance(), mSbn, null, null,
+ null, null, null);
final TextView numChannelsView =
(TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
assertEquals(View.VISIBLE, numChannelsView.getVisibility());
@@ -275,7 +329,9 @@
public void testBindNotification_NumChannelsTextDisplaysWhenNotDefaultChannel()
throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, null);
final TextView numChannelsView =
(TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
assertEquals(numChannelsView.getVisibility(), View.VISIBLE);
@@ -288,7 +344,9 @@
when(mMockINotificationManager.getNumNotificationChannelsForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(2);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, null);
final TextView numChannelsView =
(TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
assertEquals(getNumChannelsDescString(2), numChannelsView.getText());
@@ -300,7 +358,7 @@
throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel),
- null, null, null, null);
+ mNotificationChannel.getImportance(), mSbn, null, null, null, null, null);
final TextView numChannelsView =
(TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
assertEquals(getChannelsListDescString(mNotificationChannel, mDefaultNotificationChannel),
@@ -316,7 +374,7 @@
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME,
Arrays.asList(mNotificationChannel, mDefaultNotificationChannel, thirdChannel),
- null, null, null, null);
+ mNotificationChannel.getImportance(), mSbn, null, null, null, null, null);
final TextView numChannelsView =
(TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
assertEquals(
@@ -336,8 +394,8 @@
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME,
Arrays.asList(mNotificationChannel, mDefaultNotificationChannel, thirdChannel,
- fourthChannel),
- null, null, null, null);
+ fourthChannel), mNotificationChannel.getImportance(), mSbn, null, null,
+ null, null, null);
final TextView numChannelsView =
(TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
assertEquals(
@@ -352,7 +410,7 @@
throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel),
- null, null, null, null);
+ mNotificationChannel.getImportance(), mSbn, null, null, null, null, null);
final TextView channelNameView =
(TextView) mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(getNumChannelsString(2), channelNameView.getText());
@@ -363,7 +421,7 @@
public void testEnabledSwitchInvisibleIfBundleFromDifferentChannels() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel),
- null, null, null, null);
+ mNotificationChannel.getImportance(), mSbn, null, null, null, null, null);
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertEquals(View.INVISIBLE, enabledSwitch.getVisibility());
}
@@ -371,7 +429,8 @@
@Test
public void testbindNotification_ChannelDisabledTextGoneWhenNotDisabled() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null, null, null);
final TextView channelDisabledView =
(TextView) mNotificationInfo.findViewById(R.id.channel_disabled);
assertEquals(channelDisabledView.getVisibility(), View.GONE);
@@ -381,7 +440,9 @@
public void testbindNotification_ChannelDisabledTextVisibleWhenDisabled() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_NONE);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, null);
final TextView channelDisabledView =
(TextView) mNotificationInfo.findViewById(R.id.channel_disabled);
assertEquals(channelDisabledView.getVisibility(), View.VISIBLE);
@@ -398,7 +459,8 @@
mDefaultNotificationChannel.setImportance(NotificationManager.IMPORTANCE_NONE);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, Arrays.asList(mDefaultNotificationChannel),
- null, null, null, null);
+ mDefaultNotificationChannel.getImportance(), mSbn, null, null,
+ null, null, null);
final TextView channelDisabledView =
(TextView) mNotificationInfo.findViewById(R.id.channel_disabled);
assertEquals(View.VISIBLE, channelDisabledView.getVisibility());
@@ -407,7 +469,9 @@
@Test
public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, null);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -416,7 +480,9 @@
public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, null);
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
@@ -428,7 +494,9 @@
public void testHandleCloseControls_DoesNotUpdateNotificationChannelIfUnchanged()
throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, null);
mNotificationInfo.handleCloseControls(true);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
@@ -440,7 +508,9 @@
throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_UNSPECIFIED);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, null);
mNotificationInfo.handleCloseControls(true);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
@@ -451,7 +521,9 @@
public void testEnabledSwitchOnByDefault() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, null);
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertTrue(enabledSwitch.isChecked());
@@ -461,7 +533,9 @@
public void testEnabledButtonOffWhenAlreadyBanned() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_NONE);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, null);
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertFalse(enabledSwitch.isChecked());
@@ -471,7 +545,9 @@
public void testEnabledSwitchVisibleByDefault() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, null);
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertEquals(View.VISIBLE, enabledSwitch.getVisibility());
@@ -481,8 +557,9 @@
public void testEnabledSwitchInvisibleIfNonBlockable() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null,
- Collections.singleton(TEST_PACKAGE_NAME));
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, Collections.singleton(TEST_PACKAGE_NAME));
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertEquals(View.INVISIBLE, enabledSwitch.getVisibility());
@@ -492,8 +569,9 @@
public void testNonBlockableAppDoesNotBecomeBlocked() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null,
- Collections.singleton(TEST_PACKAGE_NAME));
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, Collections.singleton(TEST_PACKAGE_NAME));
mNotificationInfo.handleCloseControls(true);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
@@ -503,8 +581,9 @@
public void testEnabledSwitchChangedCallsUpdateNotificationChannel() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null,
- Collections.singleton(TEST_PACKAGE_NAME));
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, Collections.singleton(TEST_PACKAGE_NAME));
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
@@ -517,8 +596,9 @@
public void testCloseControlsDoesNotUpdateIfSaveIsFalse() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null,
- Collections.singleton(TEST_PACKAGE_NAME));
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, Collections.singleton(TEST_PACKAGE_NAME));
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
@@ -531,8 +611,10 @@
public void testCloseControlsDoesNotUpdateIfCheckSaveListenerIsNoOp() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null,
- (Runnable saveImportance) -> {},
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ (Runnable saveImportance) -> {
+ },
Collections.singleton(TEST_PACKAGE_NAME));
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
@@ -546,8 +628,11 @@
public void testCloseControlsUpdatesWhenCheckSaveListenerUsesCallback() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null,
- (Runnable saveImportance) -> { saveImportance.run(); },
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ (Runnable saveImportance) -> {
+ saveImportance.run();
+ },
Collections.singleton(TEST_PACKAGE_NAME));
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
@@ -556,4 +641,137 @@
verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
}
+
+ @Test
+ public void testDisplaySettingsLink() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final String settingsText = "work chats";
+ final ResolveInfo ri = new ResolveInfo();
+ ri.activityInfo = new ActivityInfo();
+ ri.activityInfo.packageName = TEST_PACKAGE_NAME;
+ ri.activityInfo.name = "something";
+ List<ResolveInfo> ris = new ArrayList<>();
+ ris.add(ri);
+ when(mMockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(ris);
+ mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ Notification n = new Notification.Builder(mContext, mNotificationChannel.getId())
+ .setSettingsText(settingsText).build();
+ StatusBarNotification sbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME,
+ 0, null, 0, 0, n, UserHandle.CURRENT, null, 0);
+
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), sbn, null,
+ (View v, Intent intent) -> {
+ latch.countDown();
+ }, null, null, null);
+ final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings);
+ assertEquals(View.VISIBLE, settingsLink.getVisibility());
+ assertTrue(settingsLink.getText().toString().length() > settingsText.length());
+ assertTrue(settingsLink.getText().toString().contains(settingsText));
+ settingsLink.performClick();
+ assertEquals(0, latch.getCount());
+ }
+
+ @Test
+ public void testDisplaySettingsLink_multipleChannels() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final String settingsText = "work chats";
+ final ResolveInfo ri = new ResolveInfo();
+ ri.activityInfo = new ActivityInfo();
+ ri.activityInfo.packageName = TEST_PACKAGE_NAME;
+ ri.activityInfo.name = "something";
+ List<ResolveInfo> ris = new ArrayList<>();
+ ris.add(ri);
+ when(mMockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(ris);
+ mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ Notification n = new Notification.Builder(mContext, mNotificationChannel.getId())
+ .setSettingsText(settingsText).build();
+ StatusBarNotification sbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME,
+ 0, null, 0, 0, n, UserHandle.CURRENT, null, 0);
+
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel),
+ mNotificationChannel.getImportance(), sbn, null, (View v, Intent intent) -> {
+ latch.countDown();
+ }, null, null, null);
+ final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings);
+ assertEquals(View.VISIBLE, settingsLink.getVisibility());
+ settingsLink.performClick();
+ assertEquals(0, latch.getCount());
+ }
+
+ @Test
+ public void testNoSettingsLink_noHandlingActivity() throws Exception {
+ final String settingsText = "work chats";
+ when(mMockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(null);
+ mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ Notification n = new Notification.Builder(mContext, mNotificationChannel.getId())
+ .setSettingsText(settingsText).build();
+ StatusBarNotification sbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME,
+ 0, null, 0, 0, n, UserHandle.CURRENT, null, 0);
+
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), sbn, null, null, null,
+ null, null);
+ final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings);
+ assertEquals(View.GONE, settingsLink.getVisibility());
+ }
+
+ @Test
+ public void testNoSettingsLink_noLinkText() throws Exception {
+ final ResolveInfo ri = new ResolveInfo();
+ ri.activityInfo = new ActivityInfo();
+ ri.activityInfo.packageName = TEST_PACKAGE_NAME;
+ ri.activityInfo.name = "something";
+ List<ResolveInfo> ris = new ArrayList<>();
+ ris.add(ri);
+ when(mMockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(ris);
+ mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ Notification n = new Notification.Builder(mContext, mNotificationChannel.getId()).build();
+ StatusBarNotification sbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME,
+ 0, null, 0, 0, n, UserHandle.CURRENT, null, 0);
+
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), sbn, null, null, null,
+ null, null);
+ final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings);
+ assertEquals(View.GONE, settingsLink.getVisibility());
+ }
+
+ @Test
+ public void testNoSettingsLink_afterBlockingChannel() throws Exception {
+ final String settingsText = "work chats";
+ final ResolveInfo ri = new ResolveInfo();
+ ri.activityInfo = new ActivityInfo();
+ ri.activityInfo.packageName = TEST_PACKAGE_NAME;
+ ri.activityInfo.name = "something";
+ List<ResolveInfo> ris = new ArrayList<>();
+ ris.add(ri);
+ when(mMockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(ris);
+ mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ Notification n = new Notification.Builder(mContext, mNotificationChannel.getId())
+ .setSettingsText(settingsText).build();
+ StatusBarNotification sbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME,
+ 0, null, 0, 0, n, UserHandle.CURRENT, null, 0);
+
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), sbn, null, null, null,
+ null, null);
+ final TextView settingsLink = mNotificationInfo.findViewById(R.id.app_settings);
+ assertEquals(View.VISIBLE, settingsLink.getVisibility());
+
+ // Block channel
+ Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
+ enabledSwitch.setChecked(false);
+
+ assertEquals(View.GONE, settingsLink.getVisibility());
+
+ //unblock
+ enabledSwitch.setChecked(true);
+ assertEquals(View.VISIBLE, settingsLink.getVisibility());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
index 31b9bae..efb9fea4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
@@ -44,4 +44,11 @@
ViewUtils.detachView(row.getMenuView());
TestableLooper.get(this).processAllMessages();
}
+
+ @Test
+ public void testRecreateMenu() {
+ NotificationMenuRowPlugin row = new NotificationMenuRow(mContext);
+ row.createMenu(null);
+ row.createMenu(null);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java
new file mode 100644
index 0000000..a69de7a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.notification;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class NotificationViewWrapperTest {
+
+ private Context mContext;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ }
+
+ @Test
+ public void constructor_doesntUseViewContext() throws Exception {
+ new TestableNotificationViewWrapper(mContext, new View(null /* context */), null /* row */);
+ }
+
+ static class TestableNotificationViewWrapper extends NotificationViewWrapper {
+ protected TestableNotificationViewWrapper(Context ctx, View view,
+ ExpandableNotificationRow row) {
+ super(ctx, view, row);
+ }
+ }
+}
\ No newline at end of file
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index c532efb..0acbb02 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -1282,7 +1282,9 @@
ANativeWindow *anw = nullptr;
if (sur != 0) {
+ // Connect the native window handle to buffer queue.
anw = ANativeWindow_fromSurface(_env, sur);
+ native_window_api_connect(anw, NATIVE_WINDOW_API_CPU);
}
rsAllocationSetSurface((RsContext)con, (RsAllocation)alloc, anw);
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 0482e73..ac81565 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -1631,7 +1631,7 @@
@Override
public ParceledListSlice<AppWidgetProviderInfo> getInstalledProvidersForProfile(int categoryFilter,
- int profileId) {
+ int profileId, String packageName) {
final int userId = UserHandle.getCallingUserId();
if (DEBUG) {
@@ -1653,8 +1653,11 @@
Provider provider = mProviders.get(i);
AppWidgetProviderInfo info = provider.info;
- // Ignore an invalid provider or one not matching the filter.
- if (provider.zombie || (info.widgetCategory & categoryFilter) == 0) {
+ // Ignore an invalid provider, one not matching the filter,
+ // or one that isn't in the given package, if any.
+ boolean inPackage = packageName == null
+ || provider.id.componentName.getPackageName().equals(packageName);
+ if (provider.zombie || (info.widgetCategory & categoryFilter) == 0 || !inPackage) {
continue;
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index be14440..502b5fc 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -23,7 +23,6 @@
import static com.android.server.autofill.Helper.VERBOSE;
import static com.android.server.autofill.Helper.bundleToString;
-import android.Manifest;
import android.annotation.NonNull;
import android.app.ActivityManagerInternal;
import android.content.BroadcastReceiver;
@@ -36,7 +35,6 @@
import android.database.ContentObserver;
import android.graphics.Rect;
import android.net.Uri;
-import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 3d1c251..c8a5780 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -57,6 +57,7 @@
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.IResultReceiver;
@@ -147,7 +148,7 @@
// TODO(b/33197203): since service is fetching the data (to use for save later),
// we should optimize what's sent (for example, remove layout containers,
// color / font info, etc...)
- session.mStructure = structure;
+ session.setStructureLocked(structure);
}
@@ -193,13 +194,17 @@
}
}
+ private String getComponentNameFromSettings() {
+ return Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
+ }
+
void updateLocked(boolean disabled) {
final boolean wasEnabled = isEnabled();
mDisabled = disabled;
ComponentName serviceComponent = null;
ServiceInfo serviceInfo = null;
- final String componentName = Settings.Secure.getStringForUser(
- mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
+ final String componentName = getComponentNameFromSettings();
if (!TextUtils.isEmpty(componentName)) {
try {
serviceComponent = ComponentName.unflattenFromString(componentName);
@@ -290,7 +295,8 @@
+ " f=" + flags;
mRequestsHistory.log(historyItem);
- // TODO(b/33197203): Handle partitioning
+ // TODO(b/33197203): Handle scenario when user forced autofill after app was already
+ // autofilled.
final Session session = mSessions.get(activityToken);
if (session != null) {
// Already started...
@@ -411,8 +417,7 @@
void disableSelf() {
final long identity = Binder.clearCallingIdentity();
try {
- final String autoFillService = Settings.Secure.getStringForUser(
- mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
+ final String autoFillService = getComponentNameFromSettings();
if (mInfo.getServiceInfo().getComponentName().equals(
ComponentName.unflattenFromString(autoFillService))) {
Settings.Secure.putStringForUser(mContext.getContentResolver(),
@@ -430,9 +435,14 @@
void dumpLocked(String prefix, PrintWriter pw) {
final String prefix2 = prefix + " ";
- pw.print(prefix); pw.print("Component:"); pw.println(mInfo != null
+ pw.print(prefix); pw.print("User: "); pw.println(mUserId);
+ pw.print(prefix); pw.print("Component: "); pw.println(mInfo != null
? mInfo.getServiceInfo().getComponentName() : null);
- pw.print(prefix); pw.print("Disabled:"); pw.println(mDisabled);
+ pw.print(prefix); pw.print("Component from settings: ");
+ pw.println(getComponentNameFromSettings());
+ pw.print(prefix); pw.print("Default component: ");
+ pw.println(mContext.getString(R.string.config_defaultAutofillService));
+ pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
if (VERBOSE && mInfo != null) {
// ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps)
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index ac7d19e..a3da50e 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -29,6 +29,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.AutofillOverlay;
import android.app.assist.AssistStructure.ViewNode;
import android.app.assist.AssistStructure.WindowNode;
import android.content.ComponentName;
@@ -48,6 +49,7 @@
import android.service.autofill.FillResponse;
import android.service.autofill.SaveInfo;
import android.util.ArrayMap;
+import android.util.DebugUtils;
import android.util.Slog;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
@@ -99,7 +101,7 @@
@NonNull private final String mPackageName;
@GuardedBy("mLock")
- private final Map<AutofillId, ViewState> mViewStates = new ArrayMap<>();
+ private final ArrayMap<AutofillId, ViewState> mViewStates = new ArrayMap<>();
/**
* Id of the View currently being displayed.
@@ -112,27 +114,27 @@
@GuardedBy("mLock")
RemoteFillService mRemoteFillService;
- // TODO(b/33197203 , b/35707731): Use List once it supports partitioning
@GuardedBy("mLock")
- private FillResponse mCurrentResponse;
+ private ArrayList<FillResponse> mResponses;
/**
- * Used to remember which {@link Dataset} filled the session.
+ * Response that requires a service authentitcation request.
*/
- // TODO(b/33197203 , b/35707731): will be removed once it supports partitioning
@GuardedBy("mLock")
- private Dataset mAutoFilledDataset;
+ private FillResponse mResponseWaitingAuth;
/**
* Dataset that when tapped launched a service authentication request.
*/
+ @GuardedBy("mLock")
private Dataset mDatasetWaitingAuth;
/**
* Assist structure sent by the app; it will be updated (sanitized, change values for save)
* before sent to {@link AutofillService}.
*/
- @GuardedBy("mLock") AssistStructure mStructure;
+ @GuardedBy("mLock")
+ private AssistStructure mStructure;
/**
* Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}.
@@ -140,6 +142,13 @@
private boolean mHasCallback;
/**
+ * Extras sent by service on {@code onFillRequest()} calls; the first non-null extra is saved
+ * and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls.
+ */
+ @GuardedBy("mLock")
+ private Bundle mExtras;
+
+ /**
* Flags used to start the session.
*/
int mFlags;
@@ -163,8 +172,8 @@
mClient = IAutoFillManagerClient.Stub.asInterface(client);
try {
client.linkToDeath(() -> {
- if (DEBUG) {
- Slog.d(TAG, "app binder died");
+ if (VERBOSE) {
+ Slog.v(TAG, "app binder died");
}
removeSelf();
@@ -193,6 +202,10 @@
notifyUnavailableToClient();
}
synchronized (mLock) {
+ if (response.getAuthentication() != null) {
+ // TODO(b/33197203 , b/35707731): make sure it's ignored if there is one already
+ mResponseWaitingAuth = response;
+ }
processResponseLocked(response);
}
@@ -318,23 +331,27 @@
}
public void setAuthenticationResultLocked(Bundle data) {
- if (mCurrentResponse == null || data == null) {
+ if ((mResponseWaitingAuth == null && mDatasetWaitingAuth == null) || data == null) {
removeSelf();
} else {
final Parcelable result = data.getParcelable(
AutofillManager.EXTRA_AUTHENTICATION_RESULT);
if (result instanceof FillResponse) {
mMetricsLogger.action(MetricsEvent.AUTOFILL_AUTHENTICATED, mPackageName);
-
- mCurrentResponse = (FillResponse) result;
- processResponseLocked(mCurrentResponse);
+ mResponseWaitingAuth = null;
+ processResponseLocked((FillResponse) result);
} else if (result instanceof Dataset) {
final Dataset dataset = (Dataset) result;
- final int index = mCurrentResponse.getDatasets().indexOf(mDatasetWaitingAuth);
- if (index >= 0) {
- mCurrentResponse.getDatasets().set(index, dataset);
- autoFill(dataset);
- mDatasetWaitingAuth = null;
+ for (int i = 0; i < mResponses.size(); i++) {
+ final FillResponse response = mResponses.get(i);
+ final int index = response.getDatasets().indexOf(mDatasetWaitingAuth);
+ if (index >= 0) {
+ response.getDatasets().set(index, dataset);
+ mDatasetWaitingAuth = null;
+ autoFill(dataset);
+ resetViewStatesLocked(dataset, ViewState.STATE_WAITING_DATASET_AUTH);
+ return;
+ }
}
}
}
@@ -344,6 +361,10 @@
mHasCallback = hasIt;
}
+ public void setStructureLocked(AssistStructure structure) {
+ mStructure = structure;
+ }
+
/**
* Shows the save UI, when session can be saved.
*
@@ -354,15 +375,19 @@
Slog.wtf(TAG, "showSaveLocked(): no mStructure");
return true;
}
- if (mCurrentResponse == null) {
+ if (mResponses == null) {
// Happens when the activity / session was finished before the service replied, or
// when the service cannot autofill it (and returned a null response).
if (DEBUG) {
- Slog.d(TAG, "showSaveLocked(): no mCurrentResponse");
+ Slog.d(TAG, "showSaveLocked(): no responses on session");
}
return true;
}
- final SaveInfo saveInfo = mCurrentResponse.getSaveInfo();
+
+ // TODO(b/33197203 , b/35707731): must iterate over all responses
+ final FillResponse response = mResponses.get(0);
+
+ final SaveInfo saveInfo = response.getSaveInfo();
if (DEBUG) {
Slog.d(TAG, "showSaveLocked(): saveInfo=" + saveInfo);
}
@@ -385,7 +410,6 @@
return true;
}
- // TODO(b/33197203 , b/35707731): refactor excessive calls to getCurrentValue()
boolean allRequiredAreNotEmpty = true;
boolean atLeastOneChanged = false;
for (int i = 0; i < requiredIds.length; i++) {
@@ -393,7 +417,8 @@
final ViewState viewState = mViewStates.get(id);
if (viewState == null) {
Slog.w(TAG, "showSaveLocked(): no ViewState for required " + id);
- continue;
+ allRequiredAreNotEmpty = false;
+ break;
}
final AutofillValue currentValue = viewState.getCurrentValue();
@@ -462,8 +487,6 @@
Slog.d(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
}
- final Bundle extras = this.mCurrentResponse.getExtras();
-
for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
final AutofillValue value = entry.getValue().getCurrentValue();
if (value == null) {
@@ -493,20 +516,25 @@
mStructure.dump();
}
- mRemoteFillService.onSaveRequest(mStructure, extras);
+ mRemoteFillService.onSaveRequest(mStructure, mExtras);
}
void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int flags) {
- if (mAutoFilledDataset != null && (flags & FLAG_VALUE_CHANGED) == 0) {
- // TODO(b/33197203): ignoring because we don't support partitions yet
- Slog.d(TAG, "updateLocked(): ignoring " + id + " after app was autofilled");
- return;
- }
-
ViewState viewState = mViewStates.get(id);
+
if (viewState == null) {
- viewState = new ViewState(this, id, this, ViewState.STATE_INITIAL);
- mViewStates.put(id, viewState);
+ if ((flags & (FLAG_START_SESSION | FLAG_VALUE_CHANGED)) != 0) {
+ if (DEBUG) {
+ Slog.d(TAG, "Creating viewState for " + id + " on " + getFlagAsString(flags));
+ }
+ viewState = new ViewState(this, id, value, this, ViewState.STATE_INITIAL);
+ mViewStates.put(id, viewState);
+ } else if ((flags & FLAG_VIEW_ENTERED) != 0) {
+ viewState = startPartitionLocked(id, value);
+ } else {
+ if (VERBOSE) Slog.v(TAG, "Ignored " + getFlagAsString(flags) + " for " + id);
+ return;
+ }
}
if ((flags & FLAG_START_SESSION) != 0) {
@@ -530,7 +558,8 @@
}
// Update the internal state...
viewState.setState(ViewState.STATE_CHANGED);
- // ... and the chooser UI.
+
+ //..and the UI
if (value.isText()) {
getUiForShowing().filterFillUi(value.getTextValue().toString());
} else {
@@ -551,10 +580,6 @@
// If the ViewState is ready to be displayed, onReady() will be called.
viewState.update(value, virtualBounds);
- if (mCurrentResponse != null) {
- viewState.setResponse(mCurrentResponse);
- }
-
return;
}
@@ -566,7 +591,41 @@
return;
}
- Slog.w(TAG, "updateLocked(): unknown flags " + flags);
+ Slog.w(TAG, "updateLocked(): unknown flags " + flags + ": " + getFlagAsString(flags));
+ }
+
+ private ViewState startPartitionLocked(AutofillId id, AutofillValue value) {
+ if (DEBUG) {
+ Slog.d(TAG, "Starting partition for view id " + id);
+ }
+ final ViewState newViewState =
+ new ViewState(this, id, value, this,ViewState.STATE_STARTED_PARTITION);
+ mViewStates.put(id, newViewState);
+
+ // Must update value of nodes so:
+ // - proper node is focused
+ // - autofillValue is sent back to service when it was previously autofilled
+ for (int i = 0; i < mViewStates.size(); i++) {
+ final ViewState viewState = mViewStates.valueAt(i);
+
+ final ViewNode node = findViewNodeByIdLocked(viewState.id);
+ if (node == null) {
+ Slog.w(TAG, "startPartitionLocked(): no node for " + viewState.id);
+ continue;
+ }
+
+ final AutofillValue initialValue = viewState.getInitialValue();
+ final AutofillValue filledValue = viewState.getAutofilledValue();
+ final AutofillOverlay overlay = new AutofillOverlay();
+ if (filledValue != null && !filledValue.equals(initialValue)) {
+ overlay.value = filledValue;
+ }
+ overlay.focused = id.equals(viewState.id);
+ node.setAutofillOverlay(overlay);
+ }
+ mRemoteFillService.onFillRequest(mStructure, mExtras, 0);
+
+ return newViewState;
}
@Override
@@ -580,6 +639,10 @@
getUiForShowing().showFillUi(filledId, response, filterText, mPackageName);
}
+ String getFlagAsString(int flag) {
+ return DebugUtils.flagsToString(AutofillManager.class, "FLAG_", flag);
+ }
+
private void notifyUnavailableToClient() {
if (mCurrentViewId == null) {
// TODO(b/33197203): temporary sanity check; should never happen
@@ -597,8 +660,7 @@
private void processResponseLocked(FillResponse response) {
if (DEBUG) {
- Slog.d(TAG, "processResponseLocked(auth=" + response.getAuthentication()
- + "):" + response);
+ Slog.d(TAG, "processResponseLocked(mCurrentViewId=" + mCurrentViewId + "):" + response);
}
if (mCurrentViewId == null) {
@@ -607,7 +669,13 @@
return;
}
- mCurrentResponse = response;
+ if (mResponses == null) {
+ mResponses = new ArrayList<>(4);
+ }
+ mResponses.add(response);
+ if (response != null) {
+ mExtras = response.getExtras();
+ }
setViewStatesLocked(response, ViewState.STATE_FILLABLE);
@@ -653,7 +721,7 @@
if (viewState != null) {
viewState.setState(state);
} else {
- viewState = new ViewState(this, id, this, state);
+ viewState = new ViewState(this, id, null, this, state);
if (DEBUG) { // TODO(b/33197203): change to VERBOSE once stable
Slog.d(TAG, "Adding autofillable view with id " + id + " and state " + state);
}
@@ -669,10 +737,22 @@
}
}
+ /**
+ * Resets the given state from all existing views in the given dataset.
+ */
+ private void resetViewStatesLocked(@NonNull Dataset dataset, int state) {
+ final ArrayList<AutofillId> ids = dataset.getFieldIds();
+ for (int j = 0; j < ids.size(); j++) {
+ final AutofillId id = ids.get(j);
+ final ViewState viewState = mViewStates.get(id);
+ if (viewState != null) {
+ viewState.resetState(state);
+ }
+ }
+ }
+
void autoFill(Dataset dataset) {
synchronized (mLock) {
- mAutoFilledDataset = dataset;
-
// Autofill it directly...
if (dataset.getAuthentication() == null) {
autoFillApp(dataset);
@@ -680,7 +760,9 @@
}
// ...or handle authentication.
+ // TODO(b/33197203 , b/35707731): make sure it's ignored if there is one already
mDatasetWaitingAuth = dataset;
+ setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH);
final Intent fillInIntent = createAuthFillInIntent(mStructure, null);
startAuthentication(dataset.getAuthentication(), fillInIntent);
}
@@ -690,8 +772,8 @@
return mService.getServiceName();
}
- FillResponse getCurrentResponse() {
- return mCurrentResponse;
+ FillResponse getResponseWaitingAuth() {
+ return mResponseWaitingAuth;
}
private Intent createAuthFillInIntent(AssistStructure structure, Bundle extras) {
@@ -714,8 +796,8 @@
void dumpLocked(String prefix, PrintWriter pw) {
pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
pw.print(prefix); pw.print("mFlags: "); pw.println(mFlags);
- pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse);
- pw.print(prefix); pw.print("mAutoFilledDataset: "); pw.println(mAutoFilledDataset);
+ pw.print(prefix); pw.print("mResponses: "); pw.println(mResponses);
+ pw.print(prefix); pw.print("mResponseWaitingAuth: "); pw.println(mResponseWaitingAuth);
pw.print(prefix); pw.print("mDatasetWaitingAuth: "); pw.println(mDatasetWaitingAuth);
pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId);
pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size());
@@ -735,6 +817,7 @@
}
}
pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback);
+ pw.print(prefix); pw.print("mExtras: "); pw.println(Helper.bundleToString(mExtras));
mRemoteFillService.dump(prefix, pw);
}
@@ -811,4 +894,4 @@
destroyLocked();
mService.removeSessionLocked(mActivityToken);
}
-}
\ No newline at end of file
+}
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index 20def0c..f8919ee 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -16,10 +16,13 @@
package com.android.server.autofill;
+import static com.android.server.autofill.Helper.DEBUG;
+
import android.annotation.Nullable;
import android.graphics.Rect;
import android.service.autofill.FillResponse;
import android.util.DebugUtils;
+import android.util.Slog;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
@@ -40,6 +43,8 @@
@Nullable AutofillValue value);
}
+ private static final String TAG = "ViewState";
+
// NOTE: state constants must be public because of flagstoString().
public static final int STATE_UNKNOWN = 0x00;
/** Initial state. */
@@ -52,21 +57,27 @@
public static final int STATE_CHANGED = 0x08;
/** Set only in the View that started a session. */
public static final int STATE_STARTED_SESSION = 0x10;
+ /** View that started a new partition when focused on. */
+ public static final int STATE_STARTED_PARTITION = 0x20;
+ /** User select a dataset in this view, but service must authenticate first. */
+ public static final int STATE_WAITING_DATASET_AUTH = 0x40;
public final AutofillId id;
private final Listener mListener;
private final Session mSession;
private FillResponse mResponse;
+ private AutofillValue mInitialValue;
private AutofillValue mCurrentValue;
private AutofillValue mAutofilledValue;
private Rect mVirtualBounds;
private int mState;
- ViewState(Session session, AutofillId id, Listener listener, int state) {
+ ViewState(Session session, AutofillId id, AutofillValue value, Listener listener, int state) {
mSession = session;
this.id = id;
+ mInitialValue = value;
mListener = listener;
mState = state;
}
@@ -101,6 +112,11 @@
}
@Nullable
+ AutofillValue getInitialValue() {
+ return mInitialValue;
+ }
+
+ @Nullable
FillResponse getResponse() {
return mResponse;
}
@@ -122,9 +138,15 @@
}
void setState(int state) {
- // TODO(b/33197203 , b/35707731): currently it's always setting one state, but once it
- // supports partitioning it will need to 'or' some of them..
- mState = state;
+ if (mState == STATE_INITIAL) {
+ mState = state;
+ } else {
+ mState |= state;
+ }
+ }
+
+ void resetState(int state) {
+ mState &= ~state;
}
// TODO(b/33197203): need to refactor / rename / document this method to make it clear that
@@ -147,6 +169,12 @@
* fill UI is ready to be displayed (i.e. when response and bounds are set).
*/
void maybeCallOnFillReady() {
+ if ((mState & (STATE_AUTOFILLED | STATE_WAITING_DATASET_AUTH)) != 0) {
+ if (DEBUG) {
+ Slog.d(TAG, "Ignoring UI for " + id + " on " + getStateAsString());
+ }
+ return;
+ }
// First try the current response associated with this View.
if (mResponse != null) {
if (mResponse.getDatasets() != null) {
@@ -155,22 +183,24 @@
return;
}
// Then checks if the session has a response waiting authentication; if so, uses it instead.
- final FillResponse currentResponse = mSession.getCurrentResponse();
- if (currentResponse != null && currentResponse.getAuthentication() != null) {
- mListener.onFillReady(currentResponse, this.id, mCurrentValue);
+ final FillResponse responseWaitingAuth = mSession.getResponseWaitingAuth();
+ if (responseWaitingAuth != null) {
+ mListener.onFillReady(responseWaitingAuth, this.id, mCurrentValue);
}
}
@Override
public String toString() {
- return "ViewState: [id=" + id + ", currentValue=" + mCurrentValue
- + ", bounds=" + mVirtualBounds + ", state=" + getStateAsString() +"]";
+ return "ViewState: [id=" + id + ", initialValue=" + mInitialValue
+ + ", currentValue=" + mCurrentValue + ", autofilledValue=" + mAutofilledValue
+ + ", bounds=" + mVirtualBounds + ", state=" + getStateAsString() + "]";
}
void dump(String prefix, PrintWriter pw) {
pw.print(prefix); pw.print("id:" ); pw.println(this.id);
pw.print(prefix); pw.print("state:" ); pw.println(getStateAsString());
pw.print(prefix); pw.print("has response:" ); pw.println(mResponse != null);
+ pw.print(prefix); pw.print("initialValue:" ); pw.println(mInitialValue);
pw.print(prefix); pw.print("currentValue:" ); pw.println(mCurrentValue);
pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue);
pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds);
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index d38fb96..b69d1dc 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -101,8 +101,9 @@
content.measure(widthMeasureSpec, heightMeasureSpec);
content.setOnClickListener(v -> mCallback.onResponsePicked(response));
content.setElevation(context.getResources().getDimension(R.dimen.floating_window_z));
- mContentWidth = content.getMeasuredWidth();
- mContentHeight = content.getMeasuredHeight();
+ // TODO(b/33197203 , b/36660292): temporary limiting maximum height and minimum width
+ mContentWidth = Math.max(content.getMeasuredWidth(), 1000);
+ mContentHeight = Math.min(content.getMeasuredHeight(), 500);
mWindow = new AnchoredWindow(content);
mCallback.requestShowFillUi(mContentWidth, mContentHeight, mWindowPresenter);
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 509351b..2c08afa 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -117,6 +117,12 @@
case SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD:
type = context.getString(R.string.autofill_save_type_credit_card);
break;
+ case SaveInfo.SAVE_DATA_TYPE_USERNAME:
+ type = context.getString(R.string.autofill_save_type_username);
+ break;
+ case SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS:
+ type = context.getString(R.string.autofill_save_type_email_address);
+ break;
default:
type = null;
}
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
new file mode 100644
index 0000000..a7ce95b
--- /dev/null
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -0,0 +1,494 @@
+/*
+ * 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 static android.Manifest.permission.DUMP;
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+import static android.net.IpSecManager.KEY_RESOURCE_ID;
+import static android.net.IpSecManager.KEY_SPI;
+import static android.net.IpSecManager.KEY_STATUS;
+
+import android.content.Context;
+import android.net.IIpSecService;
+import android.net.INetd;
+import android.net.IpSecAlgorithm;
+import android.net.IpSecConfig;
+import android.net.IpSecManager;
+import android.net.IpSecTransform;
+import android.net.util.NetdService;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/** @hide */
+public class IpSecService extends IIpSecService.Stub {
+ private static final String TAG = "IpSecService";
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String NETD_SERVICE_NAME = "netd";
+ private static final int[] DIRECTIONS =
+ new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
+
+ /** Binder context for this service */
+ private final Context mContext;
+
+ private Object mLock = new Object();
+
+ private static final int NETD_FETCH_TIMEOUT = 5000; //ms
+
+ private AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
+
+ private abstract class ManagedResource implements IBinder.DeathRecipient {
+ final int pid;
+ final int uid;
+ private IBinder mBinder;
+
+ ManagedResource(IBinder binder) {
+ super();
+ mBinder = binder;
+ pid = Binder.getCallingPid();
+ uid = Binder.getCallingUid();
+
+ try {
+ mBinder.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ binderDied();
+ }
+ }
+
+ /**
+ * When this record is no longer needed for managing system resources this function should
+ * unlink all references held by the record to allow efficient garbage collection.
+ */
+ public final void release() {
+ //Release all the underlying system resources first
+ releaseResources();
+
+ if (mBinder != null) {
+ mBinder.unlinkToDeath(this, 0);
+ }
+ mBinder = null;
+
+ //remove this record so that it can be cleaned up
+ nullifyRecord();
+ }
+
+ /**
+ * If the Binder object dies, this function is called to free the system resources that are
+ * being managed by this record and to subsequently release this record for garbage
+ * collection
+ */
+ public final void binderDied() {
+ release();
+ }
+
+ /**
+ * Implement this method to release all object references contained in the subclass to allow
+ * efficient garbage collection of the record. This should remove any references to the
+ * record from all other locations that hold a reference as the record is no longer valid.
+ */
+ protected abstract void nullifyRecord();
+
+ /**
+ * Implement this method to release all system resources that are being protected by this
+ * record. Once the resources are released, the record should be invalidated and no longer
+ * used by calling releaseRecord()
+ */
+ protected abstract void releaseResources();
+ };
+
+ private final class TransformRecord extends ManagedResource {
+ private IpSecConfig mConfig;
+ private int mResourceId;
+
+ TransformRecord(IpSecConfig config, int resourceId, IBinder binder) {
+ super(binder);
+ mConfig = config;
+ mResourceId = resourceId;
+ }
+
+ public IpSecConfig getConfig() {
+ return mConfig;
+ }
+
+ @Override
+ protected void releaseResources() {
+ for (int direction : DIRECTIONS) {
+ try {
+ getNetdInstance()
+ .ipSecDeleteSecurityAssociation(
+ mResourceId,
+ direction,
+ (mConfig.getLocalAddress() != null)
+ ? mConfig.getLocalAddress().getHostAddress()
+ : "",
+ (mConfig.getRemoteAddress() != null)
+ ? mConfig.getRemoteAddress().getHostAddress()
+ : "",
+ mConfig.getSpi(direction));
+ } catch (ServiceSpecificException e) {
+ // FIXME: get the error code and throw is at an IOException from Errno Exception
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
+ }
+ }
+ }
+
+ @Override
+ protected void nullifyRecord() {
+ mConfig = null;
+ mResourceId = INVALID_RESOURCE_ID;
+ }
+ }
+
+ private final class SpiRecord extends ManagedResource {
+ private final int mDirection;
+ private final String mLocalAddress;
+ private final String mRemoteAddress;
+ private final IBinder mBinder;
+ private int mSpi;
+ private int mResourceId;
+
+ SpiRecord(
+ int resourceId,
+ int direction,
+ String localAddress,
+ String remoteAddress,
+ int spi,
+ IBinder binder) {
+ super(binder);
+ mResourceId = resourceId;
+ mDirection = direction;
+ mLocalAddress = localAddress;
+ mRemoteAddress = remoteAddress;
+ mSpi = spi;
+ mBinder = binder;
+ }
+
+ protected void releaseResources() {
+ try {
+ getNetdInstance()
+ .ipSecDeleteSecurityAssociation(
+ mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi);
+ } catch (ServiceSpecificException e) {
+ // FIXME: get the error code and throw is at an IOException from Errno Exception
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId);
+ }
+ }
+
+ protected void nullifyRecord() {
+ mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
+ mResourceId = INVALID_RESOURCE_ID;
+ }
+ }
+
+ @GuardedBy("mSpiRecords")
+ private final SparseArray<SpiRecord> mSpiRecords = new SparseArray<>();
+
+ @GuardedBy("mTransformRecords")
+ private final SparseArray<TransformRecord> mTransformRecords = new SparseArray<>();
+
+ /**
+ * Constructs a new IpSecService instance
+ *
+ * @param context Binder context for this service
+ */
+ private IpSecService(Context context) {
+ mContext = context;
+ }
+
+ static IpSecService create(Context context) throws InterruptedException {
+ final IpSecService service = new IpSecService(context);
+ service.connectNativeNetdService();
+ return service;
+ }
+
+ public void systemReady() {
+ if (isNetdAlive()) {
+ Slog.d(TAG, "IpSecService is ready");
+ } else {
+ Slog.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!");
+ }
+ }
+
+ private void connectNativeNetdService() {
+ // Avoid blocking the system server to do this
+ Thread t =
+ new Thread(
+ new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ NetdService.get(NETD_FETCH_TIMEOUT);
+ }
+ }
+ });
+ t.run();
+ }
+
+ INetd getNetdInstance() throws RemoteException {
+ final INetd netd = NetdService.getInstance();
+ if (netd == null) {
+ throw new RemoteException("Failed to Get Netd Instance");
+ }
+ return netd;
+ }
+
+ boolean isNetdAlive() {
+ synchronized (mLock) {
+ try {
+ final INetd netd = getNetdInstance();
+ if (netd == null) {
+ return false;
+ }
+ return netd.isAlive();
+ } catch (RemoteException re) {
+ return false;
+ }
+ }
+ }
+
+ @Override
+ /** Get a new SPI and maintain the reservation in the system server */
+ public Bundle reserveSecurityParameterIndex(
+ int direction, String remoteAddress, int requestedSpi, IBinder binder)
+ throws RemoteException {
+ int resourceId = mNextResourceId.getAndIncrement();
+
+ int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
+ String localAddress = "";
+ Bundle retBundle = new Bundle(3);
+ try {
+ spi =
+ getNetdInstance()
+ .ipSecAllocateSpi(
+ resourceId,
+ direction,
+ localAddress,
+ remoteAddress,
+ requestedSpi);
+ Log.d(TAG, "Allocated SPI " + spi);
+ retBundle.putInt(KEY_STATUS, IpSecManager.Status.OK);
+ retBundle.putInt(KEY_RESOURCE_ID, resourceId);
+ retBundle.putInt(KEY_SPI, spi);
+ synchronized (mSpiRecords) {
+ mSpiRecords.put(
+ resourceId,
+ new SpiRecord(
+ resourceId, direction, localAddress, remoteAddress, spi, binder));
+ }
+ } catch (ServiceSpecificException e) {
+ // TODO: Add appropriate checks when other ServiceSpecificException types are supported
+ retBundle.putInt(KEY_STATUS, IpSecManager.Status.SPI_UNAVAILABLE);
+ retBundle.putInt(KEY_RESOURCE_ID, resourceId);
+ retBundle.putInt(KEY_SPI, spi);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return retBundle;
+ }
+
+ /** Release a previously allocated SPI that has been registered with the system server */
+ @Override
+ public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {}
+
+ /**
+ * Open a socket via the system server and bind it to the specified port (random if port=0).
+ * This will return a PFD to the user that represent a bound UDP socket. The system server will
+ * cache the socket and a record of its owner so that it can and must be freed when no longer
+ * needed.
+ */
+ @Override
+ public Bundle openUdpEncapsulationSocket(int port, IBinder binder) throws RemoteException {
+ return null;
+ }
+
+ /** close a socket that has been been allocated by and registered with the system server */
+ @Override
+ public void closeUdpEncapsulationSocket(ParcelFileDescriptor socket) {}
+
+ /**
+ * Create a transport mode transform, which represent two security associations (one in each
+ * direction) in the kernel. The transform will be cached by the system server and must be freed
+ * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
+ * that are using it, which will result in all of those sockets becoming unable to send or
+ * receive data.
+ */
+ @Override
+ public Bundle createTransportModeTransform(IpSecConfig c, IBinder binder)
+ throws RemoteException {
+ // TODO: Basic input validation here since it's coming over the Binder
+ int resourceId = mNextResourceId.getAndIncrement();
+ for (int direction : DIRECTIONS) {
+ IpSecAlgorithm auth = c.getAuthentication(direction);
+ IpSecAlgorithm crypt = c.getEncryption(direction);
+ try {
+ int result =
+ getNetdInstance()
+ .ipSecAddSecurityAssociation(
+ resourceId,
+ c.getMode(),
+ direction,
+ (c.getLocalAddress() != null)
+ ? c.getLocalAddress().getHostAddress()
+ : "",
+ (c.getRemoteAddress() != null)
+ ? c.getRemoteAddress().getHostAddress()
+ : "",
+ (c.getNetwork() != null)
+ ? c.getNetwork().getNetworkHandle()
+ : 0,
+ c.getSpi(direction),
+ (auth != null) ? auth.getName() : "",
+ (auth != null) ? auth.getKey() : null,
+ (auth != null) ? auth.getTruncationLengthBits() : 0,
+ (crypt != null) ? crypt.getName() : "",
+ (crypt != null) ? crypt.getKey() : null,
+ (crypt != null) ? crypt.getTruncationLengthBits() : 0,
+ c.getEncapType(),
+ c.getEncapLocalPort(),
+ c.getEncapRemotePort());
+ if (result != c.getSpi(direction)) {
+ // TODO: cleanup the first SA if creation of second SA fails
+ Bundle retBundle = new Bundle(2);
+ retBundle.putInt(KEY_STATUS, IpSecManager.Status.SPI_UNAVAILABLE);
+ retBundle.putInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID);
+ return retBundle;
+ }
+ } catch (ServiceSpecificException e) {
+ // FIXME: get the error code and throw is at an IOException from Errno Exception
+ }
+ }
+ synchronized (mTransformRecords) {
+ mTransformRecords.put(resourceId, new TransformRecord(c, resourceId, binder));
+ }
+
+ Bundle retBundle = new Bundle(2);
+ retBundle.putInt(KEY_STATUS, IpSecManager.Status.OK);
+ retBundle.putInt(KEY_RESOURCE_ID, resourceId);
+ return retBundle;
+ }
+
+ /**
+ * Delete a transport mode transform that was previously allocated by + registered with the
+ * system server. If this is called on an inactive (or non-existent) transform, it will not
+ * return an error. It's safe to de-allocate transforms that may have already been deleted for
+ * other reasons.
+ */
+ @Override
+ public void deleteTransportModeTransform(int resourceId) throws RemoteException {
+ synchronized (mTransformRecords) {
+ TransformRecord record;
+ // We want to non-destructively get so that we can check credentials before removing
+ // this from the records.
+ record = mTransformRecords.get(resourceId);
+
+ if (record == null) {
+ throw new IllegalArgumentException(
+ "Transform " + resourceId + " is not available to be deleted");
+ }
+
+ if (record.pid != Binder.getCallingPid() || record.uid != Binder.getCallingUid()) {
+ throw new SecurityException("Only the owner of an IpSec Transform may delete it!");
+ }
+
+ // TODO: if releaseResources() throws RemoteException, we can try again to clean up on
+ // binder death. Need to make sure that path is actually functional.
+ record.releaseResources();
+ mTransformRecords.remove(resourceId);
+ record.nullifyRecord();
+ }
+ }
+
+ /**
+ * Apply an active transport mode transform to a socket, which will apply the IPsec security
+ * association as a correspondent policy to the provided socket
+ */
+ @Override
+ public void applyTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
+ throws RemoteException {
+
+ synchronized (mTransformRecords) {
+ TransformRecord info;
+ // FIXME: this code should be factored out into a security check + getter
+ info = mTransformRecords.get(resourceId);
+
+ if (info == null) {
+ throw new IllegalArgumentException("Transform " + resourceId + " is not active");
+ }
+
+ // TODO: make this a function.
+ if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
+ throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
+ }
+
+ IpSecConfig c = info.getConfig();
+ try {
+ for (int direction : DIRECTIONS) {
+ getNetdInstance()
+ .ipSecApplyTransportModeTransform(
+ socket.getFileDescriptor(),
+ resourceId,
+ direction,
+ (c.getLocalAddress() != null)
+ ? c.getLocalAddress().getHostAddress()
+ : "",
+ (c.getRemoteAddress() != null)
+ ? c.getRemoteAddress().getHostAddress()
+ : "",
+ c.getSpi(direction));
+ }
+ } catch (ServiceSpecificException e) {
+ // FIXME: get the error code and throw is at an IOException from Errno Exception
+ }
+ }
+ }
+ /**
+ * Remove a transport mode transform from a socket, applying the default (empty) policy. This
+ * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of
+ * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not
+ * used: reserved for future improved input validation.
+ */
+ @Override
+ public void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
+ throws RemoteException {
+ try {
+ getNetdInstance().ipSecRemoveTransportModeTransform(socket.getFileDescriptor());
+ } catch (ServiceSpecificException e) {
+ // FIXME: get the error code and throw is at an IOException from Errno Exception
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(DUMP, TAG);
+
+ pw.println("IpSecService Log:");
+ pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
+ pw.println();
+ }
+}
diff --git a/services/core/java/com/android/server/NetworkScorerAppManager.java b/services/core/java/com/android/server/NetworkScorerAppManager.java
index e127eb9..8404025 100644
--- a/services/core/java/com/android/server/NetworkScorerAppManager.java
+++ b/services/core/java/com/android/server/NetworkScorerAppManager.java
@@ -42,6 +42,7 @@
*
* @hide
*/
+@VisibleForTesting
public class NetworkScorerAppManager {
private static final String TAG = "NetworkScorerAppManager";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -63,7 +64,8 @@
* Returns the list of available scorer apps. The list will be empty if there are
* no valid scorers.
*/
- List<NetworkScorerAppData> getAllValidScorers() {
+ @VisibleForTesting
+ public List<NetworkScorerAppData> getAllValidScorers() {
if (VERBOSE) Log.v(TAG, "getAllValidScorers()");
final PackageManager pm = mContext.getPackageManager();
final Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
@@ -168,7 +170,8 @@
* it was disabled or uninstalled).
*/
@Nullable
- NetworkScorerAppData getActiveScorer() {
+ @VisibleForTesting
+ public NetworkScorerAppData getActiveScorer() {
final int enabledSetting = getNetworkRecommendationsEnabledSetting();
if (enabledSetting == NetworkScoreManager.RECOMMENDATIONS_ENABLED_FORCED_OFF) {
return null;
@@ -211,7 +214,8 @@
* @return true if the scorer was changed, or false if the package is not a valid scorer or
* a valid network recommendation provider exists.
*/
- boolean setActiveScorer(String packageName) {
+ @VisibleForTesting
+ public boolean setActiveScorer(String packageName) {
final String oldPackageName = getNetworkRecommendationsPackage();
if (TextUtils.equals(oldPackageName, packageName)) {
@@ -246,7 +250,8 @@
* is no longer valid then {@link Settings.Global#NETWORK_RECOMMENDATIONS_ENABLED} will be set
* to <code>0</code> (disabled).
*/
- void updateState() {
+ @VisibleForTesting
+ public void updateState() {
final int enabledSetting = getNetworkRecommendationsEnabledSetting();
if (enabledSetting == NetworkScoreManager.RECOMMENDATIONS_ENABLED_FORCED_OFF) {
// Don't change anything if it's forced off.
@@ -284,7 +289,8 @@
/**
* Migrates the NETWORK_SCORER_APP Setting to the USE_OPEN_WIFI_PACKAGE Setting.
*/
- void migrateNetworkScorerAppSettingIfNeeded() {
+ @VisibleForTesting
+ public void migrateNetworkScorerAppSettingIfNeeded() {
final String scorerAppPkgNameSetting =
mSettingsFacade.getString(mContext, Settings.Global.NETWORK_SCORER_APP);
if (TextUtils.isEmpty(scorerAppPkgNameSetting)) {
diff --git a/services/core/java/com/android/server/SensorNotificationService.java b/services/core/java/com/android/server/SensorNotificationService.java
index 0610464..7f5befa 100644
--- a/services/core/java/com/android/server/SensorNotificationService.java
+++ b/services/core/java/com/android/server/SensorNotificationService.java
@@ -20,25 +20,46 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.hardware.GeomagneticField;
import android.hardware.Sensor;
+import android.hardware.SensorAdditionalInfo;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
-public class SensorNotificationService extends SystemService implements SensorEventListener {
- //TODO: set DBG to false or remove Slog before release
- private static final boolean DBG = true;
+public class SensorNotificationService extends SystemService
+ implements SensorEventListener, LocationListener {
+ private static final boolean DBG = false;
private static final String TAG = "SensorNotificationService";
- private Context mContext;
+ private static final long MINUTE_IN_MS = 60 * 1000;
+ private static final long KM_IN_M = 1000;
+
+ private static final long LOCATION_MIN_TIME = 30 * MINUTE_IN_MS;
+ private static final long LOCATION_MIN_DISTANCE = 100 * KM_IN_M;
+
+ private static final String PROPERTY_USE_MOCKED_LOCATION =
+ "sensor.notification.use_mocked"; // max key length is 32
+
+ private static final long MILLIS_2010_1_1 = 1262358000000l;
+
+ private Context mContext;
private SensorManager mSensorManager;
+ private LocationManager mLocationManager;
private Sensor mMetaSensor;
+ // for rate limiting
+ private long mLocalGeomagneticFieldUpdateTime = -LOCATION_MIN_TIME;
+
public SensorNotificationService(Context context) {
super(context);
mContext = context;
@@ -50,7 +71,6 @@
public void onBootPhase(int phase) {
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
- // start
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
mMetaSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_DYNAMIC_SENSOR_META);
if (mMetaSensor == null) {
@@ -60,13 +80,28 @@
SensorManager.SENSOR_DELAY_FASTEST);
}
}
+
+ if (phase == PHASE_BOOT_COMPLETED) {
+ // LocationManagerService is initialized after PHASE_THIRD_PARTY_APPS_CAN_START
+ mLocationManager =
+ (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+ if (mLocationManager == null) {
+ if (DBG) Slog.d(TAG, "Cannot obtain location service.");
+ } else {
+ mLocationManager.requestLocationUpdates(
+ LocationManager.PASSIVE_PROVIDER,
+ LOCATION_MIN_TIME,
+ LOCATION_MIN_DISTANCE,
+ this);
+ }
+ }
}
private void broadcastDynamicSensorChanged() {
Intent i = new Intent(Intent.ACTION_DYNAMIC_SENSOR_CHANGED);
i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); // avoid waking up manifest receivers
mContext.sendBroadcastAsUser(i, UserHandle.ALL);
- if (DBG) Slog.d(TAG, "DYNS sent dynamic sensor broadcast");
+ if (DBG) Slog.d(TAG, "dynamic sensor broadcast sent");
}
@Override
@@ -77,8 +112,62 @@
}
@Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ public void onLocationChanged(Location location) {
+ if (DBG) Slog.d(TAG, String.format(
+ "Location is (%f, %f), h %f, acc %f, mocked %b",
+ location.getLatitude(), location.getLongitude(),
+ location.getAltitude(), location.getAccuracy(),
+ location.isFromMockProvider()));
+ // lat long == 0 usually means invalid location
+ if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+ return;
+ }
+
+ // update too often, ignore
+ if (SystemClock.elapsedRealtime() - mLocalGeomagneticFieldUpdateTime < 10 * MINUTE_IN_MS) {
+ return;
+ }
+
+ long time = System.currentTimeMillis();
+ // Mocked location should not be used. Except in test, only use mocked location
+ // Wrong system clock also gives bad values so ignore as well.
+ if (useMockedLocation() == location.isFromMockProvider() || time < MILLIS_2010_1_1) {
+ return;
+ }
+
+ GeomagneticField field = new GeomagneticField(
+ (float) location.getLatitude(), (float) location.getLongitude(),
+ (float) location.getAltitude(), time);
+ if (DBG) Slog.d(TAG, String.format(
+ "Nominal mag field, norm %fuT, decline %f deg, incline %f deg",
+ field.getFieldStrength() / 1000, field.getDeclination(), field.getInclination()));
+
+ try {
+ SensorAdditionalInfo info = SensorAdditionalInfo.createLocalGeomagneticField(
+ field.getFieldStrength() / 1000, // convert from nT to uT
+ (float)(field.getDeclination() * Math.PI / 180), // from degree to rad
+ (float)(field.getInclination() * Math.PI / 180)); // from degree to rad
+ if (info != null) {
+ mSensorManager.setOperationParameter(info);
+ mLocalGeomagneticFieldUpdateTime = SystemClock.elapsedRealtime();
+ }
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Invalid local geomagnetic field, ignore.");
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {}
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {}
+ @Override
+ public void onProviderEnabled(String provider) {}
+ @Override
+ public void onProviderDisabled(String provider) {}
+
+ private boolean useMockedLocation() {
+ return "false".equals(System.getProperty(PROPERTY_USE_MOCKED_LOCATION, "false"));
}
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 7c1a609..dd4d906 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -32,37 +32,39 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.telephony.CellLocation;
-import android.telephony.Rlog;
-import android.telephony.TelephonyManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
import android.telephony.CellInfo;
-import android.telephony.VoLteServiceState;
+import android.telephony.CellLocation;
import android.telephony.DisconnectCause;
+import android.telephony.PhoneStateListener;
import android.telephony.PreciseCallState;
import android.telephony.PreciseDataConnectionState;
import android.telephony.PreciseDisconnectCause;
+import android.telephony.Rlog;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.VoLteServiceState;
import android.text.TextUtils;
-import android.text.format.Time;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
+import android.util.LocalLog;
import com.android.internal.app.IBatteryStats;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
-import com.android.internal.telephony.ITelephonyRegistry;
import com.android.internal.telephony.IPhoneStateListener;
+import com.android.internal.telephony.ITelephonyRegistry;
import com.android.internal.telephony.PhoneConstantConversions;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.am.BatteryStatsService;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
/**
* Since phone process can be restarted, this class provides a centralized place
* that applications can register and be called back from.
@@ -159,8 +161,6 @@
private String[] mDataConnectionReason;
- private String[] mDataConnectionApn;
-
private ArrayList<String>[] mConnectedApns;
private LinkProperties[] mDataConnectionLinkProperties;
@@ -191,6 +191,8 @@
private boolean mCarrierNetworkChangeState = false;
+ private final LocalLog mLocalLog = new LocalLog(100);
+
private PreciseDataConnectionState mPreciseDataConnectionState =
new PreciseDataConnectionState();
@@ -310,7 +312,6 @@
mMessageWaiting = new boolean[numPhones];
mDataConnectionPossible = new boolean[numPhones];
mDataConnectionReason = new String[numPhones];
- mDataConnectionApn = new String[numPhones];
mCallForwarding = new boolean[numPhones];
mCellLocation = new Bundle[numPhones];
mDataConnectionLinkProperties = new LinkProperties[numPhones];
@@ -329,7 +330,6 @@
mCallForwarding[i] = false;
mDataConnectionPossible[i] = false;
mDataConnectionReason[i] = "";
- mDataConnectionApn[i] = "";
mCellLocation[i] = new Bundle();
mCellInfo.add(i, null);
mConnectedApns[i] = new ArrayList<String>();
@@ -536,7 +536,6 @@
if (DBG) {
log("listen: Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId);
}
- if (VDBG) toStringLogSSC("listen");
if (notifyNow && validatePhoneId(phoneId)) {
if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
try {
@@ -780,14 +779,14 @@
}
synchronized (mRecords) {
+ String str = "notifyServiceStateForSubscriber: subId=" + subId + " phoneId=" + phoneId
+ + " state=" + state;
if (VDBG) {
- log("notifyServiceStateForSubscriber: subId=" + subId + " phoneId=" + phoneId
- + " state=" + state);
+ log(str);
}
+ mLocalLog.log(str);
if (validatePhoneId(phoneId)) {
mServiceState[phoneId] = state;
- logServiceStateChanged("notifyServiceStateForSubscriber", subId, phoneId, state);
- if (VDBG) toStringLogSSC("notifyServiceStateForSubscriber");
for (Record r : mRecords) {
if (VDBG) {
@@ -885,7 +884,6 @@
if (VDBG) {
log("notifySignalStrengthForPhoneId: subId=" + subId
+" phoneId=" + phoneId + " signalStrength=" + signalStrength);
- toStringLogSSC("notifySignalStrengthForPhoneId");
}
synchronized (mRecords) {
@@ -1137,18 +1135,20 @@
modified = true;
}
if (modified) {
- if (DBG) {
- log("onDataConnectionStateChanged(" + mDataConnectionState[phoneId]
- + ", " + mDataConnectionNetworkType[phoneId] + ")");
- }
+ String str = "onDataConnectionStateChanged(" + mDataConnectionState[phoneId]
+ + ", " + mDataConnectionNetworkType[phoneId] + ")";
+ log(str);
+ mLocalLog.log(str);
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) &&
idMatch(r.subId, subId, phoneId)) {
try {
- log("Notify data connection state changed on sub: " +
- subId);
- r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId],
+ if (DBG) {
+ log("Notify data connection state changed on sub: " + subId);
+ }
+ r.callback.onDataConnectionStateChanged(
+ mDataConnectionState[phoneId],
mDataConnectionNetworkType[phoneId]);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
@@ -1163,7 +1163,8 @@
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) {
try {
- r.callback.onPreciseDataConnectionStateChanged(mPreciseDataConnectionState);
+ r.callback.onPreciseDataConnectionStateChanged(
+ mPreciseDataConnectionState);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -1391,36 +1392,58 @@
}
@Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
synchronized (mRecords) {
final int recordCount = mRecords.size();
pw.println("last known state:");
+ pw.increaseIndent();
for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
- pw.println(" Phone Id=" + i);
- pw.println(" mCallState=" + mCallState[i]);
- pw.println(" mCallIncomingNumber=" + mCallIncomingNumber[i]);
- pw.println(" mServiceState=" + mServiceState[i]);
- pw.println(" mVoiceActivationState= " + mVoiceActivationState[i]);
- pw.println(" mDataActivationState= " + mDataActivationState[i]);
- pw.println(" mSignalStrength=" + mSignalStrength[i]);
- pw.println(" mMessageWaiting=" + mMessageWaiting[i]);
- pw.println(" mCallForwarding=" + mCallForwarding[i]);
- pw.println(" mDataActivity=" + mDataActivity[i]);
- pw.println(" mDataConnectionState=" + mDataConnectionState[i]);
- pw.println(" mDataConnectionPossible=" + mDataConnectionPossible[i]);
- pw.println(" mDataConnectionReason=" + mDataConnectionReason[i]);
- pw.println(" mDataConnectionApn=" + mDataConnectionApn[i]);
- pw.println(" mDataConnectionLinkProperties=" + mDataConnectionLinkProperties[i]);
- pw.println(" mDataConnectionNetworkCapabilities=" +
+ pw.println("Phone Id=" + i);
+ pw.increaseIndent();
+ pw.println("mCallState=" + mCallState[i]);
+ pw.println("mCallIncomingNumber=" + mCallIncomingNumber[i]);
+ pw.println("mServiceState=" + mServiceState[i]);
+ pw.println("mVoiceActivationState= " + mVoiceActivationState[i]);
+ pw.println("mDataActivationState= " + mDataActivationState[i]);
+ pw.println("mSignalStrength=" + mSignalStrength[i]);
+ pw.println("mMessageWaiting=" + mMessageWaiting[i]);
+ pw.println("mCallForwarding=" + mCallForwarding[i]);
+ pw.println("mDataActivity=" + mDataActivity[i]);
+ pw.println("mDataConnectionState=" + mDataConnectionState[i]);
+ pw.println("mDataConnectionPossible=" + mDataConnectionPossible[i]);
+ pw.println("mDataConnectionReason=" + mDataConnectionReason[i]);
+ pw.println("mDataConnectionLinkProperties=" + mDataConnectionLinkProperties[i]);
+ pw.println("mDataConnectionNetworkCapabilities=" +
mDataConnectionNetworkCapabilities[i]);
- pw.println(" mCellLocation=" + mCellLocation[i]);
- pw.println(" mCellInfo=" + mCellInfo.get(i));
+ pw.println("mCellLocation=" + mCellLocation[i]);
+ pw.println("mCellInfo=" + mCellInfo.get(i));
+ pw.decreaseIndent();
}
+ pw.println("mConnectedApns=" + Arrays.toString(mConnectedApns));
+ pw.println("mPreciseDataConnectionState=" + mPreciseDataConnectionState);
+ pw.println("mPreciseCallState=" + mPreciseCallState);
+ pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState);
+ pw.println("mRingingCallState=" + mRingingCallState);
+ pw.println("mForegroundCallState=" + mForegroundCallState);
+ pw.println("mBackgroundCallState=" + mBackgroundCallState);
+ pw.println("mVoLteServiceState=" + mVoLteServiceState);
+
+ pw.decreaseIndent();
+
+ pw.println("local logs:");
+ pw.increaseIndent();
+ mLocalLog.dump(fd, pw, args);
+ pw.decreaseIndent();
pw.println("registrations: count=" + recordCount);
+ pw.increaseIndent();
for (Record r : mRecords) {
- pw.println(" " + r);
+ pw.println(r);
}
+ pw.decreaseIndent();
}
}
@@ -1705,63 +1728,6 @@
Rlog.d(TAG, s);
}
- private static class LogSSC {
- private Time mTime;
- private String mS;
- private int mSubId;
- private int mPhoneId;
- private ServiceState mState;
-
- public void set(Time t, String s, int subId, int phoneId, ServiceState state) {
- mTime = t; mS = s; mSubId = subId; mPhoneId = phoneId; mState = state;
- }
-
- @Override
- public String toString() {
- return mS + " Time " + mTime.toString() + " mSubId " + mSubId + " mPhoneId "
- + mPhoneId + " mState " + mState;
- }
- }
-
- private LogSSC logSSC [] = new LogSSC[10];
- private int next = 0;
-
- private void logServiceStateChanged(String s, int subId, int phoneId, ServiceState state) {
- if (logSSC == null || logSSC.length == 0) {
- return;
- }
- if (logSSC[next] == null) {
- logSSC[next] = new LogSSC();
- }
- Time t = new Time();
- t.setToNow();
- logSSC[next].set(t, s, subId, phoneId, state);
- if (++next >= logSSC.length) {
- next = 0;
- }
- }
-
- private void toStringLogSSC(String prompt) {
- if (logSSC == null || logSSC.length == 0 || (next == 0 && logSSC[next] == null)) {
- log(prompt + ": logSSC is empty");
- } else {
- // There is at least one element
- log(prompt + ": logSSC.length=" + logSSC.length + " next=" + next);
- int i = next;
- if (logSSC[i] == null) {
- // logSSC is not full so back to the beginning
- i = 0;
- }
- do {
- log(logSSC[i].toString());
- if (++i >= logSSC.length) {
- i = 0;
- }
- } while (i != next);
- log(prompt + ": ----------------");
- }
- }
-
boolean idMatch(int rSubId, int subId, int phoneId) {
if(subId < 0) {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index e560d32..738365d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -427,14 +427,13 @@
public boolean addAccountExplicitlyWithVisibility(Account account, String password,
Bundle extras, Map packageToVisibility) {
Bundle.setDefusable(extras, true);
-
- final int callingUid = Binder.getCallingUid();
+ int callingUid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "addAccountExplicitly: " + account + ", caller's uid " + callingUid
+ ", pid " + Binder.getCallingPid());
}
Preconditions.checkNotNull(account, "account cannot be null");
- int userId = UserHandle.getCallingUserId();
if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format("uid %s cannot explicitly add accounts of type: %s",
callingUid, account.type);
@@ -461,9 +460,9 @@
public Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName,
String accountType) {
int callingUid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
boolean isSystemUid = UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
- List<String> managedTypes =
- getTypesForCaller(callingUid, UserHandle.getUserId(callingUid), isSystemUid);
+ List<String> managedTypes = getTypesForCaller(callingUid, userId, isSystemUid);
if ((accountType != null && !managedTypes.contains(accountType))
|| (accountType == null && !isSystemUid)) {
@@ -478,8 +477,9 @@
long identityToken = clearCallingIdentity();
try {
+ UserAccounts accounts = getUserAccounts(userId);
return getAccountsAndVisibilityForPackage(packageName, managedTypes, callingUid,
- getUserAccounts(UserHandle.getUserId(callingUid)));
+ accounts);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -490,12 +490,8 @@
*/
private Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName,
List<String> accountTypes, Integer callingUid, UserAccounts accounts) {
- int uid = 0;
- try {
- uid = mPackageManager.getPackageUidAsUser(packageName,
- UserHandle.getUserId(callingUid));
- } catch (NameNotFoundException e) {
- Log.d(TAG, "Package not found " + e.getMessage());
+ if (!packageExistsForUser(packageName, accounts.userId)) {
+ Log.d(TAG, "Package not found " + packageName);
return new LinkedHashMap<>();
}
@@ -520,19 +516,26 @@
public Map<String, Integer> getPackagesAndVisibilityForAccount(Account account) {
Preconditions.checkNotNull(account, "account cannot be null");
int callingUid = Binder.getCallingUid();
- int userId = UserHandle.getUserId(callingUid);
- UserAccounts accounts = getUserAccounts(userId);
+ int userId = UserHandle.getCallingUserId();
if (!isAccountManagedByCaller(account.type, callingUid, userId)
&& !isSystemUid(callingUid)) {
String msg =
String.format("uid %s cannot get secrets for account %s", callingUid, account);
throw new SecurityException(msg);
}
- synchronized (accounts.dbLock) {
- synchronized (accounts.cacheLock) {
- return getPackagesAndVisibilityForAccountLocked(account, accounts);
+
+ long identityToken = clearCallingIdentity();
+ try {
+ UserAccounts accounts = getUserAccounts(userId);
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ return getPackagesAndVisibilityForAccountLocked(account, accounts);
+ }
}
+ } finally {
+ restoreCallingIdentity(identityToken);
}
+
}
/**
@@ -560,8 +563,8 @@
Preconditions.checkNotNull(account, "account cannot be null");
Preconditions.checkNotNull(packageName, "packageName cannot be null");
int callingUid = Binder.getCallingUid();
- UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
- if (!isAccountManagedByCaller(account.type, callingUid, accounts.userId)
+ int userId = UserHandle.getCallingUserId();
+ if (!isAccountManagedByCaller(account.type, callingUid, userId)
&& !isSystemUid(callingUid)) {
String msg = String.format(
"uid %s cannot get secrets for accounts of type: %s",
@@ -569,7 +572,13 @@
account.type);
throw new SecurityException(msg);
}
- return resolveAccountVisibility(account, packageName, accounts);
+ long identityToken = clearCallingIdentity();
+ try {
+ UserAccounts accounts = getUserAccounts(userId);
+ return resolveAccountVisibility(account, packageName, accounts);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
}
/**
@@ -708,8 +717,8 @@
Preconditions.checkNotNull(account, "account cannot be null");
Preconditions.checkNotNull(packageName, "packageName cannot be null");
int callingUid = Binder.getCallingUid();
- UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
- if (!isAccountManagedByCaller(account.type, callingUid, accounts.userId)
+ int userId = UserHandle.getCallingUserId();
+ if (!isAccountManagedByCaller(account.type, callingUid, userId)
&& !isSystemUid(callingUid)) {
String msg = String.format(
"uid %s cannot get secrets for accounts of type: %s",
@@ -717,8 +726,14 @@
account.type);
throw new SecurityException(msg);
}
- return setAccountVisibility(account, packageName, newVisibility, true /* notify */,
+ long identityToken = clearCallingIdentity();
+ try {
+ UserAccounts accounts = getUserAccounts(userId);
+ return setAccountVisibility(account, packageName, newVisibility, true /* notify */,
accounts);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
}
/**
@@ -805,8 +820,15 @@
public void registerAccountListener(String[] accountTypes, String opPackageName) {
int callingUid = Binder.getCallingUid();
mAppOpsManager.checkPackage(callingUid, opPackageName);
- registerAccountListener(accountTypes, opPackageName,
- getUserAccounts(UserHandle.getUserId(callingUid)));
+
+ int userId = UserHandle.getCallingUserId();
+ long identityToken = clearCallingIdentity();
+ try {
+ UserAccounts accounts = getUserAccounts(userId);
+ registerAccountListener(accountTypes, opPackageName, accounts);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
}
private void registerAccountListener(String[] accountTypes, String opPackageName,
@@ -832,7 +854,18 @@
public void unregisterAccountListener(String[] accountTypes, String opPackageName) {
int callingUid = Binder.getCallingUid();
mAppOpsManager.checkPackage(callingUid, opPackageName);
- UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
+ int userId = UserHandle.getCallingUserId();
+ long identityToken = clearCallingIdentity();
+ try {
+ UserAccounts accounts = getUserAccounts(userId);
+ unregisterAccountListener(accountTypes, opPackageName, accounts);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ private void unregisterAccountListener(String[] accountTypes, String opPackageName,
+ UserAccounts accounts) {
synchronized (accounts.mReceiversForType) {
if (accountTypes == null) {
// null for any type
@@ -903,7 +936,7 @@
long identityToken = clearCallingIdentity();
try {
mPackageManager.getPackageUidAsUser(packageName, userId);
- return true; // package exist
+ return true;
} finally {
restoreCallingIdentity(identityToken);
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4cbfb27..7dd75df 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -31,8 +31,10 @@
import android.app.ActivityThread;
import android.app.AppOpsManager;
+import android.app.ServiceStartArgs;
import android.content.IIntentSender;
import android.content.IntentSender;
+import android.content.pm.ParceledListSlice;
import android.os.Build;
import android.os.Bundle;
import android.os.DeadObjectException;
@@ -369,7 +371,8 @@
}
// This app knows it is in the new model where this operation is not
// allowed, so tell it what has happened.
- return new ComponentName("?", "app is in background");
+ UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);
+ return new ComponentName("?", "app is in background uid " + uidRec);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -1956,78 +1959,86 @@
return;
}
- while (r.pendingStarts.size() > 0) {
- Exception caughtException = null;
- ServiceRecord.StartItem si = null;
- try {
- si = r.pendingStarts.remove(0);
- if (DEBUG_SERVICE) {
- Slog.v(TAG_SERVICE, "Sending arguments to: "
- + r + " " + r.intent + " args=" + si.intent);
- }
- if (si.intent == null && N > 1) {
- // If somehow we got a dummy null intent in the middle,
- // then skip it. DO NOT skip a null intent when it is
- // the only one in the list -- this is to support the
- // onStartCommand(null) case.
- continue;
- }
- si.deliveredTime = SystemClock.uptimeMillis();
- r.deliveredStarts.add(si);
- si.deliveryCount++;
- if (si.neededGrants != null) {
- mAm.grantUriPermissionUncheckedFromIntentLocked(si.neededGrants,
- si.getUriPermissionsLocked());
- }
- mAm.grantEphemeralAccessLocked(r.userId, si.intent,
- r.appInfo.uid, UserHandle.getAppId(si.callingId));
- bumpServiceExecutingLocked(r, execInFg, "start");
- if (!oomAdjusted) {
- oomAdjusted = true;
- mAm.updateOomAdjLocked(r.app);
- }
- if (r.fgRequired && !r.fgWaiting) {
- if (!r.isForeground) {
- if (DEBUG_BACKGROUND_CHECK) {
- Slog.i(TAG, "Launched service must call startForeground() within timeout: " + r);
- }
- scheduleServiceForegroundTransitionTimeoutLocked(r);
- } else {
- if (DEBUG_BACKGROUND_CHECK) {
- Slog.i(TAG, "Service already foreground; no new timeout: " + r);
- }
- r.fgRequired = false;
- }
- }
- int flags = 0;
- if (si.deliveryCount > 1) {
- flags |= Service.START_FLAG_RETRY;
- }
- if (si.doneExecutingCount > 0) {
- flags |= Service.START_FLAG_REDELIVERY;
- }
- r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent);
- } catch (TransactionTooLargeException e) {
- if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Transaction too large: intent="
- + si.intent);
- caughtException = e;
- } catch (RemoteException e) {
- // Remote process gone... we'll let the normal cleanup take care of this.
- if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while sending args: " + r);
- caughtException = e;
- } catch (Exception e) {
- Slog.w(TAG, "Unexpected exception", e);
- caughtException = e;
- }
+ ArrayList<ServiceStartArgs> args = new ArrayList<>();
- if (caughtException != null) {
- // Keep nesting count correct
- final boolean inDestroying = mDestroyingServices.contains(r);
- serviceDoneExecutingLocked(r, inDestroying, inDestroying);
- if (caughtException instanceof TransactionTooLargeException) {
- throw (TransactionTooLargeException)caughtException;
+ while (r.pendingStarts.size() > 0) {
+ ServiceRecord.StartItem si = r.pendingStarts.remove(0);
+ if (DEBUG_SERVICE) {
+ Slog.v(TAG_SERVICE, "Sending arguments to: "
+ + r + " " + r.intent + " args=" + si.intent);
+ }
+ if (si.intent == null && N > 1) {
+ // If somehow we got a dummy null intent in the middle,
+ // then skip it. DO NOT skip a null intent when it is
+ // the only one in the list -- this is to support the
+ // onStartCommand(null) case.
+ continue;
+ }
+ si.deliveredTime = SystemClock.uptimeMillis();
+ r.deliveredStarts.add(si);
+ si.deliveryCount++;
+ if (si.neededGrants != null) {
+ mAm.grantUriPermissionUncheckedFromIntentLocked(si.neededGrants,
+ si.getUriPermissionsLocked());
+ }
+ mAm.grantEphemeralAccessLocked(r.userId, si.intent,
+ r.appInfo.uid, UserHandle.getAppId(si.callingId));
+ bumpServiceExecutingLocked(r, execInFg, "start");
+ if (!oomAdjusted) {
+ oomAdjusted = true;
+ mAm.updateOomAdjLocked(r.app);
+ }
+ if (r.fgRequired && !r.fgWaiting) {
+ if (!r.isForeground) {
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Launched service must call startForeground() within timeout: " + r);
+ }
+ scheduleServiceForegroundTransitionTimeoutLocked(r);
+ } else {
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Service already foreground; no new timeout: " + r);
+ }
+ r.fgRequired = false;
}
- break;
+ }
+ int flags = 0;
+ if (si.deliveryCount > 1) {
+ flags |= Service.START_FLAG_RETRY;
+ }
+ if (si.doneExecutingCount > 0) {
+ flags |= Service.START_FLAG_REDELIVERY;
+ }
+ args.add(new ServiceStartArgs(si.taskRemoved, si.id, flags, si.intent));
+ }
+
+ ParceledListSlice<ServiceStartArgs> slice = new ParceledListSlice<>(args);
+ slice.setInlineCountLimit(4);
+ Exception caughtException = null;
+ try {
+ r.app.thread.scheduleServiceArgs(r, slice);
+ } catch (TransactionTooLargeException e) {
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Transaction too large for " + args.size()
+ + " args, first: " + args.get(0).args);
+ Slog.w(TAG, "Failed delivering service starts", e);
+ caughtException = e;
+ } catch (RemoteException e) {
+ // Remote process gone... we'll let the normal cleanup take care of this.
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while sending args: " + r);
+ Slog.w(TAG, "Failed delivering service starts", e);
+ caughtException = e;
+ } catch (Exception e) {
+ Slog.w(TAG, "Unexpected exception", e);
+ caughtException = e;
+ }
+
+ if (caughtException != null) {
+ // Keep nesting count correct
+ final boolean inDestroying = mDestroyingServices.contains(r);
+ for (int i = 0; i < args.size(); i++) {
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying);
+ }
+ if (caughtException instanceof TransactionTooLargeException) {
+ throw (TransactionTooLargeException)caughtException;
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 19fc2b8..ee2fdba 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -12143,6 +12143,24 @@
return false;
}
+ @Override
+ public void backgroundWhitelistUid(final int uid) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the OS may call backgroundWhitelistUid()");
+ }
+
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Adding uid " + uid + " to bg uid whitelist");
+ }
+ synchronized (this) {
+ final int N = mBackgroundUidWhitelist.length;
+ int[] newList = new int[N+1];
+ System.arraycopy(mBackgroundUidWhitelist, 0, newList, 0, N);
+ newList[N] = uid;
+ mBackgroundUidWhitelist = newList;
+ }
+ }
+
final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
String abiOverride) {
ProcessRecord app;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index b72cd73..3e3fee5 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -745,7 +745,7 @@
}
/**
- * Returns a {@link TaskRecord} for the input id if available. Null otherwise.
+ * Returns a {@link TaskRecord} for the input id if available. {@code null} otherwise.
* @param id Id of the task we would like returned.
* @param matchMode The mode to match the given task id in.
* @param stackId The stack to restore the task to (default launch stack will be used if
@@ -765,7 +765,7 @@
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
ActivityStack stack = stacks.get(stackNdx);
- TaskRecord task = stack.taskForIdLocked(id);
+ final TaskRecord task = stack.taskForIdLocked(id);
if (task != null) {
return task;
}
@@ -780,11 +780,17 @@
// Otherwise, check the recent tasks and return if we find it there and we are not restoring
// the task from recents
if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Looking for task id=" + id + " in recents");
- TaskRecord task = mRecentTasks.taskForIdLocked(id);
- if (matchMode == MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) {
- if (DEBUG_RECENTS && task == null) {
+ final TaskRecord task = mRecentTasks.taskForIdLocked(id);
+
+ if (task == null) {
+ if (DEBUG_RECENTS) {
Slog.d(TAG_RECENTS, "\tDidn't find task id=" + id + " in recents");
}
+
+ return null;
+ }
+
+ if (matchMode == MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) {
return task;
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index e5b2eca..983c975 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1021,32 +1021,24 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- Slog.d(TAG, "begin setBatteryStateLocked");
- try {
- synchronized (mStats) {
- final boolean onBattery = plugType == BatteryStatsImpl.BATTERY_PLUGGED_NONE;
- if (mStats.isOnBattery() == onBattery) {
- // The battery state has not changed, so we don't need to sync external
- // stats immediately.
- mStats.setBatteryStateLocked(status, health, plugType, level, temp,
- volt,
- chargeUAh, chargeFullUAh);
- return;
- }
+ synchronized (mStats) {
+ final boolean onBattery = plugType == BatteryStatsImpl.BATTERY_PLUGGED_NONE;
+ if (mStats.isOnBattery() == onBattery) {
+ // The battery state has not changed, so we don't need to sync external
+ // stats immediately.
+ mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt,
+ chargeUAh, chargeFullUAh);
+ return;
}
- } finally {
- Slog.d(TAG, "end setBatteryStateLocked");
}
// Sync external stats first as the battery has changed states. If we don't sync
// immediately here, we may not collect the relevant data later.
updateExternalStatsSync("battery-state", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
- Slog.d(TAG, "begin setBatteryStateLocked");
synchronized (mStats) {
mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt,
chargeUAh, chargeFullUAh);
}
- Slog.d(TAG, "end setBatteryStateLocked");
}
});
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 728476a..3b5e5bc 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -670,7 +670,7 @@
}
mInjector.systemServiceManagerCleanupUser(userId);
synchronized (mLock) {
- mInjector.stackSupervisorRemoveUserLocked(userId);
+ mInjector.getActivityStackSupervisor().removeUserLocked(userId);
}
// Remove the user if it is ephemeral.
if (getUserInfo(userId).isEphemeral()) {
@@ -823,8 +823,10 @@
return true;
}
- mInjector.stackSupervisorSetLockTaskModeLocked(null,
- ActivityManager.LOCK_TASK_MODE_NONE, "startUser", false);
+ if (foreground) {
+ mInjector.getActivityStackSupervisor().setLockTaskModeLocked(
+ null, ActivityManager.LOCK_TASK_MODE_NONE, "startUser", false);
+ }
final UserInfo userInfo = getUserInfo(userId);
if (userInfo == null) {
@@ -1202,11 +1204,12 @@
}
void moveUserToForegroundLocked(UserState uss, int oldUserId, int newUserId) {
- boolean homeInFront = mInjector.stackSupervisorSwitchUserLocked(newUserId, uss);
+ boolean homeInFront =
+ mInjector.getActivityStackSupervisor().switchUserLocked(newUserId, uss);
if (homeInFront) {
mInjector.startHomeActivityLocked(newUserId, "moveUserToForeground");
} else {
- mInjector.stackSupervisorResumeFocusedStackTopActivityLocked();
+ mInjector.getActivityStackSupervisor().resumeFocusedStackTopActivityLocked();
}
EventLogTags.writeAmSwitchUser(newUserId);
sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
@@ -1680,10 +1683,6 @@
mService.mSystemServiceManager.cleanupUser(userId);
}
- void stackSupervisorRemoveUserLocked(int userId) {
- mService.mStackSupervisor.removeUserLocked(userId);
- }
-
protected UserManagerService getUserManager() {
if (mUserManager == null) {
IBinder b = ServiceManager.getService(Context.USER_SERVICE);
@@ -1743,24 +1742,10 @@
return mService.checkComponentPermission(permission, pid, uid, owningUid, exported);
}
- boolean stackSupervisorSwitchUserLocked(int userId, UserState uss) {
- return mService.mStackSupervisor.switchUserLocked(userId, uss);
- }
-
void startHomeActivityLocked(int userId, String reason) {
mService.startHomeActivityLocked(userId, reason);
}
- void stackSupervisorResumeFocusedStackTopActivityLocked() {
- mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
- }
-
- void stackSupervisorSetLockTaskModeLocked(TaskRecord task, int lockTaskModeState,
- String reason, boolean andResume) {
- mService.mStackSupervisor.setLockTaskModeLocked(task, lockTaskModeState, reason,
- andResume);
- }
-
void updateUserConfigurationLocked() {
mService.updateUserConfigurationLocked();
}
@@ -1778,5 +1763,9 @@
true /* above system */);
d.show();
}
+
+ ActivityStackSupervisor getActivityStackSupervisor() {
+ return mService.mStackSupervisor;
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 82a0ff6..51aa4f8 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -103,6 +103,13 @@
apc.init();
synchronized(mPlayerLock) {
mPlayers.put(newPiid, apc);
+ if (mDuckedUids.contains(new Integer(apc.getClientUid()))) {
+ if (DEBUG) { Log.v(TAG, " > trackPlayer() piid=" + newPiid + " must be ducked"); }
+ mDuckedPlayers.add(new Integer(newPiid));
+ // FIXME here the player needs to be put in a state that is the same as if it
+ // had been ducked as it starts. At the moment, this works already for linked
+ // players, as is the case in gapless playback.
+ }
}
return newPiid;
}
@@ -141,6 +148,13 @@
Log.e(TAG, "Error handling event " + event);
change = false;
}
+ if (change && event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED
+ && mDuckedUids.contains(new Integer(apc.getClientUid()))) {
+ if (DEBUG) { Log.v(TAG, " > playerEvent() piid=" + piid + " must be ducked"); }
+ if (!mDuckedPlayers.contains(new Integer(piid))) {
+ mDuckedPlayers.add(new Integer(piid));
+ }
+ }
}
if (change) {
dispatchPlaybackChange();
@@ -273,13 +287,20 @@
// PlayerFocusEnforcer implementation
private final ArrayList<Integer> mDuckedPlayers = new ArrayList<Integer>();
private final ArrayList<Integer> mMutedPlayers = new ArrayList<Integer>();
+ // size of 2 for typical cases of double-ducking, not expected to grow beyond that, but can
+ private final ArrayList<Integer> mDuckedUids = new ArrayList<Integer>(2);
@Override
public boolean duckPlayers(FocusRequester winner, FocusRequester loser) {
if (DEBUG) {
Log.v(TAG, String.format("duckPlayers: uids winner=%d loser=%d",
- winner.getClientUid(), loser.getClientUid())); }
+ winner.getClientUid(), loser.getClientUid()));
+ }
synchronized (mPlayerLock) {
+ final Integer loserUid = new Integer(loser.getClientUid());
+ if (!mDuckedUids.contains(loserUid)) {
+ mDuckedUids.add(loserUid);
+ }
if (mPlayers.isEmpty()) {
return true;
}
@@ -296,7 +317,7 @@
&& loser.hasSameUid(apc.getClientUid())
&& apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED)
{
- if (mDuckedPlayers.contains(piid)) {
+ if (mDuckedPlayers.contains(new Integer(piid))) {
if (DEBUG) { Log.v(TAG, "player " + piid + " already ducked"); }
} else if (apc.getAudioAttributes().getContentType() ==
AudioAttributes.CONTENT_TYPE_SPEECH) {
@@ -313,7 +334,7 @@
apc.getPlayerProxy().applyVolumeShaper(
DUCK_VSHAPE,
PLAY_CREATE_IF_NEEDED);
- mDuckedPlayers.add(piid);
+ mDuckedPlayers.add(new Integer(piid));
} catch (Exception e) {
Log.e(TAG, "Error ducking player " + piid, e);
// something went wrong trying to duck, so let the app handle it
@@ -332,25 +353,36 @@
if (DEBUG) { Log.v(TAG, "unduckPlayers: uids winner=" + winner.getClientUid()); }
synchronized (mPlayerLock) {
if (mDuckedPlayers.isEmpty()) {
+ mDuckedUids.remove(new Integer(winner.getClientUid()));
return;
}
+ final ArrayList<Integer> playersToRemove =
+ new ArrayList<Integer>(mDuckedPlayers.size());
for (int piid : mDuckedPlayers) {
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
- if (apc != null
- && winner.hasSameUid(apc.getClientUid())) {
- try {
- Log.v(TAG, "unducking player" + piid);
- mDuckedPlayers.remove(new Integer(piid));
- apc.getPlayerProxy().applyVolumeShaper(
- DUCK_ID,
- VolumeShaper.Operation.REVERSE);
- } catch (Exception e) {
- Log.e(TAG, "Error unducking player " + piid, e);
+ if (apc != null) {
+ if (winner.hasSameUid(apc.getClientUid())) {
+ try {
+ Log.v(TAG, "unducking player " + piid);
+ apc.getPlayerProxy().applyVolumeShaper(
+ DUCK_ID,
+ VolumeShaper.Operation.REVERSE);
+ } catch (Exception e) {
+ Log.e(TAG, "Error unducking player " + piid, e);
+ } finally {
+ playersToRemove.add(piid);
+ }
}
} else {
- Log.e(TAG, "Error unducking player " + piid + ", player not found");
+ // this piid was in the list of ducked players, but wasn't found, discard it
+ Log.v(TAG, "Error unducking player " + piid + ", player not found");
+ playersToRemove.add(piid);
}
}
+ for (int piid : playersToRemove) {
+ mDuckedPlayers.remove(new Integer(piid));
+ }
+ mDuckedUids.remove(new Integer(winner.getClientUid()));
}
}
@@ -383,7 +415,7 @@
try {
Log.v(TAG, "call: muting player" + piid);
apc.getPlayerProxy().setVolume(0.0f);
- mMutedPlayers.add(piid);
+ mMutedPlayers.add(new Integer(piid));
} catch (Exception e) {
Log.e(TAG, "call: error muting player " + piid, e);
}
diff --git a/services/core/java/com/android/server/job/JobPackageTracker.java b/services/core/java/com/android/server/job/JobPackageTracker.java
index 0a6d8a4..8ad1bea 100644
--- a/services/core/java/com/android/server/job/JobPackageTracker.java
+++ b/services/core/java/com/android/server/job/JobPackageTracker.java
@@ -345,6 +345,7 @@
public void notePending(JobStatus job) {
final long now = SystemClock.uptimeMillis();
+ job.madePending = now;
rebatchIfNeeded(now);
mCurDataSet.incPending(job.getSourceUid(), job.getSourcePackageName(), now);
}
@@ -357,6 +358,7 @@
public void noteActive(JobStatus job) {
final long now = SystemClock.uptimeMillis();
+ job.madeActive = now;
rebatchIfNeeded(now);
if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
mCurDataSet.incActiveTop(job.getSourceUid(), job.getSourcePackageName(), now);
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 7c231ff..cd3ba4c 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -2035,27 +2035,35 @@
pw.print(" Evaluated priority: "); pw.println(priority);
}
pw.print(" Tag: "); pw.println(job.getTag());
+ pw.print(" Enq: ");
+ TimeUtils.formatDuration(now - job.madePending, pw);
+ pw.println(" ago");
}
pw.println();
pw.println("Active jobs:");
for (int i=0; i<mActiveServices.size(); i++) {
JobServiceContext jsc = mActiveServices.get(i);
pw.print(" Slot #"); pw.print(i); pw.print(": ");
- if (jsc.getRunningJob() == null) {
+ final JobStatus job = jsc.getRunningJob();
+ if (job == null) {
pw.println("inactive");
continue;
} else {
- pw.println(jsc.getRunningJob().toShortString());
+ pw.println(job.toShortString());
pw.print(" Running for: ");
TimeUtils.formatDuration(now - jsc.getExecutionStartTimeElapsed(), pw);
pw.print(", timeout at: ");
TimeUtils.formatDuration(jsc.getTimeoutElapsed() - now, pw);
pw.println();
- jsc.getRunningJob().dump(pw, " ", false);
+ job.dump(pw, " ", false);
int priority = evaluateJobPriorityLocked(jsc.getRunningJob());
if (priority != JobInfo.PRIORITY_DEFAULT) {
pw.print(" Evaluated priority: "); pw.println(priority);
}
+ pw.print(" Active at "); pw.println(job.madeActive);
+ pw.print(" Pending for ");
+ TimeUtils.formatDuration(job.madeActive - job.madePending, pw);
+ pw.println();
}
}
if (filterUid == -1) {
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
index f41e187..0e04d24 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -147,7 +147,6 @@
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
-
if (action.equals(Intent.ACTION_SCREEN_ON)
|| action.equals(Intent.ACTION_DREAMING_STOPPED)) {
if (DEBUG) {
@@ -183,6 +182,11 @@
}
mIdle = true;
reportNewIdleState(mIdle);
+ } else {
+ if (DEBUG) {
+ Slog.v(TAG, "TRIGGER_IDLE received but not changing state; idle="
+ + mIdle + " screen=" + mScreenOn);
+ }
}
}
}
@@ -191,7 +195,7 @@
@Override
public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
pw.print("Idle: ");
- pw.println(mIdleTracker.isIdle() ? "true" : "false");
+ pw.println(mIdleTracker.isIdle());
pw.print("Tracking ");
pw.print(mTrackedTasks.size());
pw.println(":");
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 47630d0..d27d0e5 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -129,6 +129,10 @@
// Used by shell commands
public int overrideState = 0;
+ // Metrics about queue latency
+ public long madePending;
+ public long madeActive;
+
/**
* For use only by ContentObserverController: state it is maintaining about content URIs
* being observed.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 44c715b..64ee1e9 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1294,19 +1294,14 @@
sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
}
- private void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel,
- boolean fromAssistant) {
+ private void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel) {
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);
- }
+ mRankingHelper.updateNotificationChannel(pkg, uid, channel);
synchronized (mNotificationLock) {
final int N = mNotificationList.size();
@@ -1709,7 +1704,7 @@
NotificationChannel channel) {
enforceSystemOrSystemUI("Caller not system or systemui");
Preconditions.checkNotNull(channel);
- updateNotificationChannelInt(pkg, uid, channel, false);
+ updateNotificationChannelInt(pkg, uid, channel);
}
@Override
@@ -2646,47 +2641,6 @@
Binder.restoreCallingIdentity(identity);
}
}
-
- @Override
- public void createNotificationChannelFromAssistant(INotificationListener token, String pkg,
- NotificationChannel channel) throws RemoteException {
- ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token);
- int uid = mPackageManager.getPackageUid(pkg, 0, info.userid);
- mRankingHelper.createNotificationChannel(pkg, uid, channel, false /* fromTargetApp */);
- savePolicyFile();
- }
-
- @Override
- public void deleteNotificationChannelFromAssistant(INotificationListener token, String pkg,
- String channelId) throws RemoteException {
- ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token);
- if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
- throw new IllegalArgumentException("Cannot delete default channel");
- }
-
- int uid = mPackageManager.getPackageUid(pkg, 0, info.userid);
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
- info.userid, REASON_CHANNEL_BANNED, null);
- mRankingHelper.deleteNotificationChannel(pkg, uid, channelId);
- savePolicyFile();
- }
-
- @Override
- public void updateNotificationChannelFromAssistant(INotificationListener token, String pkg,
- NotificationChannel channel) throws RemoteException {
- ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token);
- Preconditions.checkNotNull(channel);
- int uid = mPackageManager.getPackageUid(pkg, 0, info.userid);
- updateNotificationChannelInt(pkg, uid, channel, true);
- }
-
- @Override
- public ParceledListSlice<NotificationChannel> getNotificationChannelsFromAssistant(
- INotificationListener token, String pkg) throws RemoteException {
- ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token);
- int uid = mPackageManager.getPackageUid(pkg, 0, info.userid);
- return mRankingHelper.getNotificationChannels(pkg, uid, false /* includeDeleted */);
- }
};
private void applyAdjustment(NotificationRecord n, Adjustment adjustment) {
@@ -2695,17 +2649,10 @@
}
if (adjustment.getSignals() != null) {
Bundle.setDefusable(adjustment.getSignals(), true);
- final String overrideChannelId =
- adjustment.getSignals().getString(Adjustment.KEY_CHANNEL_ID, null);
final ArrayList<String> people =
adjustment.getSignals().getStringArrayList(Adjustment.KEY_PEOPLE);
final ArrayList<SnoozeCriterion> snoozeCriterionList =
adjustment.getSignals().getParcelableArrayList(Adjustment.KEY_SNOOZE_CRITERIA);
- if (!TextUtils.isEmpty(overrideChannelId)) {
- n.updateNotificationChannel(mRankingHelper.getNotificationChannel(
- n.sbn.getPackageName(), n.sbn.getUid(), overrideChannelId,
- false /* includeDeleted */));
- }
n.setPeopleOverride(people);
n.setSnoozeCriteria(snoozeCriterionList);
}
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index e13df19..4d19b52 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -37,7 +37,6 @@
void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp);
void updateNotificationChannel(String pkg, int uid, NotificationChannel channel);
- void updateNotificationChannelFromAssistant(String pkg, int uid, NotificationChannel channel);
NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
void deleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 65aaee0..b63b05f 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -609,55 +609,6 @@
}
@Override
- public void updateNotificationChannelFromAssistant(String pkg, int uid,
- NotificationChannel updatedChannel) {
- Record r = getOrCreateRecord(pkg, uid);
- if (r == null) {
- throw new IllegalArgumentException("Invalid package");
- }
- NotificationChannel channel = r.channels.get(updatedChannel.getId());
- if (channel == null || channel.isDeleted()) {
- throw new IllegalArgumentException("Channel does not exist");
- }
-
- if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0) {
- channel.setImportance(updatedChannel.getImportance());
- }
- if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_LIGHTS) == 0) {
- channel.enableLights(updatedChannel.shouldShowLights());
- channel.setLightColor(updatedChannel.getLightColor());
- }
- if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_PRIORITY) == 0) {
- channel.setBypassDnd(updatedChannel.canBypassDnd());
- }
- if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_SOUND) == 0) {
- channel.setSound(updatedChannel.getSound(), updatedChannel.getAudioAttributes());
- }
- if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_VIBRATION) == 0) {
- channel.enableVibration(updatedChannel.shouldVibrate());
- channel.setVibrationPattern(updatedChannel.getVibrationPattern());
- }
- if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_VISIBILITY) == 0) {
- if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
- channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
- } else {
- channel.setLockscreenVisibility(updatedChannel.getLockscreenVisibility());
- }
- }
- if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_SHOW_BADGE) == 0) {
- channel.setShowBadge(updatedChannel.canShowBadge());
- }
- if (updatedChannel.isDeleted()) {
- channel.setDeleted(true);
- }
- // Assistant cannot change the group
-
- MetricsLogger.action(getChannelLog(channel, pkg));
- r.channels.put(channel.getId(), channel);
- updateConfig();
- }
-
- @Override
public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
boolean includeDeleted) {
Preconditions.checkNotNull(pkg);
diff --git a/services/core/java/com/android/server/pm/BasePermission.java b/services/core/java/com/android/server/pm/BasePermission.java
index 07c9dec..2100038 100644
--- a/services/core/java/com/android/server/pm/BasePermission.java
+++ b/services/core/java/com/android/server/pm/BasePermission.java
@@ -98,4 +98,8 @@
public boolean isInstant() {
return (protectionLevel & PermissionInfo.PROTECTION_FLAG_EPHEMERAL) != 0;
}
+
+ public boolean isRuntimeOnly() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0;
+ }
}
diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
index b0730ef..c4cc4fb 100644
--- a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
+++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
@@ -27,6 +27,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -47,7 +48,7 @@
* service and handling all interactions in a timely manner.
* @hide
*/
-final class EphemeralResolverConnection {
+final class EphemeralResolverConnection implements DeathRecipient {
// This is running in a critical section and the timeout must be sufficiently low
private static final long BIND_SERVICE_TIMEOUT_MS =
("eng".equals(Build.TYPE)) ? 300 : 200;
@@ -63,9 +64,10 @@
private volatile boolean mBindRequested;
private IInstantAppResolver mRemoteInstance;
- public EphemeralResolverConnection(Context context, ComponentName componentName) {
+ public EphemeralResolverConnection(
+ Context context, ComponentName componentName, String action) {
mContext = context;
- mIntent = new Intent(Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE).setComponent(componentName);
+ mIntent = new Intent(action).setComponent(componentName);
}
public final List<InstantAppResolveInfo> getInstantAppResolveInfoList(int hashPrefix[],
@@ -171,6 +173,15 @@
}
}
+ @Override
+ public void binderDied() {
+ if (mRemoteInstance != null) {
+ mRemoteInstance.asBinder().unlinkToDeath(this, 0 /*flags*/);
+ }
+ mRemoteInstance = null;
+ mBindRequested = false;
+ }
+
/**
* Asynchronous callback when results come back from ephemeral resolution phase two.
*/
@@ -183,7 +194,11 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
- mRemoteInstance = IInstantAppResolver.Stub.asInterface(service);
+ try {
+ service.linkToDeath(EphemeralResolverConnection.this, 0 /*flags*/);
+ mRemoteInstance = IInstantAppResolver.Stub.asInterface(service);
+ } catch (RemoteException e) {
+ }
mLock.notifyAll();
}
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 7eef7ad..9f7c4a2 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -66,6 +66,7 @@
public static final int FLAG_FREE_CACHE_V2 = 1 << 13;
public static final int FLAG_FREE_CACHE_V2_DEFY_QUOTA = 1 << 14;
public static final int FLAG_FREE_CACHE_NOOP = 1 << 15;
+ public static final int FLAG_FORCE = 1 << 16;
private final boolean mIsolated;
@@ -202,6 +203,15 @@
}
}
+ public void fixupAppData(String uuid, int flags) throws InstallerException {
+ if (!checkBeforeRemote()) return;
+ try {
+ mInstalld.fixupAppData(uuid, flags);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
public void moveCompleteApp(String fromUuid, String toUuid, String packageName,
String dataAppName, int appId, String seInfo, int targetSdkVersion)
throws InstallerException {
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 0ae5f31..89a303d 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -272,6 +272,7 @@
} else {
// Deleting an app prunes all instant state such as cookie
deleteDir(getInstantApplicationDir(pkg.packageName, userId));
+ mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
removeAppLPw(userId, ps.appId);
}
}
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index 59f8a2d..6f593b0 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -86,22 +86,18 @@
final List<InstantAppResolveInfo> instantAppResolveInfoList =
connection.getInstantAppResolveInfoList(shaPrefix, token);
- final AuxiliaryResolveInfo resolveInfo;
if (instantAppResolveInfoList == null || instantAppResolveInfoList.size() == 0) {
// No hash prefix match; there are no instant apps for this domain.
if (DEBUG_EPHEMERAL) {
Log.d(TAG, "No results returned");
}
- resolveInfo = null;
- } else {
- resolveInfo = InstantAppResolver.filterInstantAppIntent(instantAppResolveInfoList,
- intent, requestObj.resolvedType, requestObj.userId,
- intent.getPackage(), digest, token);
+ return null;
}
-
+ final AuxiliaryResolveInfo resolveInfo = InstantAppResolver.filterInstantAppIntent(
+ instantAppResolveInfoList, intent, requestObj.resolvedType, requestObj.userId,
+ intent.getPackage(), digest, token);
logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE, startTime, token,
- resolveInfo != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE);
-
+ RESOLUTION_SUCCESS);
return resolveInfo;
}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index d9ea728..781be34 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -22,6 +22,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageParser;
import android.os.Environment;
+import android.os.FileUtils;
import android.os.PowerManager;
import android.os.UserHandle;
import android.os.WorkSource;
@@ -151,6 +152,7 @@
// TODO(calin,jeffhao): shared library paths should be adjusted to include previous code
// paths (b/34169257).
final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries);
+ // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
final int dexoptFlags = getDexFlags(pkg, compilerFilter);
int result = DEX_OPT_SKIPPED;
@@ -254,17 +256,20 @@
@GuardedBy("mInstallLock")
private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path, Set<String> isas,
String compilerFilter, boolean isUsedByOtherApps) {
+ compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps);
+ // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
int dexoptFlags = getDexFlags(info, compilerFilter) | DEXOPT_SECONDARY_DEX;
// Check the app storage and add the appropriate flags.
- if (info.dataDir.equals(info.deviceProtectedDataDir)) {
+ if (info.deviceProtectedDataDir != null &&
+ FileUtils.contains(info.deviceProtectedDataDir, path)) {
dexoptFlags |= DEXOPT_STORAGE_DE;
- } else if (info.dataDir.equals(info.credentialProtectedDataDir)) {
+ } else if (info.credentialProtectedDataDir != null &&
+ FileUtils.contains(info.credentialProtectedDataDir, path)) {
dexoptFlags |= DEXOPT_STORAGE_CE;
} else {
Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName);
return DEX_OPT_FAILED;
}
- compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps);
Log.d(TAG, "Running dexopt on: " + path
+ " pkg=" + info.packageName + " isa=" + isas
+ " dexoptFlags=" + printDexoptFlags(dexoptFlags)
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 578b10a..01847ae 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -608,6 +608,10 @@
final boolean mIsPreNUpgrade;
final boolean mIsPreNMR1Upgrade;
+ // Have we told the Activity Manager to whitelist the default container service by uid yet?
+ @GuardedBy("mPackages")
+ boolean mDefaultContainerWhitelisted = false;
+
@GuardedBy("mPackages")
private boolean mDexOptDialogShown;
@@ -2052,6 +2056,7 @@
}
if (bp != null && (bp.isRuntime() || bp.isDevelopment())
&& (!instantApp || bp.isInstant())
+ && (supportsRuntimePermissions || !bp.isRuntimeOnly())
&& (grantedPermissions == null
|| ArrayUtils.contains(grantedPermissions, permission))) {
final int flags = permissionsState.getPermissionFlags(permission, userId);
@@ -2717,6 +2722,15 @@
UserHandle.USER_SYSTEM, storageFlags, true /* migrateAppData */,
true /* onlyCoreApps */);
mPrepareAppDataFuture = SystemServerInitThreadPool.get().submit(() -> {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "fixup");
+ try {
+ mInstaller.fixupAppData(StorageManager.UUID_PRIVATE_INTERNAL,
+ StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
+ } catch (InstallerException e) {
+ Slog.w(TAG, "Trouble fixing GIDs", e);
+ }
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+
if (deferPackages == null || deferPackages.isEmpty()) {
return;
}
@@ -2800,15 +2814,17 @@
}
mInstallerService = new PackageInstallerService(context, this);
- final ComponentName ephemeralResolverComponent = getEphemeralResolverLPr();
- if (ephemeralResolverComponent != null) {
+ final Pair<ComponentName, String> instantAppResolverComponent =
+ getInstantAppResolverLPr();
+ if (instantAppResolverComponent != null) {
if (DEBUG_EPHEMERAL) {
- Slog.d(TAG, "Set ephemeral resolver: " + ephemeralResolverComponent);
+ Slog.d(TAG, "Set ephemeral resolver: " + instantAppResolverComponent);
}
- mInstantAppResolverConnection =
- new EphemeralResolverConnection(mContext, ephemeralResolverComponent);
+ mInstantAppResolverConnection = new EphemeralResolverConnection(
+ mContext, instantAppResolverComponent.first,
+ instantAppResolverComponent.second);
mInstantAppResolverSettingsComponent =
- getEphemeralResolverSettingsLPr(ephemeralResolverComponent);
+ getInstantAppResolverSettingsLPr(instantAppResolverComponent.first);
} else {
mInstantAppResolverConnection = null;
mInstantAppResolverSettingsComponent = null;
@@ -2855,7 +2871,7 @@
private void updateInstantAppInstallerLocked() {
final ComponentName oldInstantAppInstallerComponent = mInstantAppInstallerComponent;
- final ActivityInfo newInstantAppInstaller = getEphemeralInstallerLPr();
+ final ActivityInfo newInstantAppInstaller = getInstantAppInstallerLPr();
ComponentName newInstantAppInstallerComponent = newInstantAppInstaller == null
? null : newInstantAppInstaller.getComponentName();
@@ -3032,7 +3048,7 @@
}
}
- private @Nullable ComponentName getEphemeralResolverLPr() {
+ private @Nullable Pair<ComponentName, String> getInstantAppResolverLPr() {
final String[] packageArray =
mContext.getResources().getStringArray(R.array.config_ephemeralResolverPackage);
if (packageArray.length == 0 && !Build.IS_DEBUGGABLE) {
@@ -3047,10 +3063,20 @@
MATCH_DIRECT_BOOT_AWARE
| MATCH_DIRECT_BOOT_UNAWARE
| (!Build.IS_DEBUGGABLE ? MATCH_SYSTEM_ONLY : 0);
- final Intent resolverIntent = new Intent(Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE);
- final List<ResolveInfo> resolvers = queryIntentServicesInternal(resolverIntent, null,
+ String actionName = Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE;
+ final Intent resolverIntent = new Intent(actionName);
+ List<ResolveInfo> resolvers = queryIntentServicesInternal(resolverIntent, null,
resolveFlags, UserHandle.USER_SYSTEM, callingUid, false /*includeInstantApps*/);
-
+ // temporarily look for the old action
+ if (resolvers.size() == 0) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Ephemeral resolver not found with new action; try old one");
+ }
+ actionName = Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE;
+ resolverIntent.setAction(actionName);
+ resolvers = queryIntentServicesInternal(resolverIntent, null,
+ resolveFlags, UserHandle.USER_SYSTEM, callingUid, false /*includeInstantApps*/);
+ }
final int N = resolvers.size();
if (N == 0) {
if (DEBUG_EPHEMERAL) {
@@ -3080,7 +3106,7 @@
Slog.v(TAG, "Ephemeral resolver found;"
+ " pkg: " + packageName + ", info:" + info);
}
- return new ComponentName(packageName, info.serviceInfo.name);
+ return new Pair<>(new ComponentName(packageName, info.serviceInfo.name), actionName);
}
if (DEBUG_EPHEMERAL) {
Slog.v(TAG, "Ephemeral resolver NOT found");
@@ -3088,8 +3114,8 @@
return null;
}
- private @Nullable ActivityInfo getEphemeralInstallerLPr() {
- final Intent intent = new Intent(Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE);
+ private @Nullable ActivityInfo getInstantAppInstallerLPr() {
+ final Intent intent = new Intent(Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE);
@@ -3097,8 +3123,17 @@
MATCH_DIRECT_BOOT_AWARE
| MATCH_DIRECT_BOOT_UNAWARE
| (!Build.IS_DEBUGGABLE ? MATCH_SYSTEM_ONLY : 0);
- final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE,
+ List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE,
resolveFlags, UserHandle.USER_SYSTEM);
+ // temporarily look for the old action
+ if (matches.isEmpty()) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Ephemeral installer not found with new action; try old one");
+ }
+ intent.setAction(Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE);
+ matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE,
+ resolveFlags, UserHandle.USER_SYSTEM);
+ }
Iterator<ResolveInfo> iter = matches.iterator();
while (iter.hasNext()) {
final ResolveInfo rInfo = iter.next();
@@ -3121,14 +3156,23 @@
}
}
- private @Nullable ComponentName getEphemeralResolverSettingsLPr(
+ private @Nullable ComponentName getInstantAppResolverSettingsLPr(
@NonNull ComponentName resolver) {
- final Intent intent = new Intent(Intent.ACTION_EPHEMERAL_RESOLVER_SETTINGS)
+ final Intent intent = new Intent(Intent.ACTION_INSTANT_APP_RESOLVER_SETTINGS)
.addCategory(Intent.CATEGORY_DEFAULT)
.setPackage(resolver.getPackageName());
final int resolveFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
- final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null, resolveFlags,
+ List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null, resolveFlags,
UserHandle.USER_SYSTEM);
+ // temporarily look for the old action
+ if (matches.isEmpty()) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Ephemeral resolver settings not found with new action; try old one");
+ }
+ intent.setAction(Intent.ACTION_EPHEMERAL_RESOLVER_SETTINGS);
+ matches = queryIntentActivitiesInternal(intent, null, resolveFlags,
+ UserHandle.USER_SYSTEM);
+ }
if (matches.isEmpty()) {
return null;
}
@@ -5714,20 +5758,23 @@
synchronized (mPackages) {
final int count = (resolvedActivities == null ? 0 : resolvedActivities.size());
for (int n = 0; n < count; n++) {
- ResolveInfo info = resolvedActivities.get(n);
- String packageName = info.activityInfo.packageName;
- PackageSetting ps = mSettings.mPackages.get(packageName);
+ final ResolveInfo info = resolvedActivities.get(n);
+ final String packageName = info.activityInfo.packageName;
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps != null) {
- // Try to get the status from User settings first
- long packedStatus = getDomainVerificationStatusLPr(ps, userId);
- int status = (int) (packedStatus >> 32);
- if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
+ // only check domain verification status if the app is not a browser
+ if (!info.handleAllWebDataURI) {
+ // Try to get the status from User settings first
+ final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
+ final int status = (int) (packedStatus >> 32);
+ if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
|| status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
- if (DEBUG_EPHEMERAL) {
- Slog.v(TAG, "DENY ephemeral apps;"
- + " pkg: " + packageName + ", status: " + status);
+ if (DEBUG_EPHEMERAL) {
+ Slog.v(TAG, "DENY instant app;"
+ + " pkg: " + packageName + ", status: " + status);
+ }
+ return false;
}
- return false;
}
if (ps.getInstantApp(userId)) {
if (DEBUG_EPHEMERAL) {
@@ -11369,6 +11416,8 @@
for (int i=0; i<N; i++) {
final String name = pkg.requestedPermissions.get(i);
final BasePermission bp = mSettings.mPermissions.get(name);
+ final boolean appSupportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
+ >= Build.VERSION_CODES.M;
if (DEBUG_INSTALL) {
Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp);
@@ -11390,6 +11439,12 @@
continue;
}
+ if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
+ Log.i(TAG, "Denying runtime-only permission " + bp.name + " for package "
+ + pkg.packageName);
+ continue;
+ }
+
final String perm = bp.name;
boolean allowedSig = false;
int grant = GRANT_DENIED;
@@ -11405,8 +11460,6 @@
}
final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
- final boolean appSupportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
- >= Build.VERSION_CODES.M;
switch (level) {
case PermissionInfo.PROTECTION_NORMAL: {
// For all apps normal permissions are install time ones.
@@ -13015,7 +13068,18 @@
intent.setComponent(DEFAULT_CONTAINER_COMPONENT);
IActivityManager am = ActivityManager.getService();
if (am != null) {
+ int dcsUid = -1;
+ synchronized (mPackages) {
+ if (!mDefaultContainerWhitelisted) {
+ mDefaultContainerWhitelisted = true;
+ PackageSetting ps = mSettings.mPackages.get(DEFAULT_CONTAINER_PACKAGE);
+ dcsUid = UserHandle.getUid(UserHandle.USER_SYSTEM, ps.appId);
+ }
+ }
try {
+ if (dcsUid > 0) {
+ am.backgroundWhitelistUid(dcsUid);
+ }
am.startService(null, intent, null, -1, null, false, mContext.getOpPackageName(),
UserHandle.USER_SYSTEM);
} catch (RemoteException e) {
@@ -17634,6 +17698,7 @@
int removedAppId = -1;
int[] origUsers;
int[] removedUsers = null;
+ int[] broadcastUsers = null;
SparseArray<Integer> installReasons;
boolean isRemovedPackageSystemUpdate = false;
boolean isUpdate;
@@ -17707,16 +17772,16 @@
extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, removedForAllUsers);
if (removedPackage != null) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
- extras, 0, null, null, removedUsers);
+ extras, 0, null, null, broadcastUsers);
if (dataRemoved && !isRemovedPackageSystemUpdate) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED,
removedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
- null, null, removedUsers);
+ null, null, broadcastUsers);
}
}
if (removedAppId >= 0) {
sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, 0, null, null,
- removedUsers);
+ broadcastUsers);
}
}
}
@@ -17745,6 +17810,20 @@
outInfo.removedUsers = deletedPs != null
? deletedPs.queryInstalledUsers(sUserManager.getUserIds(), true)
: null;
+ if (outInfo.removedUsers == null) {
+ outInfo.broadcastUsers = null;
+ } else {
+ outInfo.broadcastUsers = EMPTY_INT_ARRAY;
+ int[] allUsers = outInfo.removedUsers;
+ for (int i = allUsers.length - 1; i >= 0; --i) {
+ final int userId = allUsers[i];
+ if (deletedPs.getInstantApp(userId)) {
+ continue;
+ }
+ outInfo.broadcastUsers =
+ ArrayUtils.appendInt(outInfo.broadcastUsers, userId);
+ }
+ }
}
}
@@ -20853,13 +20932,6 @@
mSettings.dumpRestoredPermissionGrantsLPr(pw, dumpState);
}
- if (!checkin && dumpState.isDumping(DumpState.DUMP_INSTALLS) && packageName == null) {
- // XXX should handle packageName != null by dumping only install data that
- // the given package is involved with.
- if (dumpState.onTitlePrinted()) pw.println();
- mInstallerService.dump(new IndentingPrintWriter(pw, " ", 120));
- }
-
if (!checkin && dumpState.isDumping(DumpState.DUMP_FROZEN) && packageName == null) {
// XXX should handle packageName != null by dumping only install data that
// the given package is involved with.
@@ -20930,6 +21002,14 @@
}
}
}
+
+ // PackageInstaller should be called outside of mPackages lock
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_INSTALLS) && packageName == null) {
+ // XXX should handle packageName != null by dumping only install data that
+ // the given package is involved with.
+ if (dumpState.onTitlePrinted()) pw.println();
+ mInstallerService.dump(new IndentingPrintWriter(pw, " ", 120));
+ }
}
private void dumpProto(FileDescriptor fd) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 6fb056a..554deae 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4777,7 +4777,7 @@
pw.print(" notLaunched=");
pw.print(ps.getNotLaunched(user.id));
pw.print(" enabled=");
- pw.println(ps.getEnabled(user.id));
+ pw.print(ps.getEnabled(user.id));
pw.print(" instant=");
pw.println(ps.getInstantApp(user.id));
String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index c693a47..3d7cedc 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -19,7 +19,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageParser;
+import android.os.FileUtils;
import android.os.RemoteException;
import android.os.storage.StorageManager;
import android.os.UserHandle;
@@ -93,7 +93,7 @@
* Note that this method is invoked when apps load dex files and it should
* return as fast as possible.
*
- * @param loadingPackage the package performing the load
+ * @param loadingAppInfo the package performing the load
* @param dexPaths the list of dex files being loaded
* @param loaderIsa the ISA of the app loading the dex files
* @param loaderUserId the user id which runs the code loading the dex files
@@ -191,8 +191,7 @@
throw new IllegalArgumentException(
"notifyPackageInstalled called with USER_ALL");
}
- cachePackageCodeLocation(pi.packageName, pi.applicationInfo.sourceDir,
- pi.applicationInfo.splitSourceDirs, pi.applicationInfo.dataDir, userId);
+ cachePackageInfo(pi, userId);
}
/**
@@ -231,13 +230,32 @@
}
}
- public void cachePackageCodeLocation(String packageName, String baseCodePath,
- String[] splitCodePaths, String dataDir, int userId) {
+ /**
+ * Caches the code location from the given package info.
+ */
+ private void cachePackageInfo(PackageInfo pi, int userId) {
+ ApplicationInfo ai = pi.applicationInfo;
+ String[] dataDirs = new String[] {ai.dataDir, ai.deviceProtectedDataDir,
+ ai.credentialProtectedDataDir};
+ cachePackageCodeLocation(pi.packageName, ai.sourceDir, ai.splitSourceDirs,
+ dataDirs, userId);
+ }
+
+ private void cachePackageCodeLocation(String packageName, String baseCodePath,
+ String[] splitCodePaths, String[] dataDirs, int userId) {
PackageCodeLocations pcl = putIfAbsent(mPackageCodeLocationsCache, packageName,
new PackageCodeLocations(packageName, baseCodePath, splitCodePaths));
pcl.updateCodeLocation(baseCodePath, splitCodePaths);
- if (dataDir != null) {
- pcl.mergeAppDataDirs(dataDir, userId);
+ if (dataDirs != null) {
+ for (String dataDir : dataDirs) {
+ // The set of data dirs includes deviceProtectedDataDir and
+ // credentialProtectedDataDir which might be null for shared
+ // libraries. Currently we don't track these but be lenient
+ // and check in case we ever decide to store their usage data.
+ if (dataDir != null) {
+ pcl.mergeAppDataDirs(dataDir, userId);
+ }
+ }
}
}
@@ -250,8 +268,7 @@
int userId = entry.getKey();
for (PackageInfo pi : packageInfoList) {
// Cache the code locations.
- cachePackageCodeLocation(pi.packageName, pi.applicationInfo.sourceDir,
- pi.applicationInfo.splitSourceDirs, pi.applicationInfo.dataDir, userId);
+ cachePackageInfo(pi, userId);
// Cache a map from package name to the set of user ids who installed the package.
// We will use it to sync the data and remove obsolete entries from
@@ -329,6 +346,7 @@
mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
continue;
}
+
int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps());
success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
@@ -350,7 +368,7 @@
// Nothing to reconcile.
return;
}
- Set<String> dexFilesToRemove = new HashSet<>();
+
boolean updated = false;
for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
String dexPath = entry.getKey();
@@ -378,14 +396,16 @@
}
ApplicationInfo info = pkg.applicationInfo;
int flags = 0;
- if (info.dataDir.equals(info.deviceProtectedDataDir)) {
+ if (info.deviceProtectedDataDir != null &&
+ FileUtils.contains(info.deviceProtectedDataDir, dexPath)) {
flags |= StorageManager.FLAG_STORAGE_DE;
- } else if (info.dataDir.equals(info.credentialProtectedDataDir)) {
+ } else if (info.credentialProtectedDataDir!= null &&
+ FileUtils.contains(info.credentialProtectedDataDir, dexPath)) {
flags |= StorageManager.FLAG_STORAGE_CE;
} else {
- Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName);
- updated = mPackageDexUsage.removeUserPackage(
- packageName, dexUseInfo.getOwnerUserId()) || updated;
+ Slog.e(TAG, "Could not infer CE/DE storage for path " + dexPath);
+ updated = mPackageDexUsage.removeDexFile(
+ packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
continue;
}
diff --git a/services/core/java/com/android/server/vr/CompatibilityDisplay.java b/services/core/java/com/android/server/vr/CompatibilityDisplay.java
index ae1d50f..772fc26 100644
--- a/services/core/java/com/android/server/vr/CompatibilityDisplay.java
+++ b/services/core/java/com/android/server/vr/CompatibilityDisplay.java
@@ -95,20 +95,15 @@
* Creates and Destroys the virtual display depending on the current state of VrMode.
*/
private void updateVirtualDisplay() {
- boolean createVirtualDisplay = "true".equals(SystemProperties.get("vr_virtualdisplay"));
if (DEBUG) {
- Log.i(TAG, "isVrMode: " + mIsVrModeEnabled + ", createVD: " + createVirtualDisplay +
- ", override: " + mIsVrModeOverrideEnabled);
+ Log.i(TAG, "isVrMode: " + mIsVrModeEnabled + ", override: " + mIsVrModeOverrideEnabled);
}
- if (mIsVrModeEnabled || (createVirtualDisplay && mIsVrModeOverrideEnabled)) {
+ if (mIsVrModeEnabled || mIsVrModeOverrideEnabled) {
// TODO: Consider not creating the display until ActivityManager needs one on
// which to display a 2D application.
- // TODO: STOPSHIP Remove createVirtualDisplay conditional before launching.
- if (createVirtualDisplay) {
- startVirtualDisplay();
- startImageReader();
- }
+ startVirtualDisplay();
+ startImageReader();
} else {
// Stop virtual display to test exit condition
stopVirtualDisplay();
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 1decf4e..a8664a5 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -505,6 +505,13 @@
getController().removeStartingWindow();
}
+ // If this window was animating, then we need to ensure that the app transition notifies
+ // that animations have completed in WMS.handleAnimatingStoppedAndTransitionLocked(), so
+ // add to that list now
+ if (mAppAnimator.animating) {
+ mService.mNoAnimationNotifyOnTransitionFinished.add(token);
+ }
+
final TaskStack stack = getTask().mStack;
if (delayed && !isEmpty()) {
// set the token aside because it has an active animation to be finished
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index 9f0ed21..7b8057ca 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -61,17 +61,21 @@
extends WindowManagerInternal.AppTransitionListener implements Runnable {
public void onAppTransitionCancelledLocked() {
+ if (DEBUG) Slog.d(TAG, "onAppTransitionCancelledLocked:"
+ + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition);
animationFinished();
}
public void onAppTransitionFinishedLocked(IBinder token) {
+ if (DEBUG) Slog.d(TAG, "onAppTransitionFinishedLocked:"
+ + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition);
animationFinished();
}
private void animationFinished() {
if (mFinishAnimationAfterTransition) {
mHandler.removeCallbacks(this);
- // This might end up calling into activity manager which will be bad since we have the
- // window manager lock held at this point. Post a message to take care of the processing
- // so we don't deadlock.
+ // This might end up calling into activity manager which will be bad since we have
+ // the window manager lock held at this point. Post a message to take care of the
+ // processing so we don't deadlock.
mHandler.post(this);
}
}
@@ -195,6 +199,7 @@
if (!mTarget.setPinnedStackSize(mTmpRect, mTmpTaskBounds)) {
// Whoops, the target doesn't feel like animating anymore. Let's immediately finish
// any further animation.
+ if (DEBUG) Slog.d(TAG, "animateUpdate: cancelled");
animation.cancel();
}
}
@@ -203,7 +208,9 @@
public void onAnimationEnd(Animator animation) {
if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
+ " mMoveToFullScreen=" + mMoveToFullScreen
- + " mSkipAnimationEnd=" + mSkipAnimationEnd);
+ + " mSkipAnimationEnd=" + mSkipAnimationEnd
+ + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition
+ + " mAppTransitionIsRunning=" + mAppTransition.isRunning());
// There could be another animation running. For example in the
// move to fullscreen case, recents will also be closing while the
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 3cb96a1..ee2d5de 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -441,6 +441,8 @@
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken);
appAnimator.clearThumbnail();
appAnimator.setNullAnimation();
+ // TODO: Do we need to add to mNoAnimationNotifyOnTransitionFinished like above if not
+ // animating?
wtoken.setVisibility(animLp, false, transit, false, voiceInteraction);
wtoken.updateReportedVisibilityLocked();
// Force the allDrawn flag, because we want to start
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a5eac46..63af2da 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -668,6 +668,7 @@
VibratorService vibrator = null;
IStorageManager storageManager = null;
NetworkManagementService networkManagement = null;
+ IpSecService ipSecService = null;
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
ConnectivityService connectivity = null;
@@ -1015,6 +1016,15 @@
reportWtf("starting NetworkManagement Service", e);
}
traceEnd();
+
+ traceBeginAndSlog("StartIpSecService");
+ try {
+ ipSecService = IpSecService.create(context);
+ ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService);
+ } catch (Throwable e) {
+ reportWtf("starting IpSec Service", e);
+ }
+ traceEnd();
}
if (!disableNonCoreServices && !disableTextServices) {
@@ -1628,6 +1638,7 @@
final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
final MediaRouterService mediaRouterF = mediaRouter;
final MmsServiceBroker mmsServiceF = mmsService;
+ final IpSecService ipSecServiceF = ipSecService;
// We now tell the activity manager it is okay to run third party
// code. It will call back into us once it has gotten to the state
@@ -1691,6 +1702,13 @@
.networkScoreAndNetworkManagementServiceReady();
}
traceEnd();
+ traceBeginAndSlog("MakeIpSecServiceReady");
+ try {
+ if (ipSecServiceF != null) ipSecServiceF.systemReady();
+ } catch (Throwable e) {
+ reportWtf("making IpSec Service ready", e);
+ }
+ traceEnd();
traceBeginAndSlog("MakeNetworkStatsServiceReady");
try {
if (networkStatsF != null) networkStatsF.systemReady();
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 590bce1..61a9294 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -23,7 +23,6 @@
import android.net.apf.ApfCapabilities;
import android.net.apf.ApfFilter;
import android.net.DhcpResults;
-import android.net.INetd;
import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -35,12 +34,10 @@
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.IpManagerEvent;
import android.net.util.MultinetworkPolicyTracker;
-import android.net.util.NetdService;
import android.os.INetworkManagementService;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.LocalLog;
@@ -1030,16 +1027,14 @@
private boolean startIPv6() {
// Set privacy extensions.
- final String PREFER_TEMPADDRS = "2";
try {
- NetdService.run((INetd netd) -> {
- netd.setProcSysNet(
- INetd.IPV6, INetd.CONF, mInterfaceName, "use_tempaddr",
- PREFER_TEMPADDRS);
- });
+ mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
mNwService.enableIpv6(mInterfaceName);
- } catch (IllegalStateException|RemoteException|ServiceSpecificException e) {
- logError("Unable to change interface settings: %s", e);
+ } catch (RemoteException re) {
+ logError("Unable to change interface settings: %s", re);
+ return false;
+ } catch (IllegalStateException ie) {
+ logError("Unable to change interface settings: %s", ie);
return false;
}
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 40af2f8..ad593be 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -497,149 +497,6 @@
new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true);
}
- @Test
- public void testUpdate_userLockedImportance() throws Exception {
- // all fields locked by user
- final NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
-
- mHelper.createNotificationChannel(PKG, UID, channel, false);
-
- // same id, try to update
- final NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
-
- mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
-
- // no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
- }
-
- @Test
- public void testUpdate_userLockedVisibility() throws Exception {
- // all fields locked by user
- final NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
-
- mHelper.createNotificationChannel(PKG, UID, channel, false);
-
- // same id, try to update
- final NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
- channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
-
- mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
-
- // no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
- }
-
- @Test
- public void testUpdate_userLockedVibration() throws Exception {
- // all fields locked by user
- final NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.enableLights(false);
- channel.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
-
- mHelper.createNotificationChannel(PKG, UID, channel, false);
-
- // same id, try to update
- final NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
- channel2.enableVibration(true);
- channel2.setVibrationPattern(new long[]{100});
-
- mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
-
- // no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
- }
-
- @Test
- public void testUpdate_userLockedLights() throws Exception {
- // all fields locked by user
- final NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.enableLights(false);
- channel.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
-
- mHelper.createNotificationChannel(PKG, UID, channel, false);
-
- // same id, try to update
- final NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
- channel2.enableLights(true);
-
- mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
-
- // no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
- }
-
- @Test
- public void testUpdate_userLockedPriority() throws Exception {
- // all fields locked by user
- final NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.setBypassDnd(true);
- channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
-
- mHelper.createNotificationChannel(PKG, UID, channel, false);
-
- // same id, try to update all fields
- final NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
- channel2.setBypassDnd(false);
-
- mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
-
- // no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
- }
-
- @Test
- public void testUpdate_userLockedRingtone() throws Exception {
- // all fields locked by user
- final NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
- channel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
-
- mHelper.createNotificationChannel(PKG, UID, channel, false);
-
- // same id, try to update all fields
- final NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
- channel2.setSound(new Uri.Builder().scheme("test2").build(), mAudioAttributes);
-
- mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
-
- // no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
- }
-
- @Test
- public void testUpdate_userLockedBadge() throws Exception {
- final NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.setShowBadge(true);
- channel.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
-
- mHelper.createNotificationChannel(PKG, UID, channel, false);
-
- final NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
- channel2.setShowBadge(false);
-
- mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
-
- // no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
- }
@Test
public void testUpdate() throws Exception {
@@ -816,30 +673,6 @@
}
@Test
- public void testUpdateDeletedChannels() throws Exception {
- NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(PKG, UID, channel, true);
-
- mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
-
- channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
- try {
- mHelper.updateNotificationChannel(PKG, UID, channel);
- fail("Updated deleted channel");
- } catch (IllegalArgumentException e) {
- // :)
- }
-
- try {
- mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel);
- fail("Updated deleted channel");
- } catch (IllegalArgumentException e) {
- // :)
- }
- }
-
- @Test
public void testCreateDeletedChannel() throws Exception {
long[] vibration = new long[]{100, 67, 145, 156};
NotificationChannel channel =
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 6c7f146..205c8de 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -52,20 +52,6 @@
<application>
<uses-library android:name="android.test.runner" />
- <service android:name="com.android.server.AccessibilityManagerServiceTest$MyFirstMockAccessibilityService"
- android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
- <intent-filter>
- <action android:name="android.accessibilityservice.AccessibilityService"/>
- </intent-filter>
- </service>
-
- <service android:name="com.android.server.AccessibilityManagerServiceTest$MySecondMockAccessibilityService"
- android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
- <intent-filter>
- <action android:name="android.accessibilityservice.AccessibilityService"/>
- </intent-filter>
- </service>
-
<service android:name="com.android.server.accounts.TestAccountType1AuthenticatorService"
android:exported="false">
<intent-filter>
diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java
deleted file mode 100644
index 340c624..0000000
--- a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java
+++ /dev/null
@@ -1,762 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.accessibilityservice.AccessibilityService;
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ServiceInfo;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.IAccessibilityManager;
-import android.view.accessibility.IAccessibilityManagerClient;
-
-import com.android.internal.util.IntPair;
-
-/**
- * This test exercises the
- * {@link com.android.server.accessibility.AccessibilityManagerService} by mocking the
- * {@link android.view.accessibility.AccessibilityManager} which talks to to the
- * service. The service itself is interacting with the platform. Note: Testing
- * the service in full isolation would require significant amount of work for
- * mocking all system interactions. It would also require a lot of mocking code.
- */
-public class AccessibilityManagerServiceTest extends AndroidTestCase {
-
- /**
- * Timeout required for pending Binder calls or event processing to
- * complete.
- */
- private static final long TIMEOUT_BINDER_CALL = 100;
-
- /**
- * Timeout in which we are waiting for the system to start the mock
- * accessibility services.
- */
- private static final long TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES = 1000;
-
- /**
- * Timeout used for testing that a service is notified only upon a
- * notification timeout.
- */
- private static final long TIMEOUT_TEST_NOTIFICATION_TIMEOUT = 300;
-
- /**
- * The interface used to talk to the tested service.
- */
- private IAccessibilityManager mManagerService;
-
- @Override
- protected void setUp() throws Exception {
- // Reset the state.
- ensureOnlyMockServicesEnabled(getContext(), false, false);
- }
-
- @Override
- public void setContext(Context context) {
- super.setContext(context);
- if (MyFirstMockAccessibilityService.sComponentName == null) {
- MyFirstMockAccessibilityService.sComponentName = new ComponentName(
- context.getPackageName(), MyFirstMockAccessibilityService.class.getName())
- .flattenToShortString();
- }
- if (MySecondMockAccessibilityService.sComponentName == null) {
- MySecondMockAccessibilityService.sComponentName = new ComponentName(
- context.getPackageName(), MySecondMockAccessibilityService.class.getName())
- .flattenToShortString();
- }
- }
-
- /**
- * Creates a new instance.
- */
- public AccessibilityManagerServiceTest() {
- IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
- mManagerService = IAccessibilityManager.Stub.asInterface(iBinder);
- }
-
- @LargeTest
- public void testAddClient_AccessibilityDisabledThenEnabled() throws Exception {
- // at least some service must be enabled, otherwise accessibility will always be disabled.
- ensureOnlyMockServicesEnabled(mContext, true, false);
-
- // make sure accessibility is disabled
- ensureAccessibilityEnabled(mContext, false);
-
- // create a client mock instance
- MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient();
-
- // invoke the method under test
- final int stateFlagsDisabled =
- IntPair.first(mManagerService.addClient(mockClient, UserHandle.USER_CURRENT));
- boolean enabledAccessibilityDisabled =
- (stateFlagsDisabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
-
- // check expected result
- assertFalse("The client must be disabled since accessibility is disabled.",
- enabledAccessibilityDisabled);
-
- // enable accessibility
- ensureAccessibilityEnabled(mContext, true);
-
- // invoke the method under test
- final int stateFlagsEnabled =
- IntPair.first(mManagerService.addClient(mockClient, UserHandle.USER_CURRENT));
- boolean enabledAccessibilityEnabled =
- (stateFlagsEnabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
-
- // check expected result
- assertTrue("The client must be enabled since accessibility is enabled.",
- enabledAccessibilityEnabled);
- }
-
- @LargeTest
- public void testAddClient_AccessibilityEnabledThenDisabled() throws Exception {
- // at least some service must be enabled, otherwise accessibility will always be disabled.
- ensureOnlyMockServicesEnabled(mContext, true, false);
-
- // enable accessibility before registering the client
- ensureAccessibilityEnabled(mContext, true);
-
- // create a client mock instance
- MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient();
-
- // invoke the method under test
- final int stateFlagsEnabled =
- IntPair.first(mManagerService.addClient(mockClient, UserHandle.USER_CURRENT));
- boolean enabledAccessibilityEnabled =
- (stateFlagsEnabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
-
- // check expected result
- assertTrue("The client must be enabled since accessibility is enabled.",
- enabledAccessibilityEnabled);
-
- // disable accessibility
- ensureAccessibilityEnabled(mContext, false);
-
- // invoke the method under test
- final int stateFlagsDisabled =
- IntPair.first(mManagerService.addClient(mockClient, UserHandle.USER_CURRENT));
- boolean enabledAccessibilityDisabled =
- (stateFlagsDisabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
-
- // check expected result
- assertFalse("The client must be disabled since accessibility is disabled.",
- enabledAccessibilityDisabled);
- }
-
- @LargeTest
- public void testGetAccessibilityServicesList() throws Exception {
- boolean firstMockServiceInstalled = false;
- boolean secondMockServiceInstalled = false;
-
- String packageName = getContext().getPackageName();
- String firstMockServiceClassName = MyFirstMockAccessibilityService.class.getName();
- String secondMockServiceClassName = MySecondMockAccessibilityService.class.getName();
-
- // look for the two mock services
- for (AccessibilityServiceInfo info : mManagerService.getInstalledAccessibilityServiceList(
- UserHandle.USER_CURRENT)) {
- ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
- if (packageName.equals(serviceInfo.packageName)) {
- if (firstMockServiceClassName.equals(serviceInfo.name)) {
- firstMockServiceInstalled = true;
- } else if (secondMockServiceClassName.equals(serviceInfo.name)) {
- secondMockServiceInstalled = true;
- }
- }
- }
-
- // check expected result
- assertTrue("First mock service must be installed", firstMockServiceInstalled);
- assertTrue("Second mock service must be installed", secondMockServiceInstalled);
- }
-
- @LargeTest
- public void testSendAccessibilityEvent_OneService_MatchingPackageAndEventType()
- throws Exception {
- // enable the mock accessibility service
- ensureOnlyMockServicesEnabled(mContext, true, false);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the mock service
- MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance;
- service.setServiceInfo(MockAccessibilityService.createDefaultInfo());
-
- // wait for the binder call to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // create and populate an event to be sent
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(sentEvent);
-
- // set expectations
- service.expectEvent(sentEvent);
- service.replay();
-
- // send the event
- mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
- }
-
- @LargeTest
- public void testSendAccessibilityEvent_OneService_NotMatchingPackage() throws Exception {
- // enable the mock accessibility service
- ensureOnlyMockServicesEnabled(mContext, true, false);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the mock service
- MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance;
- service.setServiceInfo(MockAccessibilityService.createDefaultInfo());
-
- // wait for the binder call to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // create and populate an event to be sent
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(sentEvent);
- sentEvent.setPackageName("no.service.registered.for.this.package");
-
- // set expectations
- service.replay();
-
- // send the event
- mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
- }
-
- @LargeTest
- public void testSendAccessibilityEvent_OneService_NotMatchingEventType() throws Exception {
- // enable the mock accessibility service
- ensureOnlyMockServicesEnabled(mContext, true, false);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the mock service
- MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance;
- service.setServiceInfo(MockAccessibilityService.createDefaultInfo());
-
- // wait for the binder call to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // create and populate an event to be sent
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(sentEvent);
- sentEvent.setEventType(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
-
- // set expectations
- service.replay();
-
- // send the event
- mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
- }
-
- @LargeTest
- public void testSendAccessibilityEvent_OneService_NotificationAfterTimeout() throws Exception {
- // enable the mock accessibility service
- ensureOnlyMockServicesEnabled(mContext, true, false);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the mock service
- MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance;
- AccessibilityServiceInfo info = MockAccessibilityService.createDefaultInfo();
- info.notificationTimeout = TIMEOUT_TEST_NOTIFICATION_TIMEOUT;
- service.setServiceInfo(info);
-
- // wait for the binder call to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // create and populate the first event to be sent
- AccessibilityEvent firstEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(firstEvent);
-
- // create and populate the second event to be sent
- AccessibilityEvent secondEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(secondEvent);
-
- // set expectations
- service.expectEvent(secondEvent);
- service.replay();
-
- // send the events
- mManagerService.sendAccessibilityEvent(firstEvent, UserHandle.USER_CURRENT);
- mManagerService.sendAccessibilityEvent(secondEvent, UserHandle.USER_CURRENT);
-
- // wait for #sendAccessibilityEvent to reach the backing service
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- try {
- service.verify();
- fail("No events must be dispatched before the expiration of the notification timeout.");
- } catch (IllegalStateException ise) {
- /* expected */
- }
-
- // wait for the configured notification timeout to expire
- Thread.sleep(TIMEOUT_TEST_NOTIFICATION_TIMEOUT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(service);
- }
-
- @LargeTest
- public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_DiffFeedback()
- throws Exception {
- // enable the mock accessibility services
- ensureOnlyMockServicesEnabled(mContext, true, true);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the first mock service
- MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
- AccessibilityServiceInfo firstInfo = MockAccessibilityService.createDefaultInfo();
- firstInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE;
- firstService.setServiceInfo(firstInfo);
-
- // configure the second mock service
- MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
- AccessibilityServiceInfo secondInfo = MockAccessibilityService.createDefaultInfo();
- secondInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_HAPTIC;
- secondService.setServiceInfo(secondInfo);
-
- // wait for the binder calls to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // create and populate an event to be sent
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(sentEvent);
-
- // set expectations for the first mock service
- firstService.expectEvent(sentEvent);
- firstService.replay();
-
- // set expectations for the second mock service
- secondService.expectEvent(sentEvent);
- secondService.replay();
-
- // send the event
- mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(firstService);
- assertMockServiceVerifiedWithinTimeout(secondService);
- }
-
- @LargeTest
- public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType()
- throws Exception {
- // enable the mock accessibility services
- ensureOnlyMockServicesEnabled(mContext, true, true);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the first mock service
- MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
- firstService.setServiceInfo(MockAccessibilityService.createDefaultInfo());
-
- // configure the second mock service
- MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
- secondService.setServiceInfo(MockAccessibilityService.createDefaultInfo());
-
- // wait for the binder calls to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // create and populate an event to be sent
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(sentEvent);
-
- // set expectations for the first mock service
- firstService.expectEvent(sentEvent);
- firstService.replay();
-
- // set expectations for the second mock service
- secondService.replay();
-
- // send the event
- mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(firstService);
- assertMockServiceVerifiedWithinTimeout(secondService);
- }
-
- @LargeTest
- public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_OneDefault()
- throws Exception {
- // enable the mock accessibility services
- ensureOnlyMockServicesEnabled(mContext, true, true);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the first mock service
- MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
- AccessibilityServiceInfo firstInfo = MyFirstMockAccessibilityService.createDefaultInfo();
- firstInfo.flags = AccessibilityServiceInfo.DEFAULT;
- firstService.setServiceInfo(firstInfo);
-
- // configure the second mock service
- MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
- secondService.setServiceInfo(MySecondMockAccessibilityService.createDefaultInfo());
-
- // wait for the binder calls to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // create and populate an event to be sent
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(sentEvent);
-
- // set expectations for the first mock service
- firstService.replay();
-
- // set expectations for the second mock service
- secondService.expectEvent(sentEvent);
- secondService.replay();
-
- // send the event
- mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(firstService);
- assertMockServiceVerifiedWithinTimeout(secondService);
- }
-
- @LargeTest
- public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_TwoDefault()
- throws Exception {
- // enable the mock accessibility services
- ensureOnlyMockServicesEnabled(mContext, true, true);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the first mock service
- MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
- AccessibilityServiceInfo firstInfo = MyFirstMockAccessibilityService.createDefaultInfo();
- firstInfo.flags = AccessibilityServiceInfo.DEFAULT;
- firstService.setServiceInfo(firstInfo);
-
- // configure the second mock service
- MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
- AccessibilityServiceInfo secondInfo = MyFirstMockAccessibilityService.createDefaultInfo();
- secondInfo.flags = AccessibilityServiceInfo.DEFAULT;
- secondService.setServiceInfo(firstInfo);
-
- // wait for the binder calls to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // create and populate an event to be sent
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
- fullyPopulateDefaultAccessibilityEvent(sentEvent);
-
- // set expectations for the first mock service
- firstService.expectEvent(sentEvent);
- firstService.replay();
-
- // set expectations for the second mock service
- secondService.replay();
-
- // send the event
- mManagerService.sendAccessibilityEvent(sentEvent, UserHandle.USER_CURRENT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(firstService);
- assertMockServiceVerifiedWithinTimeout(secondService);
- }
-
- @LargeTest
- public void testInterrupt() throws Exception {
- // enable the mock accessibility services
- ensureOnlyMockServicesEnabled(mContext, true, true);
-
- // set the accessibility setting value
- ensureAccessibilityEnabled(mContext, true);
-
- // configure the first mock service
- MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
- firstService.setServiceInfo(MockAccessibilityService.createDefaultInfo());
-
- // configure the second mock service
- MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
- secondService.setServiceInfo(MockAccessibilityService.createDefaultInfo());
-
- // wait for the binder calls to #setService to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // set expectations for the first mock service
- firstService.expectInterrupt();
- firstService.replay();
-
- // set expectations for the second mock service
- secondService.expectInterrupt();
- secondService.replay();
-
- // call the method under test
- mManagerService.interrupt(UserHandle.USER_CURRENT);
-
- // verify if all expected methods have been called
- assertMockServiceVerifiedWithinTimeout(firstService);
- assertMockServiceVerifiedWithinTimeout(secondService);
- }
-
- /**
- * Fully populates the {@link AccessibilityEvent} to marshal.
- *
- * @param sentEvent The event to populate.
- */
- private void fullyPopulateDefaultAccessibilityEvent(AccessibilityEvent sentEvent) {
- sentEvent.setAddedCount(1);
- sentEvent.setBeforeText("BeforeText");
- sentEvent.setChecked(true);
- sentEvent.setClassName("foo.bar.baz.Class");
- sentEvent.setContentDescription("ContentDescription");
- sentEvent.setCurrentItemIndex(1);
- sentEvent.setEnabled(true);
- sentEvent.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
- sentEvent.setEventTime(1000);
- sentEvent.setFromIndex(1);
- sentEvent.setFullScreen(true);
- sentEvent.setItemCount(1);
- sentEvent.setPackageName("foo.bar.baz");
- sentEvent.setParcelableData(Message.obtain(null, 1, null));
- sentEvent.setPassword(true);
- sentEvent.setRemovedCount(1);
- }
-
- /**
- * This class is a mock {@link IAccessibilityManagerClient}.
- */
- public class MyMockAccessibilityManagerClient extends IAccessibilityManagerClient.Stub {
- int mState;
-
- public void setState(int state) {
- mState = state;
- }
-
- public void notifyServicesStateChanged() {}
-
- public void setRelevantEventTypes(int eventTypes) {}
-
- public void setTouchExplorationEnabled(boolean enabled) {}
- }
-
- /**
- * Ensures accessibility is in a given state by writing the state to the
- * settings and waiting until the accessibility manager service pick it up.
- *
- * @param context A context handle to access the settings.
- * @param enabled The accessibility state to write to the settings.
- * @throws Exception If any error occurs.
- */
- private void ensureAccessibilityEnabled(Context context, boolean enabled) throws Exception {
- boolean isEnabled = Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
-
- if (isEnabled == enabled) {
- return;
- }
-
- Settings.Secure.putInt(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED,
- enabled ? 1 : 0);
-
- // wait the accessibility manager service to pick the change up
- Thread.sleep(TIMEOUT_BINDER_CALL);
- }
-
- /**
- * Ensures the only {@link MockAccessibilityService}s with given component
- * names are enabled by writing to the system settings and waiting until the
- * accessibility manager service picks that up or the
- * {@link #TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES} is exceeded.
- *
- * @param context A context handle to access the settings.
- * @param firstMockServiceEnabled If the first mock accessibility service is enabled.
- * @param secondMockServiceEnabled If the second mock accessibility service is enabled.
- * @throws IllegalStateException If some of the requested for enabling mock services
- * is not properly started.
- * @throws Exception Exception If any error occurs.
- */
- private void ensureOnlyMockServicesEnabled(Context context, boolean firstMockServiceEnabled,
- boolean secondMockServiceEnabled) throws Exception {
- String enabledServices = Settings.Secure.getString(context.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
-
- StringBuilder servicesToEnable = new StringBuilder();
- if (firstMockServiceEnabled) {
- servicesToEnable.append(MyFirstMockAccessibilityService.sComponentName).append(":");
- }
- if (secondMockServiceEnabled) {
- servicesToEnable.append(MySecondMockAccessibilityService.sComponentName).append(":");
- }
-
- Settings.Secure.putString(context.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, servicesToEnable.toString());
-
- // Optimization. If things will not change, we don't have to do anything.
- if (servicesToEnable.equals(enabledServices)) {
- return;
- }
-
- // we have enabled the services of interest and need to wait until they
- // are instantiated and started (if needed) and the system binds to them
- boolean firstMockServiceOK = false;
- boolean secondMockServiceOK = false;
- long start = SystemClock.uptimeMillis();
- long pollingInterval = TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES / 6;
-
- while (SystemClock.uptimeMillis() - start < TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES) {
- firstMockServiceOK = !firstMockServiceEnabled
- || (MyFirstMockAccessibilityService.sInstance != null
- && MyFirstMockAccessibilityService.sInstance.isSystemBoundAsClient());
-
- secondMockServiceOK = !secondMockServiceEnabled
- || (MySecondMockAccessibilityService.sInstance != null
- && MySecondMockAccessibilityService.sInstance.isSystemBoundAsClient());
-
- if (firstMockServiceOK && secondMockServiceOK) {
- return;
- }
-
- Thread.sleep(pollingInterval);
- }
-
- StringBuilder message = new StringBuilder();
- message.append("Mock accessibility services not started or system not bound as a client: ");
- if (!firstMockServiceOK) {
- message.append(MyFirstMockAccessibilityService.sComponentName);
- message.append(" ");
- }
- if (!secondMockServiceOK) {
- message.append(MySecondMockAccessibilityService.sComponentName);
- }
- throw new IllegalStateException(message.toString());
- }
-
- /**
- * Asserts the the mock accessibility service has been successfully verified
- * (which is it has received the expected method calls with expected
- * arguments) within the {@link #TIMEOUT_BINDER_CALL}. The verified state is
- * checked by polling upon small intervals.
- *
- * @param service The service to verify.
- * @throws Exception If the verification has failed with exception after the
- * {@link #TIMEOUT_BINDER_CALL}.
- */
- private void assertMockServiceVerifiedWithinTimeout(MockAccessibilityService service)
- throws Exception {
- Exception lastVerifyException = null;
- long beginTime = SystemClock.uptimeMillis();
- long pollTimeout = TIMEOUT_BINDER_CALL / 5;
-
- // poll until the timeout has elapsed
- while (SystemClock.uptimeMillis() - beginTime < TIMEOUT_BINDER_CALL) {
- // sleep first since immediate call will always fail
- try {
- Thread.sleep(pollTimeout);
- } catch (InterruptedException ie) {
- /* ignore */
- }
- // poll for verification and if this fails save the exception and
- // keep polling
- try {
- service.verify();
- // reset so it does not accept more events
- service.reset();
- return;
- } catch (Exception e) {
- lastVerifyException = e;
- }
- }
-
- // reset, we have already failed
- service.reset();
-
- // always not null
- throw lastVerifyException;
- }
-
- /**
- * This class is the first mock {@link AccessibilityService}.
- */
- public static class MyFirstMockAccessibilityService extends MockAccessibilityService {
-
- /**
- * The service {@link ComponentName} flattened as a string.
- */
- static String sComponentName;
-
- /**
- * Handle to the service instance.
- */
- static MyFirstMockAccessibilityService sInstance;
-
- /**
- * Creates a new instance.
- */
- public MyFirstMockAccessibilityService() {
- sInstance = this;
- }
- }
-
- /**
- * This class is the first mock {@link AccessibilityService}.
- */
- public static class MySecondMockAccessibilityService extends MockAccessibilityService {
-
- /**
- * The service {@link ComponentName} flattened as a string.
- */
- static String sComponentName;
-
- /**
- * Handle to the service instance.
- */
- static MySecondMockAccessibilityService sInstance;
-
- /**
- * Creates a new instance.
- */
- public MySecondMockAccessibilityService() {
- sInstance = this;
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java
deleted file mode 100644
index 9261771..0000000
--- a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.os.UserHandle;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.IAccessibilityManager;
-import android.view.accessibility.IAccessibilityManagerClient;
-
-import com.android.internal.util.IntPair;
-
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Tests for the AccessibilityManager which mocking the backing service.
- */
-public class AccessibilityManagerTest extends AndroidTestCase {
-
- /**
- * Timeout required for pending Binder calls or event processing to
- * complete.
- */
- public static final long TIMEOUT_BINDER_CALL = 50;
-
- @Mock
- private IAccessibilityManager mMockService;
-
- @Override
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- }
-
- private AccessibilityManager createManager(boolean enabled) throws Exception {
- if (enabled) {
- when(mMockService.addClient(any(IAccessibilityManagerClient.class), anyInt()))
- .thenReturn(
- IntPair.of(AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED,
- AccessibilityEvent.TYPES_ALL_MASK));
- } else {
- when(mMockService.addClient(any(IAccessibilityManagerClient.class), anyInt()))
- .thenReturn(IntPair.of(0, AccessibilityEvent.TYPES_ALL_MASK));
- }
-
- AccessibilityManager manager =
- new AccessibilityManager(mContext, mMockService, UserHandle.USER_CURRENT);
-
- verify(mMockService).addClient(any(IAccessibilityManagerClient.class), anyInt());
-
- return manager;
- }
-
- @MediumTest
- public void testGetAccessibilityServiceList() throws Exception {
- // create a list of installed accessibility services the mock service returns
- List<AccessibilityServiceInfo> expectedServices = new ArrayList<>();
- AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
- accessibilityServiceInfo.packageNames = new String[] { "foo.bar" };
- expectedServices.add(accessibilityServiceInfo);
-
- // configure the mock service behavior
- when(mMockService.getInstalledAccessibilityServiceList(anyInt()))
- .thenReturn(expectedServices);
-
- // invoke the method under test
- AccessibilityManager manager = createManager(true);
- List<AccessibilityServiceInfo> receivedServices =
- manager.getInstalledAccessibilityServiceList();
-
- verify(mMockService).getInstalledAccessibilityServiceList(UserHandle.USER_CURRENT);
- // check expected result (list equals() compares it contents as well)
- assertEquals("All expected services must be returned", expectedServices, receivedServices);
- }
-
- @MediumTest
- public void testInterrupt() throws Exception {
- AccessibilityManager manager = createManager(true);
- manager.interrupt();
-
- verify(mMockService).interrupt(UserHandle.USER_CURRENT);
- }
-
- @LargeTest
- public void testIsEnabled() throws Exception {
- // invoke the method under test
- AccessibilityManager manager = createManager(true);
- boolean isEnabledServiceEnabled = manager.isEnabled();
-
- // check expected result
- assertTrue("Must be enabled since the mock service is enabled", isEnabledServiceEnabled);
-
- // disable accessibility
- manager.getClient().setState(0);
-
- // wait for the asynchronous IBinder call to complete
- Thread.sleep(TIMEOUT_BINDER_CALL);
-
- // invoke the method under test
- boolean isEnabledServcieDisabled = manager.isEnabled();
-
- // check expected result
- assertFalse("Must be disabled since the mock service is disabled",
- isEnabledServcieDisabled);
- }
-
- @MediumTest
- public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception {
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
-
- AccessibilityManager manager = createManager(true);
- manager.sendAccessibilityEvent(sentEvent);
-
- assertSame("The event should be recycled.", sentEvent, AccessibilityEvent.obtain());
- }
-
- @MediumTest
- public void testSendAccessibilityEvent_AccessibilityDisabled() throws Exception {
- AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
-
- AccessibilityManager manager = createManager(false /* disabled */);
-
- try {
- manager.sendAccessibilityEvent(sentEvent);
- fail("No accessibility events are sent if accessibility is disabled");
- } catch (IllegalStateException ise) {
- // check expected result
- assertEquals("Accessibility off. Did you forget to check that?", ise.getMessage());
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java b/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java
deleted file mode 100644
index e1c5cee..0000000
--- a/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.accessibilityservice.AccessibilityService;
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.content.Intent;
-import android.os.Message;
-import android.view.accessibility.AccessibilityEvent;
-
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Queue;
-
-import junit.framework.TestCase;
-
-/**
- * This is the base class for mock {@link AccessibilityService}s.
- */
-public abstract class MockAccessibilityService extends AccessibilityService {
-
- /**
- * The event this service expects to receive.
- */
- private final Queue<AccessibilityEvent> mExpectedEvents = new LinkedList<AccessibilityEvent>();
-
- /**
- * Interruption call this service expects to receive.
- */
- private boolean mExpectedInterrupt;
-
- /**
- * Flag if the mock is currently replaying.
- */
- private boolean mReplaying;
-
- /**
- * Flag if the system is bound as a client to this service.
- */
- private boolean mIsSystemBoundAsClient;
-
- /**
- * Creates an {@link AccessibilityServiceInfo} populated with default
- * values.
- *
- * @return The default info.
- */
- public static AccessibilityServiceInfo createDefaultInfo() {
- AccessibilityServiceInfo defaultInfo = new AccessibilityServiceInfo();
- defaultInfo.eventTypes = AccessibilityEvent.TYPE_ANNOUNCEMENT;
- defaultInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE;
- defaultInfo.flags = 0;
- defaultInfo.notificationTimeout = 0;
- defaultInfo.packageNames = new String[] {
- "foo.bar.baz"
- };
-
- return defaultInfo;
- }
-
- /**
- * Starts replaying the mock.
- */
- public void replay() {
- mReplaying = true;
- }
-
- /**
- * Verifies if all expected service methods have been called.
- */
- public void verify() {
- if (!mReplaying) {
- throw new IllegalStateException("Did you forget to call replay()");
- }
-
- if (mExpectedInterrupt) {
- throw new IllegalStateException("Expected call to #interrupt() not received");
- }
- if (!mExpectedEvents.isEmpty()) {
- throw new IllegalStateException("Expected a call to onAccessibilityEvent() for "
- + "events \"" + mExpectedEvents + "\" not received");
- }
- }
-
- /**
- * Resets this instance so it can be reused.
- */
- public void reset() {
- mExpectedEvents.clear();
- mExpectedInterrupt = false;
- mReplaying = false;
- }
-
- /**
- * Sets an expected call to
- * {@link #onAccessibilityEvent(AccessibilityEvent)} with given event as
- * argument.
- *
- * @param expectedEvent The expected event argument.
- */
- public void expectEvent(AccessibilityEvent expectedEvent) {
- mExpectedEvents.add(expectedEvent);
- }
-
- /**
- * Sets an expected call of {@link #onInterrupt()}.
- */
- public void expectInterrupt() {
- mExpectedInterrupt = true;
- }
-
- @Override
- public void onAccessibilityEvent(AccessibilityEvent receivedEvent) {
- if (!mReplaying) {
- return;
- }
-
- if (mExpectedEvents.isEmpty()) {
- throw new IllegalStateException("Unexpected event: " + receivedEvent);
- }
-
- AccessibilityEvent expectedEvent = mExpectedEvents.poll();
- assertEqualsAccessiblityEvent(expectedEvent, receivedEvent);
- }
-
- @Override
- public void onInterrupt() {
- if (!mReplaying) {
- return;
- }
-
- if (!mExpectedInterrupt) {
- throw new IllegalStateException("Unexpected call to onInterrupt()");
- }
-
- mExpectedInterrupt = false;
- }
-
- @Override
- protected void onServiceConnected() {
- mIsSystemBoundAsClient = true;
- }
-
- @Override
- public boolean onUnbind(Intent intent) {
- mIsSystemBoundAsClient = false;
- return false;
- }
-
- /**
- * Returns if the system is bound as client to this service.
- *
- * @return True if the system is bound, false otherwise.
- */
- public boolean isSystemBoundAsClient() {
- return mIsSystemBoundAsClient;
- }
-
- /**
- * Compares all properties of the <code>expectedEvent</code> and the
- * <code>receviedEvent</code> to verify that the received event is the one
- * that is expected.
- */
- private void assertEqualsAccessiblityEvent(AccessibilityEvent expectedEvent,
- AccessibilityEvent receivedEvent) {
- TestCase.assertEquals("addedCount has incorrect value", expectedEvent.getAddedCount(),
- receivedEvent.getAddedCount());
- TestCase.assertEquals("beforeText has incorrect value", expectedEvent.getBeforeText(),
- receivedEvent.getBeforeText());
- TestCase.assertEquals("checked has incorrect value", expectedEvent.isChecked(),
- receivedEvent.isChecked());
- TestCase.assertEquals("className has incorrect value", expectedEvent.getClassName(),
- receivedEvent.getClassName());
- TestCase.assertEquals("contentDescription has incorrect value", expectedEvent
- .getContentDescription(), receivedEvent.getContentDescription());
- TestCase.assertEquals("currentItemIndex has incorrect value", expectedEvent
- .getCurrentItemIndex(), receivedEvent.getCurrentItemIndex());
- TestCase.assertEquals("enabled has incorrect value", expectedEvent.isEnabled(),
- receivedEvent.isEnabled());
- TestCase.assertEquals("eventType has incorrect value", expectedEvent.getEventType(),
- receivedEvent.getEventType());
- TestCase.assertEquals("fromIndex has incorrect value", expectedEvent.getFromIndex(),
- receivedEvent.getFromIndex());
- TestCase.assertEquals("fullScreen has incorrect value", expectedEvent.isFullScreen(),
- receivedEvent.isFullScreen());
- TestCase.assertEquals("itemCount has incorrect value", expectedEvent.getItemCount(),
- receivedEvent.getItemCount());
- assertEqualsNotificationAsParcelableData(expectedEvent, receivedEvent);
- TestCase.assertEquals("password has incorrect value", expectedEvent.isPassword(),
- receivedEvent.isPassword());
- TestCase.assertEquals("removedCount has incorrect value", expectedEvent.getRemovedCount(),
- receivedEvent.getRemovedCount());
- assertEqualsText(expectedEvent, receivedEvent);
- }
-
- /**
- * Compares the {@link android.os.Parcelable} data of the
- * <code>expectedEvent</code> and <code>receivedEvent</code> to verify that
- * the received event is the one that is expected.
- */
- private void assertEqualsNotificationAsParcelableData(AccessibilityEvent expectedEvent,
- AccessibilityEvent receivedEvent) {
- String message = "parcelableData has incorrect value";
- Message expectedMessage = (Message) expectedEvent.getParcelableData();
- Message receivedMessage = (Message) receivedEvent.getParcelableData();
-
- if (expectedMessage == null) {
- if (receivedMessage == null) {
- return;
- }
- }
-
- TestCase.assertNotNull(message, receivedMessage);
-
- // we do a very simple sanity check since we do not test Message
- TestCase.assertEquals(message, expectedMessage.what, receivedMessage.what);
- }
-
- /**
- * Compares the text of the <code>expectedEvent</code> and
- * <code>receivedEvent</code> by comparing the string representation of the
- * corresponding {@link CharSequence}s.
- */
- private void assertEqualsText(AccessibilityEvent expectedEvent,
- AccessibilityEvent receivedEvent) {
- String message = "text has incorrect value";
- List<CharSequence> expectedText = expectedEvent.getText();
- List<CharSequence> receivedText = receivedEvent.getText();
-
- TestCase.assertEquals(message, expectedText.size(), receivedText.size());
-
- Iterator<CharSequence> expectedTextIterator = expectedText.iterator();
- Iterator<CharSequence> receivedTextIterator = receivedText.iterator();
-
- for (int i = 0; i < expectedText.size(); i++) {
- // compare the string representation
- TestCase.assertEquals(message, expectedTextIterator.next().toString(),
- receivedTextIterator.next().toString());
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index e0ac393..353199a 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -645,20 +645,6 @@
}
@Test
- public void testDump_noDumpPermission() {
- doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
- eq(permission.DUMP), anyString());
-
- try {
- mNetworkScoreService.dump(
- new FileDescriptor(), new PrintWriter(new StringWriter()), new String[0]);
- fail("SecurityException expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
- @Test
public void testDump_doesNotCrash() {
when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER);
StringWriter stringWriter = new StringWriter();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java
new file mode 100644
index 0000000..5d09e31
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertSame;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Instrumentation;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManager;
+import android.view.accessibility.IAccessibilityManagerClient;
+
+import com.android.internal.util.IntPair;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for the AccessibilityManager by mocking the backing service.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityManagerTest {
+ private static final boolean WITH_A11Y_ENABLED = true;
+ private static final boolean WITH_A11Y_DISABLED = false;
+
+ @Mock private IAccessibilityManager mMockService;
+ private MessageCapturingHandler mHandler;
+ private Instrumentation mInstrumentation;
+
+ @BeforeClass
+ public static void oneTimeInitialization() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mHandler = new MessageCapturingHandler(null);
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ }
+
+ private AccessibilityManager createManager(boolean enabled) throws Exception {
+ long serviceReturnValue = IntPair.of(
+ (enabled) ? AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED : 0,
+ AccessibilityEvent.TYPES_ALL_MASK);
+ when(mMockService.addClient(any(IAccessibilityManagerClient.class), anyInt()))
+ .thenReturn(serviceReturnValue);
+
+ AccessibilityManager manager =
+ new AccessibilityManager(mHandler, mMockService, UserHandle.USER_CURRENT);
+
+ verify(mMockService).addClient(any(IAccessibilityManagerClient.class), anyInt());
+ mHandler.setCallback(manager.getCallback());
+ mHandler.sendAllMessages();
+ return manager;
+ }
+
+ @Test
+ public void testGetAccessibilityServiceList() throws Exception {
+ // create a list of installed accessibility services the mock service returns
+ List<AccessibilityServiceInfo> expectedServices = new ArrayList<>();
+ AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
+ accessibilityServiceInfo.packageNames = new String[] { "foo.bar" };
+ expectedServices.add(accessibilityServiceInfo);
+
+ // configure the mock service behavior
+ when(mMockService.getInstalledAccessibilityServiceList(anyInt()))
+ .thenReturn(expectedServices);
+
+ // invoke the method under test
+ AccessibilityManager manager = createManager(true);
+ List<AccessibilityServiceInfo> receivedServices =
+ manager.getInstalledAccessibilityServiceList();
+
+ verify(mMockService).getInstalledAccessibilityServiceList(UserHandle.USER_CURRENT);
+ // check expected result (list equals() compares it contents as well)
+ assertEquals("All expected services must be returned", expectedServices, receivedServices);
+ }
+
+ @Test
+ public void testInterrupt() throws Exception {
+ AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+ manager.interrupt();
+
+ verify(mMockService).interrupt(UserHandle.USER_CURRENT);
+ }
+
+ @Test
+ public void testIsEnabled() throws Exception {
+ // Create manager with a11y enabled
+ AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+ assertTrue("Must be enabled since the mock service is enabled", manager.isEnabled());
+
+ // Disable accessibility
+ manager.getClient().setState(0);
+ mHandler.sendAllMessages();
+ assertFalse("Must be disabled since the mock service is disabled", manager.isEnabled());
+ }
+
+ @Test
+ public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception {
+ AccessibilityEvent sentEvent = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_ANNOUNCEMENT);
+
+ AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+ manager.sendAccessibilityEvent(sentEvent);
+
+ assertSame("The event should be recycled.", sentEvent, AccessibilityEvent.obtain());
+ }
+
+ @Test
+ public void testSendAccessibilityEvent_AccessibilityDisabled() throws Exception {
+ AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+
+ AccessibilityManager manager = createManager(WITH_A11Y_DISABLED);
+ mInstrumentation.runOnMainSync(() -> {
+ try {
+ manager.sendAccessibilityEvent(sentEvent);
+ fail("No accessibility events are sent if accessibility is disabled");
+ } catch (IllegalStateException ise) {
+ // check expected result
+ assertEquals("Accessibility off. Did you forget to check that?", ise.getMessage());
+ }
+ });
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
index d44c1ca..5887215 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
@@ -20,11 +20,12 @@
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -48,6 +49,7 @@
import android.view.WindowManagerInternal.MagnificationCallbacks;
import com.android.internal.R;
+
import org.hamcrest.CoreMatchers;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
@@ -471,9 +473,10 @@
public void testResetIfNeeded_doesWhatItSays() {
mMagnificationController.register();
zoomIn2xToMiddle();
+ reset(mMockAms);
assertTrue(mMagnificationController.resetIfNeeded(false));
verify(mMockAms).notifyMagnificationChanged(
- eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyInt(), anyInt());
+ eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyFloat(), anyFloat());
assertFalse(mMagnificationController.isMagnifying());
assertFalse(mMagnificationController.resetIfNeeded(false));
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java b/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
index 003f7ab..0dba35f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
@@ -42,6 +42,10 @@
return super.sendMessageAtTime(message, uptimeMillis);
}
+ public void setCallback(Handler.Callback callback) {
+ mCallback = callback;
+ }
+
public void sendOneMessage() {
Message message = timedMessages.remove(0).first;
removeMessages(message.what, message.obj);
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
index aa37407..5d0c23f 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
@@ -149,7 +149,7 @@
// 2nd account
Account account2 = new Account("name", "example2.com");
long accId2 = mAccountsDb.insertCeAccount(account2, "password");
- mAccountsDb.insertDeAccount(account2, accId);
+ mAccountsDb.insertDeAccount(account2, accId2);
mAccountsDb.insertAuthToken(accId2, "type", "token");
mAccountsDb.deleteAuthTokensByAccountId(accId2);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
new file mode 100644
index 0000000..8423aff
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
@@ -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
+ */
+
+package com.android.server.am;
+
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE;
+
+/**
+ * Tests for the {@link ActivityStackSupervisor} class.
+ *
+ * Build/Install/Run:
+ * bit FrameworksServicesTests:com.android.server.am.ActivityStackSupervisorTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ActivityStackSupervisorTests extends ActivityTestsBase {
+ /**
+ * This test ensures that we do not try to restore a task based off an invalid task id. The
+ * stack supervisor is a test version so there will be no tasks present. We should expect
+ * {@code null} to be returned in this case.
+ */
+ @Test
+ public void testRestoringInvalidTask() throws Exception {
+ final ActivityManagerService service = createActivityManagerService();
+ TaskRecord task = service.mStackSupervisor.anyTaskForIdLocked(0 /*taskId*/,
+ MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, 0 /*stackId*/);
+ assertNull(task);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index c5cc2ff..5240586 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -25,7 +25,7 @@
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
-import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.support.test.InstrumentationRegistry;
import com.android.server.AttributeCache;
@@ -34,6 +34,7 @@
import com.android.server.wm.WindowManagerService;
import com.android.server.wm.WindowTestUtils;
+import org.junit.After;
import org.junit.Before;
import org.mockito.MockitoAnnotations;
@@ -42,8 +43,7 @@
*/
public class ActivityTestsBase {
private final Context mContext = InstrumentationRegistry.getContext();
- private static boolean sLooperPrepared;
- private Handler mHandler;
+ private HandlerThread mHandlerThread;
// Grabbing an instance of {@link WindowManagerService} creates it if not present so this must
// be called at before any tests.
@@ -52,11 +52,13 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mHandlerThread = new HandlerThread("ActivityTestsBaseThread");
+ mHandlerThread.start();
+ }
- if (!sLooperPrepared) {
- sLooperPrepared = true;
- Looper.prepare();
- }
+ @After
+ public void tearDown() {
+ mHandlerThread.quitSafely();
}
protected ActivityManagerService createActivityManagerService() {
@@ -126,7 +128,7 @@
@Override
protected ActivityStackSupervisor createStackSupervisor() {
- return new TestActivityStackSupervisor(this, new Handler().getLooper());
+ return new TestActivityStackSupervisor(this, mHandlerThread.getLooper());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 0f1b81e..7a4746a 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import android.app.ActivityManager;
import android.app.IUserSwitchObserver;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -49,16 +50,20 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.server.am.ActivityManagerService.CONTINUE_USER_SWITCH_MSG;
+import static com.android.server.am.ActivityManagerService.REPORT_LOCKED_BOOT_COMPLETE_MSG;
import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_COMPLETE_MSG;
import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_MSG;
import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_MSG;
import static com.android.server.am.ActivityManagerService.SYSTEM_USER_START_MSG;
import static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_MSG;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -71,9 +76,29 @@
private UserController mUserController;
private TestInjector mInjector;
+ private static final List<String> START_FOREGROUND_USER_ACTIONS =
+ Arrays.asList(
+ Intent.ACTION_USER_STARTED,
+ Intent.ACTION_USER_SWITCHED,
+ Intent.ACTION_USER_STARTING);
+
+ private static final List<String> START_BACKGROUND_USER_ACTIONS =
+ Arrays.asList(
+ Intent.ACTION_USER_STARTED,
+ Intent.ACTION_LOCKED_BOOT_COMPLETED,
+ Intent.ACTION_USER_STARTING);
+
+ private static final Set<Integer> START_FOREGROUND_USER_MESSAGE_CODES =
+ new HashSet<>(Arrays.asList(REPORT_USER_SWITCH_MSG, USER_SWITCH_TIMEOUT_MSG,
+ SYSTEM_USER_START_MSG, SYSTEM_USER_CURRENT_MSG));
+
+ private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES =
+ new HashSet<>(Arrays.asList(SYSTEM_USER_START_MSG, REPORT_LOCKED_BOOT_COMPLETE_MSG));
+
@Override
public void setUp() throws Exception {
super.setUp();
+ System.setProperty("dexmaker.share_classloader", "true");
mInjector = new TestInjector(getContext());
mUserController = new UserController(mInjector);
setUpUser(TEST_USER_ID, 0);
@@ -83,39 +108,62 @@
protected void tearDown() throws Exception {
super.tearDown();
mInjector.handlerThread.quit();
-
}
@SmallTest
- public void testStartUser() throws RemoteException {
- mUserController.startUser(TEST_USER_ID, true);
+ public void testStartUser_foreground() throws RemoteException {
+ mUserController.startUser(TEST_USER_ID, true /* foreground */);
Mockito.verify(mInjector.getWindowManager()).startFreezingScreen(anyInt(), anyInt());
Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
Mockito.verify(mInjector.getWindowManager()).setSwitchingUser(true);
- startUserAssertions();
+ Mockito.verify(mInjector.getActivityStackSupervisor()).setLockTaskModeLocked(
+ nullable(TaskRecord.class),
+ eq(ActivityManager.LOCK_TASK_MODE_NONE),
+ anyString(),
+ anyBoolean());
+ startForegroundUserAssertions();
+ }
+
+ @SmallTest
+ public void testStartUser_background() throws RemoteException {
+ mUserController.startUser(TEST_USER_ID, false /* foreground */);
+ Mockito.verify(
+ mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
+ Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
+ Mockito.verify(mInjector.getActivityStackSupervisor(), never()).setLockTaskModeLocked(
+ nullable(TaskRecord.class),
+ eq(ActivityManager.LOCK_TASK_MODE_NONE),
+ anyString(),
+ anyBoolean());
+ startBackgroundUserAssertions();
}
@SmallTest
public void testStartUserUIDisabled() throws RemoteException {
mUserController.mUserSwitchUiEnabled = false;
- mUserController.startUser(TEST_USER_ID, true);
+ mUserController.startUser(TEST_USER_ID, true /* foreground */);
Mockito.verify(mInjector.getWindowManager(), never())
.startFreezingScreen(anyInt(), anyInt());
Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
- startUserAssertions();
+ startForegroundUserAssertions();
}
- private void startUserAssertions() throws RemoteException {
- List<String> expectedActions = Arrays.asList(Intent.ACTION_USER_STARTED,
- Intent.ACTION_USER_SWITCHED, Intent.ACTION_USER_STARTING);
+ private void startUserAssertions(
+ List<String> expectedActions, Set<Integer> expectedMessageCodes)
+ throws RemoteException {
assertEquals(expectedActions, getActions(mInjector.sentIntents));
- Set<Integer> expectedCodes = new HashSet<>(
- Arrays.asList(REPORT_USER_SWITCH_MSG, USER_SWITCH_TIMEOUT_MSG,
- SYSTEM_USER_START_MSG, SYSTEM_USER_CURRENT_MSG));
Set<Integer> actualCodes = mInjector.handler.getMessageCodes();
- assertEquals("Unexpected message sent", expectedCodes, actualCodes);
+ assertEquals("Unexpected message sent", expectedMessageCodes, actualCodes);
+ }
+
+ private void startBackgroundUserAssertions() throws RemoteException {
+ startUserAssertions(START_BACKGROUND_USER_ACTIONS, START_BACKGROUND_USER_MESSAGE_CODES);
+ }
+
+ private void startForegroundUserAssertions() throws RemoteException {
+ startUserAssertions(START_FOREGROUND_USER_ACTIONS, START_FOREGROUND_USER_MESSAGE_CODES);
Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG);
assertNotNull(reportMsg);
UserState userState = (UserState) reportMsg.obj;
@@ -275,6 +323,7 @@
UserManagerService userManagerMock;
UserManagerInternal userManagerInternalMock;
WindowManagerService windowManagerMock;
+ ActivityStackSupervisor activityStackSupervisor;
private Context mCtx;
List<Intent> sentIntents = new ArrayList<>();
@@ -287,6 +336,7 @@
userManagerMock = mock(UserManagerService.class);
userManagerInternalMock = mock(UserManagerInternal.class);
windowManagerMock = mock(WindowManagerService.class);
+ activityStackSupervisor = mock(ActivityStackSupervisor.class);
}
@Override
@@ -321,12 +371,6 @@
}
@Override
- void stackSupervisorSetLockTaskModeLocked(TaskRecord task, int lockTaskModeState,
- String reason, boolean andResume) {
- Log.i(TAG, "stackSupervisorSetLockTaskModeLocked");
- }
-
- @Override
WindowManagerService getWindowManager() {
return windowManagerMock;
}
@@ -347,16 +391,15 @@
}
@Override
- boolean stackSupervisorSwitchUserLocked(int userId, UserState uss) {
- Log.i(TAG, "stackSupervisorSwitchUserLocked " + userId);
- return true;
- }
-
- @Override
void startHomeActivityLocked(int userId, String reason) {
Log.i(TAG, "startHomeActivityLocked " + userId);
}
- }
+
+ @Override
+ ActivityStackSupervisor getActivityStackSupervisor() {
+ return activityStackSupervisor;
+ }
+ }
private static class TestHandler extends Handler {
private final List<Message> mMessages = new ArrayList<>();
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index f3f68ff..2663aaf 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -50,6 +50,7 @@
import org.mockito.ArgumentCaptor;
+import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
@@ -249,6 +250,25 @@
assertEquals(7, updates.size());
}
+ public void testGetInstalledProvidersForPackage() {
+ List<AppWidgetProviderInfo> allProviders = mManager.getInstalledProviders();
+ assertTrue(!allProviders.isEmpty());
+ String packageName = allProviders.get(0).provider.getPackageName();
+ List<AppWidgetProviderInfo> providersForPackage = mManager.getInstalledProvidersForPackage(
+ packageName, null);
+ // Remove providers from allProviders that don't have the given package name.
+ Iterator<AppWidgetProviderInfo> iter = allProviders.iterator();
+ while (iter.hasNext()) {
+ if (!iter.next().provider.getPackageName().equals(packageName)) {
+ iter.remove();
+ }
+ }
+ assertEquals(allProviders.size(), providersForPackage.size());
+ for (int i = 0; i < allProviders.size(); i++) {
+ assertEquals(allProviders.get(i).provider, providersForPackage.get(i).provider);
+ }
+ }
+
private int setupHostAndWidget() {
List<PendingHostUpdate> updates = mService.startListening(
mMockHost, mPkgName, HOST_ID, new int[0]).getList();
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index 72fb78e..afc0f67 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -360,6 +360,19 @@
assertNull(mDexManager.getPackageUseInfo(frameworkDex));
}
+ @Test
+ public void testNotifySecondaryFromProtected() {
+ // Foo loads its own secondary files.
+ List<String> fooSecondaries = mFooUser0.getSecondaryDexPathsFromProtectedDirs();
+ notifyDexLoad(mFooUser0, fooSecondaries, mUser0);
+
+ PackageUseInfo pui = getPackageUseInfo(mFooUser0);
+ assertNotNull(pui);
+ assertFalse(pui.isUsedByOtherApps());
+ assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size());
+ assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0);
+ }
+
private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) {
for (String dex : secondaries) {
@@ -394,6 +407,8 @@
ai.setBaseCodePath(codeDir + "/base.dex");
ai.setSplitCodePaths(new String[] {codeDir + "/split-1.dex", codeDir + "/split-2.dex"});
ai.dataDir = "/data/user/" + userId + "/" + packageName;
+ ai.deviceProtectedDataDir = "/data/user_de/" + userId + "/" + packageName;
+ ai.credentialProtectedDataDir = "/data/user_ce/" + userId + "/" + packageName;
ai.packageName = packageName;
return ai;
}
@@ -426,6 +441,13 @@
return paths;
}
+ List<String> getSecondaryDexPathsFromProtectedDirs() {
+ List<String> paths = new ArrayList<>();
+ paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary6.dex");
+ paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary7.dex");
+ return paths;
+ }
+
List<String> getBaseAndSplitDexPaths() {
List<String> paths = new ArrayList<>();
paths.add(mPackageInfo.applicationInfo.sourceDir);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 92233b1..f80ee73 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -356,6 +356,7 @@
private final StatusHints mStatusHints;
private final Bundle mExtras;
private final Bundle mIntentExtras;
+ private final long mCreationTimeMillis;
/**
* Whether the supplied capabilities supports the specified capability.
@@ -578,9 +579,12 @@
}
/**
- * @return The time the {@code Call} has been connected. This information is updated
- * periodically, but user interfaces should not rely on this to display any "call time
- * clock".
+ * Returns the time the {@link Call} connected (i.e. became active). This information is
+ * updated periodically, but user interfaces should not rely on this to display the "call
+ * time clock". For the time when the call was first added to Telecom, see
+ * {@link #getCreationTimeMillis()}.
+ *
+ * @return The time the {@link Call} connected in milliseconds since the epoch.
*/
public final long getConnectTimeMillis() {
return mConnectTimeMillis;
@@ -622,6 +626,18 @@
return mIntentExtras;
}
+ /**
+ * Returns the time when the call was first created and added to Telecom. This is the same
+ * time that is logged as the start time in the Call Log (see
+ * {@link android.provider.CallLog.Calls#DATE}). To determine when the call was connected
+ * (became active), see {@link #getConnectTimeMillis()}.
+ *
+ * @return The creation time of the call, in millis since the epoch.
+ */
+ public long getCreationTimeMillis() {
+ return mCreationTimeMillis;
+ }
+
@Override
public boolean equals(Object o) {
if (o instanceof Details) {
@@ -641,28 +657,29 @@
Objects.equals(mVideoState, d.mVideoState) &&
Objects.equals(mStatusHints, d.mStatusHints) &&
areBundlesEqual(mExtras, d.mExtras) &&
- areBundlesEqual(mIntentExtras, d.mIntentExtras);
+ areBundlesEqual(mIntentExtras, d.mIntentExtras) &&
+ Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis);
}
return false;
}
@Override
public int hashCode() {
- return
- Objects.hashCode(mHandle) +
- Objects.hashCode(mHandlePresentation) +
- Objects.hashCode(mCallerDisplayName) +
- Objects.hashCode(mCallerDisplayNamePresentation) +
- Objects.hashCode(mAccountHandle) +
- Objects.hashCode(mCallCapabilities) +
- Objects.hashCode(mCallProperties) +
- Objects.hashCode(mDisconnectCause) +
- Objects.hashCode(mConnectTimeMillis) +
- Objects.hashCode(mGatewayInfo) +
- Objects.hashCode(mVideoState) +
- Objects.hashCode(mStatusHints) +
- Objects.hashCode(mExtras) +
- Objects.hashCode(mIntentExtras);
+ return Objects.hash(mHandle,
+ mHandlePresentation,
+ mCallerDisplayName,
+ mCallerDisplayNamePresentation,
+ mAccountHandle,
+ mCallCapabilities,
+ mCallProperties,
+ mDisconnectCause,
+ mConnectTimeMillis,
+ mGatewayInfo,
+ mVideoState,
+ mStatusHints,
+ mExtras,
+ mIntentExtras,
+ mCreationTimeMillis);
}
/** {@hide} */
@@ -681,7 +698,8 @@
int videoState,
StatusHints statusHints,
Bundle extras,
- Bundle intentExtras) {
+ Bundle intentExtras,
+ long creationTimeMillis) {
mTelecomCallId = telecomCallId;
mHandle = handle;
mHandlePresentation = handlePresentation;
@@ -697,6 +715,7 @@
mStatusHints = statusHints;
mExtras = extras;
mIntentExtras = intentExtras;
+ mCreationTimeMillis = creationTimeMillis;
}
/** {@hide} */
@@ -716,7 +735,8 @@
parcelableCall.getVideoState(),
parcelableCall.getStatusHints(),
parcelableCall.getExtras(),
- parcelableCall.getIntentExtras());
+ parcelableCall.getIntentExtras(),
+ parcelableCall.getCreationTimeMillis());
}
@Override
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index 85a92d1..6212a77 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -59,6 +59,7 @@
private final List<String> mConferenceableCallIds;
private final Bundle mIntentExtras;
private final Bundle mExtras;
+ private final long mCreationTimeMillis;
public ParcelableCall(
String id,
@@ -85,7 +86,8 @@
int videoState,
List<String> conferenceableCallIds,
Bundle intentExtras,
- Bundle extras) {
+ Bundle extras,
+ long creationTimeMillis) {
mId = id;
mState = state;
mDisconnectCause = disconnectCause;
@@ -111,6 +113,7 @@
mConferenceableCallIds = Collections.unmodifiableList(conferenceableCallIds);
mIntentExtras = intentExtras;
mExtras = extras;
+ mCreationTimeMillis = creationTimeMillis;
}
/** The unique ID of the call. */
@@ -289,6 +292,13 @@
return mIsVideoCallProviderChanged;
}
+ /**
+ * @return The time the call was created, in milliseconds since the epoch.
+ */
+ public long getCreationTimeMillis() {
+ return mCreationTimeMillis;
+ }
+
/** Responsible for creating ParcelableCall objects for deserialized Parcels. */
public static final Parcelable.Creator<ParcelableCall> CREATOR =
new Parcelable.Creator<ParcelableCall> () {
@@ -324,6 +334,7 @@
int supportedAudioRoutes = source.readInt();
boolean isRttCallChanged = source.readByte() == 1;
ParcelableRttCall rttCall = source.readParcelable(classLoader);
+ long creationTimeMillis = source.readLong();
return new ParcelableCall(
id,
state,
@@ -349,7 +360,8 @@
videoState,
conferenceableCallIds,
intentExtras,
- extras);
+ extras,
+ creationTimeMillis);
}
@Override
@@ -393,6 +405,7 @@
destination.writeInt(mSupportedAudioRoutes);
destination.writeByte((byte) (mIsRttCallChanged ? 1 : 0));
destination.writeParcelable(mRttCall, 0);
+ destination.writeLong(mCreationTimeMillis);
}
@Override
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index b4c531e..f9875c5 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -53,8 +53,9 @@
*
* <p class="note">This is a protected intent that can only be sent
* by the system.
+ * @deprecated use {@link Intent#ACTION_SERVICE_STATE}
*/
- public static final String ACTION_SERVICE_STATE_CHANGED = "android.intent.action.SERVICE_STATE";
+ public static final String ACTION_SERVICE_STATE_CHANGED = Intent.ACTION_SERVICE_STATE;
/**
* <p>Broadcast Action: The radio technology has changed. The intent will have the following
diff --git a/tests/UiBench/Android.mk b/tests/UiBench/Android.mk
index 36ebc90..71067ae 100644
--- a/tests/UiBench/Android.mk
+++ b/tests/UiBench/Android.mk
@@ -2,7 +2,7 @@
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := 24
+LOCAL_SDK_VERSION := current
LOCAL_MIN_SDK_VERSION := 21
# omit gradle 'build' dir
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 8461905..90f713b 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -155,7 +155,10 @@
xml::XmlPullParser* parser, std::string* out_raw_string, StyleString* out_style_string,
std::vector<UntranslatableSection>* out_untranslatable_sections) {
// Keeps track of formatting tags (<b>, <i>) and the range of characters for which they apply.
- std::vector<Span> span_stack;
+ // The stack elements refer to the indices in out_style_string->spans.
+ // By first adding to the out_style_string->spans vector, and then using the stack to refer
+ // to this vector, the original order of tags is preserved in cases such as <b><i>hello</b></i>.
+ std::vector<size_t> span_stack;
// Clear the output variables.
out_raw_string->clear();
@@ -192,7 +195,9 @@
return false;
}
- span_stack.push_back(Span{std::move(span_name), static_cast<uint32_t>(builder.Utf16Len())});
+ out_style_string->spans.push_back(
+ Span{std::move(span_name), static_cast<uint32_t>(builder.Utf16Len())});
+ span_stack.push_back(out_style_string->spans.size() - 1);
} else if (parser->element_namespace() == sXliffNamespaceUri) {
if (parser->element_name() == "g") {
if (untranslatable_start_depth) {
@@ -233,9 +238,8 @@
if (parser->element_namespace().empty()) {
// This is an HTML tag which we encode as a span. Update the span
// stack and pop the top entry.
- Span& top_span = span_stack.back();
+ Span& top_span = out_style_string->spans[span_stack.back()];
top_span.last_char = builder.Utf16Len() - 1;
- out_style_string->spans.push_back(std::move(top_span));
span_stack.pop_back();
} else if (untranslatable_start_depth == make_value(depth)) {
// This is the end of an untranslatable section. Use UTF8 indices/lengths.
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index eefa320..8062c2e6 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -101,20 +101,24 @@
// Use a surrogate pair unicode point so that we can verify that the span
// indices use UTF-16 length and not UTF-8 length.
std::string input =
- "<string name=\"foo\">This is my aunt\u2019s <b>string</b></string>";
+ "<string name=\"foo\">This is my aunt\u2019s <b>fickle <small>string</small></b></string>";
ASSERT_TRUE(TestParse(input));
StyledString* str = test::GetValue<StyledString>(&table_, "string/foo");
ASSERT_NE(nullptr, str);
- const std::string expected_str = "This is my aunt\u2019s string";
+ const std::string expected_str = "This is my aunt\u2019s fickle string";
EXPECT_EQ(expected_str, *str->value->str);
- EXPECT_EQ(1u, str->value->spans.size());
+ EXPECT_EQ(2u, str->value->spans.size());
EXPECT_TRUE(str->untranslatable_sections.empty());
EXPECT_EQ(std::string("b"), *str->value->spans[0].name);
EXPECT_EQ(17u, str->value->spans[0].first_char);
- EXPECT_EQ(23u, str->value->spans[0].last_char);
+ EXPECT_EQ(30u, str->value->spans[0].last_char);
+
+ EXPECT_EQ(std::string("small"), *str->value->spans[1].name);
+ EXPECT_EQ(24u, str->value->spans[1].first_char);
+ EXPECT_EQ(30u, str->value->spans[1].last_char);
}
TEST_F(ResourceParserTest, ParseStringWithWhitespace) {
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index fad9edd..a031ea4 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -22,136 +22,194 @@
#include "ResourceValues.h"
#include "ValueVisitor.h"
#include "compile/Pseudolocalizer.h"
+#include "util/Util.h"
using android::StringPiece;
+using android::StringPiece16;
namespace aapt {
-std::unique_ptr<StyledString> PseudolocalizeStyledString(
- StyledString* string, Pseudolocalizer::Method method, StringPool* pool) {
+// The struct that represents both Span objects and UntranslatableSections.
+struct UnifiedSpan {
+ // Only present for Span objects. If not present, this was an UntranslatableSection.
+ Maybe<std::string> tag;
+
+ // The UTF-16 index into the string where this span starts.
+ uint32_t first_char;
+
+ // The UTF-16 index into the string where this span ends, inclusive.
+ uint32_t last_char;
+};
+
+inline static bool operator<(const UnifiedSpan& left, const UnifiedSpan& right) {
+ if (left.first_char < right.first_char) {
+ return true;
+ } else if (left.first_char > right.first_char) {
+ return false;
+ } else if (left.last_char < right.last_char) {
+ return true;
+ }
+ return false;
+}
+
+inline static UnifiedSpan SpanToUnifiedSpan(const StringPool::Span& span) {
+ return UnifiedSpan{*span.name, span.first_char, span.last_char};
+}
+
+inline static UnifiedSpan UntranslatableSectionToUnifiedSpan(const UntranslatableSection& section) {
+ return UnifiedSpan{
+ {}, static_cast<uint32_t>(section.start), static_cast<uint32_t>(section.end) - 1};
+}
+
+// Merges the Span and UntranslatableSections of this StyledString into a single vector of
+// UnifiedSpans. This will first check that the Spans are sorted in ascending order.
+static std::vector<UnifiedSpan> MergeSpans(const StyledString& string) {
+ // Ensure the Spans are sorted and converted.
+ std::vector<UnifiedSpan> sorted_spans;
+ sorted_spans.reserve(string.value->spans.size());
+ std::transform(string.value->spans.begin(), string.value->spans.end(),
+ std::back_inserter(sorted_spans), SpanToUnifiedSpan);
+
+ // Stable sort to ensure tag sequences like "<b><i>" are preserved.
+ std::stable_sort(sorted_spans.begin(), sorted_spans.end());
+
+ // Ensure the UntranslatableSections are sorted and converted.
+ std::vector<UnifiedSpan> sorted_untranslatable_sections;
+ sorted_untranslatable_sections.reserve(string.untranslatable_sections.size());
+ std::transform(string.untranslatable_sections.begin(), string.untranslatable_sections.end(),
+ std::back_inserter(sorted_untranslatable_sections),
+ UntranslatableSectionToUnifiedSpan);
+ std::sort(sorted_untranslatable_sections.begin(), sorted_untranslatable_sections.end());
+
+ std::vector<UnifiedSpan> merged_spans;
+ merged_spans.reserve(sorted_spans.size() + sorted_untranslatable_sections.size());
+ auto span_iter = sorted_spans.begin();
+ auto untranslatable_iter = sorted_untranslatable_sections.begin();
+ while (span_iter != sorted_spans.end() &&
+ untranslatable_iter != sorted_untranslatable_sections.end()) {
+ if (*span_iter < *untranslatable_iter) {
+ merged_spans.push_back(std::move(*span_iter));
+ ++span_iter;
+ } else {
+ merged_spans.push_back(std::move(*untranslatable_iter));
+ ++untranslatable_iter;
+ }
+ }
+
+ while (span_iter != sorted_spans.end()) {
+ merged_spans.push_back(std::move(*span_iter));
+ ++span_iter;
+ }
+
+ while (untranslatable_iter != sorted_untranslatable_sections.end()) {
+ merged_spans.push_back(std::move(*untranslatable_iter));
+ ++untranslatable_iter;
+ }
+ return merged_spans;
+}
+
+std::unique_ptr<StyledString> PseudolocalizeStyledString(StyledString* string,
+ Pseudolocalizer::Method method,
+ StringPool* pool) {
Pseudolocalizer localizer(method);
- const StringPiece original_text = *string->value->str;
+ // Collect the spans and untranslatable sections into one set of spans, sorted by first_char.
+ // This will effectively subdivide the string into multiple sections that can be individually
+ // pseudolocalized, while keeping the span indices synchronized.
+ std::vector<UnifiedSpan> merged_spans = MergeSpans(*string);
+
+ // All Span indices are UTF-16 based, according to the resources.arsc format expected by the
+ // runtime. So we will do all our processing in UTF-16, then convert back.
+ const std::u16string text16 = util::Utf8ToUtf16(*string->value->str);
+
+ // Convenient wrapper around the text that allows us to work with StringPieces.
+ const StringPiece16 text(text16);
+
+ // The new string.
+ std::string new_string = localizer.Start();
+
+ // The stack that keeps track of what nested Span we're in.
+ std::vector<size_t> span_stack;
+
+ // The current position in the original text.
+ uint32_t cursor = 0u;
+
+ // The current position in the new text.
+ uint32_t new_cursor = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(new_string.data()),
+ new_string.size(), false);
+
+ // We assume no nesting of untranslatable sections, since XLIFF doesn't allow it.
+ bool translatable = true;
+ size_t span_idx = 0u;
+ while (span_idx < merged_spans.size() || !span_stack.empty()) {
+ UnifiedSpan* span = span_idx >= merged_spans.size() ? nullptr : &merged_spans[span_idx];
+ UnifiedSpan* parent_span = span_stack.empty() ? nullptr : &merged_spans[span_stack.back()];
+
+ if (span != nullptr) {
+ if (parent_span == nullptr || parent_span->last_char > span->first_char) {
+ // There is no parent, or this span is the child of the parent.
+ // Pseudolocalize all the text until this span.
+ const StringPiece16 substr = text.substr(cursor, span->first_char - cursor);
+ cursor += substr.size();
+
+ // Pseudolocalize the substring.
+ std::string new_substr = util::Utf16ToUtf8(substr);
+ if (translatable) {
+ new_substr = localizer.Text(new_substr);
+ }
+ new_cursor += utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(new_substr.data()),
+ new_substr.size(), false);
+ new_string += new_substr;
+
+ // Rewrite the first_char.
+ span->first_char = new_cursor;
+ if (!span->tag) {
+ // An untranslatable section has begun!
+ translatable = false;
+ }
+ span_stack.push_back(span_idx);
+ ++span_idx;
+ continue;
+ }
+ }
+
+ if (parent_span != nullptr) {
+ // There is a parent, and either this span is not a child of it, or there are no more spans.
+ // Pop this off the stack.
+ const StringPiece16 substr = text.substr(cursor, parent_span->last_char - cursor + 1);
+ cursor += substr.size();
+
+ // Pseudolocalize the substring.
+ std::string new_substr = util::Utf16ToUtf8(substr);
+ if (translatable) {
+ new_substr = localizer.Text(new_substr);
+ }
+ new_cursor += utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(new_substr.data()),
+ new_substr.size(), false);
+ new_string += new_substr;
+
+ parent_span->last_char = new_cursor - 1;
+ if (parent_span->tag) {
+ // An end to an untranslatable section.
+ translatable = true;
+ }
+ span_stack.pop_back();
+ }
+ }
+
+ // Finish the pseudolocalization at the end of the string.
+ new_string += localizer.Text(util::Utf16ToUtf8(text.substr(cursor, text.size() - cursor)));
+ new_string += localizer.End();
StyleString localized;
+ localized.str = std::move(new_string);
- // Copy the spans. We will update their offsets when we localize.
- localized.spans.reserve(string->value->spans.size());
- for (const StringPool::Span& span : string->value->spans) {
- localized.spans.push_back(
- Span{*span.name, span.first_char, span.last_char});
- }
-
- // The ranges are all represented with a single value. This is the start of
- // one range and end of another.
- struct Range {
- size_t start;
-
- // If set to true, toggles the state of translatability.
- bool toggle_translatability;
-
- // Once the new string is localized, these are the pointers to the spans to adjust.
- // Since this struct represents the start of one range and end of another,
- // we have the two pointers respectively.
- uint32_t* update_start;
- uint32_t* update_end;
- };
-
- auto cmp = [](const Range& r, size_t index) -> bool {
- return r.start < index;
- };
-
- // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7]
- // The ranges are the spaces in between. In this example, with a total string
- // length of 9, the vector represents: (0,1], (2,4], (5,6], (7,9]
- //
- std::vector<Range> ranges;
- ranges.push_back(Range{0, false, nullptr, nullptr});
- ranges.push_back(Range{original_text.size() - 1, false, nullptr, nullptr});
- for (size_t i = 0; i < string->value->spans.size(); i++) {
- const StringPool::Span& span = string->value->spans[i];
-
- // Insert or update the Range marker for the start of this span.
- auto iter =
- std::lower_bound(ranges.begin(), ranges.end(), span.first_char, cmp);
- if (iter != ranges.end() && iter->start == span.first_char) {
- iter->update_start = &localized.spans[i].first_char;
- } else {
- ranges.insert(iter, Range{span.first_char, false, &localized.spans[i].first_char, nullptr});
- }
-
- // Insert or update the Range marker for the end of this span.
- iter = std::lower_bound(ranges.begin(), ranges.end(), span.last_char, cmp);
- if (iter != ranges.end() && iter->start == span.last_char) {
- iter->update_end = &localized.spans[i].last_char;
- } else {
- ranges.insert(iter, Range{span.last_char, false, nullptr, &localized.spans[i].last_char});
+ // Convert the UnifiedSpans into regular Spans, skipping the UntranslatableSections.
+ for (UnifiedSpan& span : merged_spans) {
+ if (span.tag) {
+ localized.spans.push_back(Span{std::move(span.tag.value()), span.first_char, span.last_char});
}
}
-
- // Parts of the string may be untranslatable. Merge those ranges
- // in as well, so that we have continuous sections of text to
- // feed into the pseudolocalizer.
- // We do this by marking the beginning of a range as either toggling
- // the translatability state or not.
- for (const UntranslatableSection& section : string->untranslatable_sections) {
- auto iter = std::lower_bound(ranges.begin(), ranges.end(), section.start, cmp);
- if (iter != ranges.end() && iter->start == section.start) {
- // An existing span starts (or ends) here. We just need to mark that
- // the translatability should toggle here. If translatability was
- // already being toggled, then that means we have two adjacent ranges of untranslatable
- // text, so remove the toggle and only toggle at the end of this range,
- // effectively merging these ranges.
- iter->toggle_translatability = !iter->toggle_translatability;
- } else {
- // Insert a new range that specifies to toggle the translatability.
- iter = ranges.insert(iter, Range{section.start, true, nullptr, nullptr});
- }
-
- // Update/create an end to the untranslatable section.
- iter = std::lower_bound(iter, ranges.end(), section.end, cmp);
- if (iter != ranges.end() && iter->start == section.end) {
- iter->toggle_translatability = true;
- } else {
- iter = ranges.insert(iter, Range{section.end, true, nullptr, nullptr});
- }
- }
-
- localized.str += localizer.Start();
-
- // Iterate over the ranges and localize each section.
- // The text starts as translatable, and each time a range has toggle_translatability
- // set to true, we toggle whether to translate or not.
- // This assumes no untranslatable ranges overlap.
- bool translatable = true;
- for (size_t i = 0; i < ranges.size(); i++) {
- const size_t start = ranges[i].start;
- size_t len = original_text.size() - start;
- if (i + 1 < ranges.size()) {
- len = ranges[i + 1].start - start;
- }
-
- if (ranges[i].update_start) {
- *ranges[i].update_start = localized.str.size();
- }
-
- if (ranges[i].update_end) {
- *ranges[i].update_end = localized.str.size();
- }
-
- if (ranges[i].toggle_translatability) {
- translatable = !translatable;
- }
-
- if (translatable) {
- localized.str += localizer.Text(original_text.substr(start, len));
- } else {
- localized.str += original_text.substr(start, len);
- }
- }
-
- localized.str += localizer.End();
-
return util::make_unique<StyledString>(pool->MakeRef(localized));
}
@@ -175,8 +233,7 @@
if (sub_visitor.value) {
localized->values[i] = std::move(sub_visitor.item);
} else {
- localized->values[i] =
- std::unique_ptr<Item>(plural->values[i]->Clone(pool_));
+ localized->values[i] = std::unique_ptr<Item>(plural->values[i]->Clone(pool_));
}
}
}
@@ -210,8 +267,7 @@
}
result += localizer_.End();
- std::unique_ptr<String> localized =
- util::make_unique<String>(pool_->MakeRef(result));
+ std::unique_ptr<String> localized = util::make_unique<String>(pool_->MakeRef(result));
localized->SetSource(string->GetSource());
localized->SetWeak(true);
item = std::move(localized);
@@ -282,14 +338,10 @@
}
}
-/**
- * A value is pseudolocalizable if it does not define a locale (or is the
- * default locale)
- * and is translatable.
- */
+// A value is pseudolocalizable if it does not define a locale (or is the default locale) and is
+// translatable.
static bool IsPseudolocalizable(ResourceConfigValue* config_value) {
- const int diff =
- config_value->config.diff(ConfigDescription::DefaultConfig());
+ const int diff = config_value->config.diff(ConfigDescription::DefaultConfig());
if (diff & ConfigDescription::CONFIG_LOCALE) {
return false;
}
@@ -298,19 +350,16 @@
} // namespace
-bool PseudolocaleGenerator::Consume(IAaptContext* context,
- ResourceTable* table) {
+bool PseudolocaleGenerator::Consume(IAaptContext* context, ResourceTable* table) {
for (auto& package : table->packages) {
for (auto& type : package->types) {
for (auto& entry : type->entries) {
- std::vector<ResourceConfigValue*> values =
- entry->FindValuesIf(IsPseudolocalizable);
-
+ std::vector<ResourceConfigValue*> values = entry->FindValuesIf(IsPseudolocalizable);
for (ResourceConfigValue* value : values) {
- PseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
- &table->string_pool, entry.get());
- PseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
- &table->string_pool, entry.get());
+ PseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value, &table->string_pool,
+ entry.get());
+ PseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value, &table->string_pool,
+ entry.get());
}
}
}
diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
index 4db37db..b08e1da 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
@@ -25,7 +25,7 @@
StringPool pool;
StyleString original_style;
original_style.str = "Hello world!";
- original_style.spans = {Span{"b", 2, 3}, Span{"b", 6, 7}, Span{"i", 1, 10}};
+ original_style.spans = {Span{"i", 1, 10}, Span{"b", 2, 3}, Span{"b", 6, 7}};
std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString(
util::make_unique<StyledString>(pool.MakeRef(original_style)).get(),
@@ -34,22 +34,19 @@
EXPECT_EQ(original_style.str, *new_string->value->str);
ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size());
- EXPECT_EQ(std::string("He").size(), new_string->value->spans[0].first_char);
- EXPECT_EQ(std::string("Hel").size(), new_string->value->spans[0].last_char);
- EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name);
+ EXPECT_EQ(std::string("i"), *new_string->value->spans[0].name);
+ EXPECT_EQ(std::u16string(u"H").size(), new_string->value->spans[0].first_char);
+ EXPECT_EQ(std::u16string(u"Hello worl").size(), new_string->value->spans[0].last_char);
- EXPECT_EQ(std::string("Hello ").size(),
- new_string->value->spans[1].first_char);
- EXPECT_EQ(std::string("Hello w").size(),
- new_string->value->spans[1].last_char);
EXPECT_EQ(std::string("b"), *new_string->value->spans[1].name);
+ EXPECT_EQ(std::u16string(u"He").size(), new_string->value->spans[1].first_char);
+ EXPECT_EQ(std::u16string(u"Hel").size(), new_string->value->spans[1].last_char);
- EXPECT_EQ(std::string("H").size(), new_string->value->spans[2].first_char);
- EXPECT_EQ(std::string("Hello worl").size(),
- new_string->value->spans[2].last_char);
- EXPECT_EQ(std::string("i"), *new_string->value->spans[2].name);
+ EXPECT_EQ(std::string("b"), *new_string->value->spans[2].name);
+ EXPECT_EQ(std::u16string(u"Hello ").size(), new_string->value->spans[2].first_char);
+ EXPECT_EQ(std::u16string(u"Hello w").size(), new_string->value->spans[2].last_char);
- original_style.spans.push_back(Span{"em", 0, 11u});
+ original_style.spans.insert(original_style.spans.begin(), Span{"em", 0, 11u});
new_string = PseudolocalizeStyledString(
util::make_unique<StyledString>(pool.MakeRef(original_style)).get(),
@@ -58,23 +55,128 @@
EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), *new_string->value->str);
ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size());
- EXPECT_EQ(std::string("[Ĥé").size(), new_string->value->spans[0].first_char);
- EXPECT_EQ(std::string("[Ĥéļ").size(), new_string->value->spans[0].last_char);
+ EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char);
+ EXPECT_EQ(std::u16string(u"[Ĥéļļö ŵöŕļð").size(), new_string->value->spans[0].last_char);
- EXPECT_EQ(std::string("[Ĥéļļö ").size(),
+ EXPECT_EQ(std::u16string(u"[Ĥ").size(), new_string->value->spans[1].first_char);
+ EXPECT_EQ(std::u16string(u"[Ĥéļļö ŵöŕļ").size(), new_string->value->spans[1].last_char);
+
+ EXPECT_EQ(std::u16string(u"[Ĥé").size(), new_string->value->spans[2].first_char);
+ EXPECT_EQ(std::u16string(u"[Ĥéļ").size(), new_string->value->spans[2].last_char);
+
+ EXPECT_EQ(std::u16string(u"[Ĥéļļö ").size(), new_string->value->spans[3].first_char);
+ EXPECT_EQ(std::u16string(u"[Ĥéļļö ŵ").size(), new_string->value->spans[3].last_char);
+}
+
+TEST(PseudolocaleGeneratorTest, PseudolocalizeAdjacentNestedTags) {
+ StringPool pool;
+ StyleString original_style;
+ original_style.str = "bold";
+ original_style.spans = {Span{"b", 0, 3}, Span{"i", 0, 3}};
+
+ std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString(
+ util::make_unique<StyledString>(pool.MakeRef(original_style)).get(),
+ Pseudolocalizer::Method::kAccent, &pool);
+ ASSERT_NE(nullptr, new_string);
+ ASSERT_EQ(2u, new_string->value->spans.size());
+ EXPECT_EQ(std::string("[ɓöļð one]"), *new_string->value->str);
+
+ EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name);
+ EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char);
+ EXPECT_EQ(std::u16string(u"[ɓöļ").size(), new_string->value->spans[0].last_char);
+
+ EXPECT_EQ(std::string("i"), *new_string->value->spans[1].name);
+ EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[1].first_char);
+ EXPECT_EQ(std::u16string(u"[ɓöļ").size(), new_string->value->spans[1].last_char);
+}
+
+TEST(PseudolocaleGeneratorTest, PseudolocalizeAdjacentTagsUnsorted) {
+ StringPool pool;
+ StyleString original_style;
+ original_style.str = "bold";
+ original_style.spans = {Span{"i", 2, 3}, Span{"b", 0, 1}};
+
+ std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString(
+ util::make_unique<StyledString>(pool.MakeRef(original_style)).get(),
+ Pseudolocalizer::Method::kAccent, &pool);
+ ASSERT_NE(nullptr, new_string);
+ ASSERT_EQ(2u, new_string->value->spans.size());
+ EXPECT_EQ(std::string("[ɓöļð one]"), *new_string->value->str);
+
+ EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name);
+ EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char);
+ EXPECT_EQ(std::u16string(u"[ɓ").size(), new_string->value->spans[0].last_char);
+
+ EXPECT_EQ(std::string("i"), *new_string->value->spans[1].name);
+ EXPECT_EQ(std::u16string(u"[ɓö").size(), new_string->value->spans[1].first_char);
+ EXPECT_EQ(std::u16string(u"[ɓöļ").size(), new_string->value->spans[1].last_char);
+}
+
+TEST(PseudolocaleGeneratorTest, PseudolocalizeNestedAndAdjacentTags) {
+ StringPool pool;
+ StyleString original_style;
+ original_style.str = "This sentence is not what you think it is at all.";
+ original_style.spans = {Span{"b", 16u, 19u}, Span{"em", 29u, 47u}, Span{"i", 38u, 40u},
+ Span{"b", 44u, 47u}};
+
+ std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString(
+ util::make_unique<StyledString>(pool.MakeRef(original_style)).get(),
+ Pseudolocalizer::Method::kAccent, &pool);
+ ASSERT_NE(nullptr, new_string);
+ ASSERT_EQ(4u, new_string->value->spans.size());
+ EXPECT_EQ(std::string(
+ "[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļļ. one two three four five six]"),
+ *new_string->value->str);
+
+ EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name);
+ EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš").size(), new_string->value->spans[0].first_char);
+ EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñö").size(), new_string->value->spans[0].last_char);
+
+ EXPECT_EQ(std::string("em"), *new_string->value->spans[1].name);
+ EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû").size(),
new_string->value->spans[1].first_char);
- EXPECT_EQ(std::string("[Ĥéļļö ŵ").size(),
+ EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļ").size(),
new_string->value->spans[1].last_char);
- EXPECT_EQ(std::string("[Ĥ").size(), new_string->value->spans[2].first_char);
- EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļ").size(),
+ EXPECT_EQ(std::string("i"), *new_string->value->spans[2].name);
+ EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ").size(),
+ new_string->value->spans[2].first_char);
+ EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ î").size(),
new_string->value->spans[2].last_char);
- EXPECT_EQ(std::string("[").size(), new_string->value->spans[3].first_char);
- EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð").size(),
+ EXPECT_EQ(std::string("b"), *new_string->value->spans[3].name);
+ EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ").size(),
+ new_string->value->spans[3].first_char);
+ EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļ").size(),
new_string->value->spans[3].last_char);
}
+TEST(PseudolocaleGeneratorTest, PseudolocalizePartsOfString) {
+ StringPool pool;
+ StyleString original_style;
+ original_style.str = "This should NOT be pseudolocalized.";
+ original_style.spans = {Span{"em", 4u, 14u}, Span{"i", 18u, 33u}};
+ std::unique_ptr<StyledString> original_string =
+ util::make_unique<StyledString>(pool.MakeRef(original_style));
+ original_string->untranslatable_sections = {UntranslatableSection{11u, 15u}};
+
+ std::unique_ptr<StyledString> new_string =
+ PseudolocalizeStyledString(original_string.get(), Pseudolocalizer::Method::kAccent, &pool);
+ ASSERT_NE(nullptr, new_string);
+ ASSERT_EQ(2u, new_string->value->spans.size());
+ EXPECT_EQ(std::string("[Ţĥîš šĥöûļð NOT ɓé þšéûðöļöçåļîžéð. one two three four]"),
+ *new_string->value->str);
+
+ EXPECT_EQ(std::string("em"), *new_string->value->spans[0].name);
+ EXPECT_EQ(std::u16string(u"[Ţĥîš").size(), new_string->value->spans[0].first_char);
+ EXPECT_EQ(std::u16string(u"[Ţĥîš šĥöûļð NO").size(), new_string->value->spans[0].last_char);
+
+ EXPECT_EQ(std::string("i"), *new_string->value->spans[1].name);
+ EXPECT_EQ(std::u16string(u"[Ţĥîš šĥöûļð NOT ɓé").size(), new_string->value->spans[1].first_char);
+ EXPECT_EQ(std::u16string(u"[Ţĥîš šĥöûļð NOT ɓé þšéûðöļöçåļîžé").size(),
+ new_string->value->spans[1].last_char);
+}
+
TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
@@ -138,7 +240,7 @@
{
StyleString original_style;
original_style.str = "Hello world!";
- original_style.spans = {Span{"b", 2, 3}, Span{"b", 6, 7}, Span{"i", 1, 10}};
+ original_style.spans = {Span{"i", 1, 10}, Span{"b", 2, 3}, Span{"b", 6, 7}};
auto styled_string =
util::make_unique<StyledString>(table->string_pool.MakeRef(original_style));