Merge "Update font config name."
diff --git a/Android.bp b/Android.bp
index 9e9faf2..04e1dd6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -147,9 +147,10 @@
"core/java/android/hardware/display/IDisplayManager.aidl",
"core/java/android/hardware/display/IDisplayManagerCallback.aidl",
"core/java/android/hardware/display/IVirtualDisplayCallback.aidl",
+ "core/java/android/hardware/fingerprint/IFingerprintClientActiveCallback.aidl",
+ "core/java/android/hardware/fingerprint/IFingerprintDialogReceiver.aidl",
"core/java/android/hardware/fingerprint/IFingerprintService.aidl",
"core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl",
- "core/java/android/hardware/fingerprint/IFingerprintClientActiveCallback.aidl",
"core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl",
"core/java/android/hardware/hdmi/IHdmiControlCallback.aidl",
"core/java/android/hardware/hdmi/IHdmiControlService.aidl",
@@ -177,6 +178,8 @@
"core/java/android/hardware/location/IContextHubClientCallback.aidl",
"core/java/android/hardware/location/IContextHubService.aidl",
"core/java/android/hardware/location/IContextHubTransactionCallback.aidl",
+ "core/java/android/hardware/radio/IAnnouncementListener.aidl",
+ "core/java/android/hardware/radio/ICloseHandle.aidl",
"core/java/android/hardware/radio/IRadioService.aidl",
"core/java/android/hardware/radio/ITuner.aidl",
"core/java/android/hardware/radio/ITunerCallback.aidl",
@@ -630,6 +633,11 @@
],
},
+ // See comment on framework-oahl-backward-compatibility module below
+ exclude_srcs: [
+ "core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java",
+ ],
+
no_framework_libs: true,
libs: [
"conscrypt",
@@ -665,6 +673,18 @@
],
}
+// A temporary build target that is conditionally included on the bootclasspath if
+// org.apache.http.legacy library has been removed and which provides support for
+// maintaining backwards compatibility for APKs that target pre-P and depend on
+// org.apache.http.legacy classes. This is used iff REMOVE_OAHL_FROM_BCP=true is
+// specified on the build command line.
+java_library {
+ name: "framework-oahl-backward-compatibility",
+ srcs: [
+ "core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java",
+ ],
+}
+
genrule {
name: "framework-statslog-gen",
tools: ["stats-log-api-gen"],
diff --git a/api/current.txt b/api/current.txt
index 6ae25bc..5e91789 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -72,6 +72,7 @@
field public static final java.lang.String DUMP = "android.permission.DUMP";
field public static final java.lang.String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
field public static final java.lang.String FACTORY_TEST = "android.permission.FACTORY_TEST";
+ field public static final java.lang.String FOREGROUND_SERVICE = "android.permission.FOREGROUND_SERVICE";
field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
@@ -3764,6 +3765,7 @@
method public final void requestShowKeyboardShortcuts();
method public deprecated boolean requestVisibleBehind(boolean);
method public final boolean requestWindowFeature(int);
+ method public final <T extends android.view.View> T requireViewById(int);
method public final void runOnUiThread(java.lang.Runnable);
method public void setActionBar(android.widget.Toolbar);
method public void setContentTransitionManager(android.transition.TransitionManager);
@@ -4458,6 +4460,7 @@
method public void openOptionsMenu();
method public void registerForContextMenu(android.view.View);
method public final boolean requestWindowFeature(int);
+ method public final <T extends android.view.View> T requireViewById(int);
method public void setCancelMessage(android.os.Message);
method public void setCancelable(boolean);
method public void setCanceledOnTouchOutside(boolean);
@@ -5693,6 +5696,7 @@
method public final void setInterruptionFilter(int);
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
+ field public static final java.lang.String ACTION_APP_BLOCK_STATE_CHANGED = "android.app.action.APP_BLOCK_STATE_CHANGED";
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
@@ -6344,6 +6348,7 @@
method public void onReceive(android.content.Context, android.content.Intent);
method public void onSecurityLogsAvailable(android.content.Context, android.content.Intent);
method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long);
+ method public void onTransferAffiliatedProfileOwnershipComplete(android.content.Context, android.os.UserHandle);
method public void onTransferOwnershipComplete(android.content.Context, android.os.PersistableBundle);
method public void onUserAdded(android.content.Context, android.content.Intent, android.os.UserHandle);
method public void onUserRemoved(android.content.Context, android.content.Intent, android.os.UserHandle);
@@ -6365,7 +6370,7 @@
field public static final java.lang.String DEVICE_ADMIN_META_DATA = "android.app.device_admin";
field public static final java.lang.String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING";
field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE";
- field public static final java.lang.String EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE = "android.app.extra.TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE";
+ field public static final java.lang.String EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE = "android.app.extra.TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE";
field public static final java.lang.String SUPPORT_TRANSFER_OWNERSHIP_META_DATA = "android.app.support_transfer_ownership";
}
@@ -6377,6 +6382,7 @@
public class DevicePolicyManager {
method public void addCrossProfileIntentFilter(android.content.ComponentName, android.content.IntentFilter, int);
method public boolean addCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
+ method public int addOverrideApn(android.content.ComponentName, android.telephony.data.ApnSetting);
method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName);
method public void addUserRestriction(android.content.ComponentName, java.lang.String);
method public boolean bindDeviceAdminServiceAsUser(android.content.ComponentName, android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle);
@@ -6420,8 +6426,10 @@
method public android.content.ComponentName getMandatoryBackupTransport();
method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
method public long getMaximumTimeToLock(android.content.ComponentName);
+ method public java.util.List<java.lang.String> getMeteredDataDisabled(android.content.ComponentName);
method public int getOrganizationColor(android.content.ComponentName);
method public java.lang.CharSequence getOrganizationName(android.content.ComponentName);
+ method public java.util.List<android.telephony.data.ApnSetting> getOverrideApns(android.content.ComponentName);
method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
method public java.lang.String getPasswordBlacklistName(android.content.ComponentName);
method public long getPasswordExpiration(android.content.ComponentName);
@@ -6450,6 +6458,7 @@
method public boolean getStorageEncryption(android.content.ComponentName);
method public int getStorageEncryptionStatus();
method public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy();
+ method public android.os.PersistableBundle getTransferOwnershipBundle();
method public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName);
method public android.os.Bundle getUserRestrictions(android.content.ComponentName);
method public java.lang.String getWifiMacAddress(android.content.ComponentName);
@@ -6473,6 +6482,7 @@
method public boolean isManagedProfile(android.content.ComponentName);
method public boolean isMasterVolumeMuted(android.content.ComponentName);
method public boolean isNetworkLoggingEnabled(android.content.ComponentName);
+ method public boolean isOverrideApnEnabled(android.content.ComponentName);
method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public boolean isPrintingEnabled();
method public boolean isProfileOwnerApp(java.lang.String);
@@ -6488,6 +6498,7 @@
method public void removeActiveAdmin(android.content.ComponentName);
method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
method public boolean removeKeyPair(android.content.ComponentName, java.lang.String);
+ method public boolean removeOverrideApn(android.content.ComponentName, int);
method public boolean removeUser(android.content.ComponentName, android.os.UserHandle);
method public boolean requestBugreport(android.content.ComponentName);
method public boolean resetPassword(java.lang.String, int);
@@ -6524,9 +6535,11 @@
method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int);
method public void setMaximumTimeToLock(android.content.ComponentName, long);
+ method public java.util.List<java.lang.String> setMeteredDataDisabled(android.content.ComponentName, java.util.List<java.lang.String>);
method public void setNetworkLoggingEnabled(android.content.ComponentName, boolean);
method public void setOrganizationColor(android.content.ComponentName, int);
method public void setOrganizationName(android.content.ComponentName, java.lang.CharSequence);
+ method public void setOverrideApnsEnabled(android.content.ComponentName, boolean);
method public java.lang.String[] setPackagesSuspended(android.content.ComponentName, java.lang.String[], boolean);
method public boolean setPasswordBlacklist(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>);
method public void setPasswordExpirationTimeout(android.content.ComponentName, long);
@@ -6571,6 +6584,7 @@
method public void transferOwnership(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle);
method public void uninstallAllUserCaCerts(android.content.ComponentName);
method public void uninstallCaCert(android.content.ComponentName, byte[]);
+ method public boolean updateOverrideApn(android.content.ComponentName, int, android.telephony.data.ApnSetting);
method public void wipeData(int);
method public void wipeDataWithReason(int, java.lang.CharSequence);
field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
@@ -7100,6 +7114,7 @@
field public static final android.os.Parcelable.Creator<android.app.slice.Slice> CREATOR;
field public static final java.lang.String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
field public static final java.lang.String HINT_ACTIONS = "actions";
+ field public static final java.lang.String HINT_CALLER_NEEDED = "caller_needed";
field public static final java.lang.String HINT_HORIZONTAL = "horizontal";
field public static final java.lang.String HINT_LARGE = "large";
field public static final java.lang.String HINT_LIST = "list";
@@ -7128,8 +7143,6 @@
method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice, java.lang.String);
method public android.app.slice.Slice.Builder addBundle(android.os.Bundle, java.lang.String, java.lang.String...);
method public android.app.slice.Slice.Builder addBundle(android.os.Bundle, java.lang.String, java.util.List<java.lang.String>);
- method public deprecated android.app.slice.Slice.Builder addColor(int, java.lang.String, java.lang.String...);
- method public deprecated android.app.slice.Slice.Builder addColor(int, java.lang.String, java.util.List<java.lang.String>);
method public android.app.slice.Slice.Builder addHints(java.lang.String...);
method public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>);
method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String, java.lang.String...);
@@ -7152,7 +7165,6 @@
method public int describeContents();
method public android.app.PendingIntent getAction();
method public android.os.Bundle getBundle();
- method public deprecated int getColor();
method public java.lang.String getFormat();
method public java.util.List<java.lang.String> getHints();
method public android.graphics.drawable.Icon getIcon();
@@ -7167,7 +7179,6 @@
field public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR;
field public static final java.lang.String FORMAT_ACTION = "action";
field public static final java.lang.String FORMAT_BUNDLE = "bundle";
- field public static final deprecated java.lang.String FORMAT_COLOR = "color";
field public static final java.lang.String FORMAT_IMAGE = "image";
field public static final java.lang.String FORMAT_INT = "int";
field public static final java.lang.String FORMAT_REMOTE_INPUT = "input";
@@ -7197,6 +7208,7 @@
public abstract class SliceProvider extends android.content.ContentProvider {
ctor public SliceProvider();
method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
+ method public final java.lang.String getBindingPackage();
method public final java.lang.String getType(android.net.Uri);
method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
@@ -10873,7 +10885,8 @@
field public android.content.pm.ServiceInfo[] services;
field public java.lang.String sharedUserId;
field public int sharedUserLabel;
- field public android.content.pm.Signature[] signatures;
+ field public deprecated android.content.pm.Signature[] signatures;
+ field public android.content.pm.Signature[][] signingCertificateHistory;
field public java.lang.String[] splitNames;
field public int[] splitRevisionCodes;
field public deprecated int versionCode;
@@ -11076,6 +11089,8 @@
method public abstract android.graphics.drawable.Drawable getUserBadgedIcon(android.graphics.drawable.Drawable, android.os.UserHandle);
method public abstract java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle);
method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
+ method public boolean hasSigningCertificate(java.lang.String, byte[], int);
+ method public boolean hasSigningCertificate(int, byte[], int);
method public abstract boolean hasSystemFeature(java.lang.String);
method public abstract boolean hasSystemFeature(java.lang.String, int);
method public abstract boolean isInstantApp();
@@ -11101,6 +11116,8 @@
method public abstract void setInstallerPackageName(java.lang.String, java.lang.String);
method public abstract void updateInstantAppCookie(byte[]);
method public abstract void verifyPendingInstall(int, int);
+ field public static final int CERT_INPUT_RAW_X509 = 0; // 0x0
+ field public static final int CERT_INPUT_SHA256 = 1; // 0x1
field public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0; // 0x0
field public static final int COMPONENT_ENABLED_STATE_DISABLED = 2; // 0x2
field public static final int COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4; // 0x4
@@ -11219,7 +11236,8 @@
field public static final int GET_RESOLVED_FILTER = 64; // 0x40
field public static final int GET_SERVICES = 4; // 0x4
field public static final int GET_SHARED_LIBRARY_FILES = 1024; // 0x400
- field public static final int GET_SIGNATURES = 64; // 0x40
+ field public static final deprecated int GET_SIGNATURES = 64; // 0x40
+ field public static final int GET_SIGNING_CERTIFICATES = 134217728; // 0x8000000
field public static final deprecated int GET_UNINSTALLED_PACKAGES = 8192; // 0x2000
field public static final int GET_URI_PERMISSION_PATTERNS = 2048; // 0x800
field public static final int INSTALL_REASON_DEVICE_RESTORE = 2; // 0x2
@@ -16396,6 +16414,20 @@
package android.hardware.fingerprint {
+ public class FingerprintDialog {
+ method public void authenticate(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.CancellationSignal, java.util.concurrent.Executor, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback);
+ method public void authenticate(android.os.CancellationSignal, java.util.concurrent.Executor, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback);
+ }
+
+ public static class FingerprintDialog.Builder {
+ ctor public FingerprintDialog.Builder();
+ method public android.hardware.fingerprint.FingerprintDialog build(android.content.Context);
+ method public android.hardware.fingerprint.FingerprintDialog.Builder setDescription(java.lang.CharSequence);
+ method public android.hardware.fingerprint.FingerprintDialog.Builder setNegativeButton(java.lang.CharSequence, java.util.concurrent.Executor, android.content.DialogInterface.OnClickListener);
+ method public android.hardware.fingerprint.FingerprintDialog.Builder setSubtitle(java.lang.CharSequence);
+ method public android.hardware.fingerprint.FingerprintDialog.Builder setTitle(java.lang.CharSequence);
+ }
+
public class FingerprintManager {
method public void authenticate(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.CancellationSignal, int, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, android.os.Handler);
method public boolean hasEnrolledFingerprints();
@@ -21587,6 +21619,7 @@
method public android.location.LocationProvider getProvider(java.lang.String);
method public java.util.List<java.lang.String> getProviders(boolean);
method public java.util.List<java.lang.String> getProviders(android.location.Criteria, boolean);
+ method public boolean isLocationEnabled();
method public boolean isProviderEnabled(java.lang.String);
method public boolean registerGnssMeasurementsCallback(android.location.GnssMeasurementsEvent.Callback);
method public boolean registerGnssMeasurementsCallback(android.location.GnssMeasurementsEvent.Callback, android.os.Handler);
@@ -23213,8 +23246,10 @@
method public java.lang.String getDefaultUrl();
method public int getRequestType();
field public static final int REQUEST_TYPE_INITIAL = 0; // 0x0
+ field public static final int REQUEST_TYPE_NONE = 3; // 0x3
field public static final int REQUEST_TYPE_RELEASE = 2; // 0x2
field public static final int REQUEST_TYPE_RENEWAL = 1; // 0x1
+ field public static final int REQUEST_TYPE_UPDATE = 4; // 0x4
}
public static final class MediaDrm.KeyStatus {
@@ -35903,6 +35938,7 @@
field public static final java.lang.String ACTION_SETTINGS = "android.settings.SETTINGS";
field public static final java.lang.String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
field public static final java.lang.String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS";
+ field public static final java.lang.String ACTION_STORAGE_VOLUME_ACCESS_SETTINGS = "android.settings.STORAGE_VOLUME_ACCESS_SETTINGS";
field public static final java.lang.String ACTION_SYNC_SETTINGS = "android.settings.SYNC_SETTINGS";
field public static final java.lang.String ACTION_USAGE_ACCESS_SETTINGS = "android.settings.USAGE_ACCESS_SETTINGS";
field public static final java.lang.String ACTION_USER_DICTIONARY_SETTINGS = "android.settings.USER_DICTIONARY_SETTINGS";
@@ -36034,11 +36070,11 @@
field public static final deprecated java.lang.String HTTP_PROXY = "http_proxy";
field public static final java.lang.String INPUT_METHOD_SELECTOR_VISIBILITY = "input_method_selector_visibility";
field public static final deprecated java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
- field public static final java.lang.String LOCATION_MODE = "location_mode";
- field public static final int LOCATION_MODE_BATTERY_SAVING = 2; // 0x2
- field public static final int LOCATION_MODE_HIGH_ACCURACY = 3; // 0x3
- field public static final int LOCATION_MODE_OFF = 0; // 0x0
- field public static final int LOCATION_MODE_SENSORS_ONLY = 1; // 0x1
+ field public static final deprecated java.lang.String LOCATION_MODE = "location_mode";
+ field public static final deprecated int LOCATION_MODE_BATTERY_SAVING = 2; // 0x2
+ field public static final deprecated int LOCATION_MODE_HIGH_ACCURACY = 3; // 0x3
+ field public static final deprecated int LOCATION_MODE_OFF = 0; // 0x0
+ field public static final deprecated int LOCATION_MODE_SENSORS_ONLY = 1; // 0x1
field public static final deprecated java.lang.String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";
field public static final deprecated java.lang.String LOCK_PATTERN_ENABLED = "lock_pattern_autolock";
field public static final deprecated java.lang.String LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED = "lock_pattern_tactile_feedback_enabled";
@@ -36454,6 +36490,7 @@
field public static final int RESULT_SMS_HANDLED = 1; // 0x1
field public static final int RESULT_SMS_OUT_OF_MEMORY = 3; // 0x3
field public static final int RESULT_SMS_UNSUPPORTED = 4; // 0x4
+ field public static final java.lang.String SECRET_CODE_ACTION = "android.provider.Telephony.SECRET_CODE";
field public static final java.lang.String SIM_FULL_ACTION = "android.provider.Telephony.SIM_FULL";
field public static final java.lang.String SMS_CB_RECEIVED_ACTION = "android.provider.Telephony.SMS_CB_RECEIVED";
field public static final java.lang.String SMS_DELIVER_ACTION = "android.provider.Telephony.SMS_DELIVER";
@@ -38423,6 +38460,7 @@
method public void onWindowFocusChanged(boolean);
method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback);
method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
+ method public final <T extends android.view.View> T requireViewById(int);
method public void setContentView(int);
method public void setContentView(android.view.View);
method public void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
@@ -40901,6 +40939,7 @@
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+ field public static final java.lang.String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
field public static final java.lang.String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool";
field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
@@ -41594,10 +41633,16 @@
method public static int getDefaultSmsSubscriptionId();
method public static int getDefaultSubscriptionId();
method public static int getDefaultVoiceSubscriptionId();
+ method public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int);
method public boolean isNetworkRoaming(int);
method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
+ method public void setSubscriptionOverrideCongested(int, boolean, long);
+ method public void setSubscriptionOverrideUnmetered(int, boolean, long);
+ method public void setSubscriptionPlans(int, java.util.List<android.telephony.SubscriptionPlan>);
field public static final java.lang.String ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED";
field public static final java.lang.String ACTION_DEFAULT_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED";
+ field public static final java.lang.String ACTION_MANAGE_SUBSCRIPTION_PLANS = "android.telephony.action.MANAGE_SUBSCRIPTION_PLANS";
+ field public static final java.lang.String ACTION_REFRESH_SUBSCRIPTION_PLANS = "android.telephony.action.REFRESH_SUBSCRIPTION_PLANS";
field public static final int DATA_ROAMING_DISABLE = 0; // 0x0
field public static final int DATA_ROAMING_ENABLE = 1; // 0x1
field public static final java.lang.String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
@@ -41609,6 +41654,38 @@
method public void onSubscriptionsChanged();
}
+ public final class SubscriptionPlan implements android.os.Parcelable {
+ method public java.util.Iterator<android.util.Pair<java.time.ZonedDateTime, java.time.ZonedDateTime>> cycleIterator();
+ method public int describeContents();
+ method public int getDataLimitBehavior();
+ method public long getDataLimitBytes();
+ method public long getDataUsageBytes();
+ method public long getDataUsageTime();
+ method public java.lang.CharSequence getSummary();
+ method public java.lang.CharSequence getTitle();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final long BYTES_UNKNOWN = -1L; // 0xffffffffffffffffL
+ field public static final long BYTES_UNLIMITED = 9223372036854775807L; // 0x7fffffffffffffffL
+ field public static final android.os.Parcelable.Creator<android.telephony.SubscriptionPlan> CREATOR;
+ field public static final int LIMIT_BEHAVIOR_BILLED = 1; // 0x1
+ field public static final int LIMIT_BEHAVIOR_DISABLED = 0; // 0x0
+ field public static final int LIMIT_BEHAVIOR_THROTTLED = 2; // 0x2
+ field public static final int LIMIT_BEHAVIOR_UNKNOWN = -1; // 0xffffffff
+ field public static final long TIME_UNKNOWN = -1L; // 0xffffffffffffffffL
+ }
+
+ public static class SubscriptionPlan.Builder {
+ method public android.telephony.SubscriptionPlan build();
+ method public static android.telephony.SubscriptionPlan.Builder createNonrecurring(java.time.ZonedDateTime, java.time.ZonedDateTime);
+ method public static android.telephony.SubscriptionPlan.Builder createRecurringDaily(java.time.ZonedDateTime);
+ method public static android.telephony.SubscriptionPlan.Builder createRecurringMonthly(java.time.ZonedDateTime);
+ method public static android.telephony.SubscriptionPlan.Builder createRecurringWeekly(java.time.ZonedDateTime);
+ method public android.telephony.SubscriptionPlan.Builder setDataLimit(long, int);
+ method public android.telephony.SubscriptionPlan.Builder setDataUsage(long, long);
+ method public android.telephony.SubscriptionPlan.Builder setSummary(java.lang.CharSequence);
+ method public android.telephony.SubscriptionPlan.Builder setTitle(java.lang.CharSequence);
+ }
+
public class TelephonyManager {
method public boolean canChangeDtmfToneLength();
method public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle);
@@ -45704,6 +45781,7 @@
field public static final int KEYCODE_PROG_YELLOW = 185; // 0xb9
field public static final int KEYCODE_Q = 45; // 0x2d
field public static final int KEYCODE_R = 46; // 0x2e
+ field public static final int KEYCODE_REFRESH = 285; // 0x11d
field public static final int KEYCODE_RIGHT_BRACKET = 72; // 0x48
field public static final int KEYCODE_RO = 217; // 0xd9
field public static final int KEYCODE_S = 47; // 0x2f
@@ -46870,6 +46948,7 @@
method public boolean requestRectangleOnScreen(android.graphics.Rect);
method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
method public final void requestUnbufferedDispatch(android.view.MotionEvent);
+ method public final <T extends android.view.View> T requireViewById(int);
method public static int resolveSize(int, int);
method public static int resolveSizeAndState(int, int, int);
method public boolean restoreDefaultFocus();
@@ -47872,6 +47951,7 @@
method protected final int getLocalFeatures();
method public android.media.session.MediaController getMediaController();
method public abstract int getNavigationBarColor();
+ method public int getNavigationBarDividerColor();
method public android.transition.Transition getReenterTransition();
method public android.transition.Transition getReturnTransition();
method public android.transition.Transition getSharedElementEnterTransition();
@@ -47904,6 +47984,7 @@
method public abstract boolean performPanelShortcut(int, int, android.view.KeyEvent, int);
method public final void removeOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener);
method public boolean requestFeature(int);
+ method public final <T extends android.view.View> T requireViewById(int);
method public abstract void restoreHierarchyState(android.os.Bundle);
method public abstract android.os.Bundle saveHierarchyState();
method public void setAllowEnterTransitionOverlap(boolean);
@@ -47940,6 +48021,7 @@
method public void setLogo(int);
method public void setMediaController(android.media.session.MediaController);
method public abstract void setNavigationBarColor(int);
+ method public void setNavigationBarDividerColor(int);
method public void setReenterTransition(android.transition.Transition);
method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable);
method public final void setRestrictedCaptionAreaListener(android.view.Window.OnRestrictedCaptionAreaChangedListener);
@@ -50322,6 +50404,7 @@
method public android.graphics.Bitmap getFavicon();
method public android.webkit.WebView.HitTestResult getHitTestResult();
method public deprecated java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String);
+ method public android.os.Looper getLooper();
method public java.lang.String getOriginalUrl();
method public int getProgress();
method public boolean getRendererPriorityWaivedWhenNotVisible();
diff --git a/api/system-current.txt b/api/system-current.txt
index bfe44c9..8678c5e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -382,7 +382,6 @@
method public java.lang.CharSequence getDeviceOwnerOrganizationName();
method public java.util.List<java.lang.String> getPermittedAccessibilityServices(int);
method public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser();
- method public java.lang.CharSequence getPrintingDisabledReason();
method public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException;
method public java.lang.String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException;
method public int getUserProvisioningState();
@@ -1738,6 +1737,27 @@
package android.hardware.radio {
+ public final class Announcement implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.hardware.radio.ProgramSelector getSelector();
+ method public int getType();
+ method public java.util.Map<java.lang.String, java.lang.String> getVendorInfo();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.radio.Announcement> CREATOR;
+ field public static final int TYPE_EMERGENCY = 1; // 0x1
+ field public static final int TYPE_EVENT = 6; // 0x6
+ field public static final int TYPE_MISC = 8; // 0x8
+ field public static final int TYPE_NEWS = 5; // 0x5
+ field public static final int TYPE_SPORT = 7; // 0x7
+ field public static final int TYPE_TRAFFIC = 3; // 0x3
+ field public static final int TYPE_WARNING = 2; // 0x2
+ field public static final int TYPE_WEATHER = 4; // 0x4
+ }
+
+ public static abstract interface Announcement.OnListUpdatedListener {
+ method public abstract void onListUpdated(java.util.Collection<android.hardware.radio.Announcement>);
+ }
+
public final class ProgramList implements java.lang.AutoCloseable {
method public void addOnCompleteListener(java.util.concurrent.Executor, android.hardware.radio.ProgramList.OnCompleteListener);
method public void addOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener);
@@ -1782,6 +1802,7 @@
method public deprecated int getProgramType();
method public android.hardware.radio.ProgramSelector.Identifier[] getSecondaryIds();
method public deprecated long[] getVendorIds();
+ method public android.hardware.radio.ProgramSelector withSecondaryPreferred(android.hardware.radio.ProgramSelector.Identifier);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector> CREATOR;
field public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1; // 0x1
@@ -1832,8 +1853,11 @@
}
public class RadioManager {
+ method public void addAnnouncementListener(java.util.Set<java.lang.Integer>, android.hardware.radio.Announcement.OnListUpdatedListener);
+ method public void addAnnouncementListener(java.util.concurrent.Executor, java.util.Set<java.lang.Integer>, android.hardware.radio.Announcement.OnListUpdatedListener);
method public int listModules(java.util.List<android.hardware.radio.RadioManager.ModuleProperties>);
method public android.hardware.radio.RadioTuner openTuner(int, android.hardware.radio.RadioManager.BandConfig, boolean, android.hardware.radio.RadioTuner.Callback, android.os.Handler);
+ method public void removeAnnouncementListener(android.hardware.radio.Announcement.OnListUpdatedListener);
field public static final int BAND_AM = 0; // 0x0
field public static final int BAND_AM_HD = 3; // 0x3
field public static final int BAND_FM = 1; // 0x1
@@ -1963,12 +1987,15 @@
public static class RadioManager.ProgramInfo implements android.os.Parcelable {
method public int describeContents();
method public deprecated int getChannel();
+ method public android.hardware.radio.ProgramSelector.Identifier getLogicallyTunedTo();
method public android.hardware.radio.RadioMetadata getMetadata();
+ method public android.hardware.radio.ProgramSelector.Identifier getPhysicallyTunedTo();
+ method public java.util.Collection<android.hardware.radio.ProgramSelector.Identifier> getRelatedContent();
method public android.hardware.radio.ProgramSelector getSelector();
method public int getSignalStrength();
method public deprecated int getSubChannel();
method public java.util.Map<java.lang.String, java.lang.String> getVendorInfo();
- method public boolean isDigital();
+ method public deprecated boolean isDigital();
method public boolean isLive();
method public boolean isMuted();
method public boolean isStereo();
@@ -2333,11 +2360,15 @@
method public deprecated boolean addGpsNavigationMessageListener(android.location.GpsNavigationMessageEvent.Listener);
method public void flushGnssBatch();
method public int getGnssBatchSize();
+ method public boolean isLocationEnabledForUser(android.os.UserHandle);
+ method public boolean isProviderEnabledForUser(java.lang.String, android.os.UserHandle);
method public boolean registerGnssBatchedLocationCallback(long, boolean, android.location.BatchedLocationCallback, android.os.Handler);
method public deprecated void removeGpsMeasurementListener(android.location.GpsMeasurementsEvent.Listener);
method public deprecated void removeGpsNavigationMessageListener(android.location.GpsNavigationMessageEvent.Listener);
method public void requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper);
method public void requestLocationUpdates(android.location.LocationRequest, android.app.PendingIntent);
+ method public void setLocationEnabledForUser(boolean, android.os.UserHandle);
+ method public boolean setProviderEnabledForUser(java.lang.String, boolean, android.os.UserHandle);
method public boolean unregisterGnssBatchedLocationCallback(android.location.BatchedLocationCallback);
}
@@ -2867,6 +2898,7 @@
}
public final class IpSecManager {
+ method public void applyTunnelModeTransform(android.net.IpSecManager.IpSecTunnelInterface, int, android.net.IpSecTransform) throws java.io.IOException;
method public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(java.net.InetAddress, java.net.InetAddress, android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
}
@@ -3898,6 +3930,120 @@
}
+package android.security.keystore.recovery {
+
+ public class DecryptionFailedException extends java.security.GeneralSecurityException {
+ ctor public DecryptionFailedException(java.lang.String);
+ }
+
+ public class InternalRecoveryServiceException extends java.security.GeneralSecurityException {
+ ctor public InternalRecoveryServiceException(java.lang.String);
+ ctor public InternalRecoveryServiceException(java.lang.String, java.lang.Throwable);
+ }
+
+ public final class KeyChainProtectionParams implements android.os.Parcelable {
+ method public void clearSecret();
+ method public int describeContents();
+ method public android.security.keystore.recovery.KeyDerivationParams getKeyDerivationParams();
+ method public int getLockScreenUiFormat();
+ method public byte[] getSecret();
+ method public int getUserSecretType();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.security.keystore.recovery.KeyChainProtectionParams> CREATOR;
+ field public static final int TYPE_CUSTOM_PASSWORD = 101; // 0x65
+ field public static final int TYPE_LOCKSCREEN = 100; // 0x64
+ field public static final int UI_FORMAT_PASSWORD = 2; // 0x2
+ field public static final int UI_FORMAT_PATTERN = 3; // 0x3
+ field public static final int UI_FORMAT_PIN = 1; // 0x1
+ }
+
+ public static class KeyChainProtectionParams.Builder {
+ ctor public KeyChainProtectionParams.Builder();
+ method public android.security.keystore.recovery.KeyChainProtectionParams build();
+ method public android.security.keystore.recovery.KeyChainProtectionParams.Builder setKeyDerivationParams(android.security.keystore.recovery.KeyDerivationParams);
+ method public android.security.keystore.recovery.KeyChainProtectionParams.Builder setLockScreenUiFormat(int);
+ method public android.security.keystore.recovery.KeyChainProtectionParams.Builder setSecret(byte[]);
+ method public android.security.keystore.recovery.KeyChainProtectionParams.Builder setUserSecretType(int);
+ }
+
+ public final class KeyChainSnapshot implements android.os.Parcelable {
+ method public int describeContents();
+ method public long getCounterId();
+ method public byte[] getEncryptedRecoveryKeyBlob();
+ method public java.util.List<android.security.keystore.recovery.KeyChainProtectionParams> getKeyChainProtectionParams();
+ method public int getMaxAttempts();
+ method public byte[] getServerParams();
+ method public int getSnapshotVersion();
+ method public byte[] getTrustedHardwarePublicKey();
+ method public java.util.List<android.security.keystore.recovery.WrappedApplicationKey> getWrappedApplicationKeys();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.security.keystore.recovery.KeyChainSnapshot> CREATOR;
+ }
+
+ public final class KeyDerivationParams implements android.os.Parcelable {
+ method public static android.security.keystore.recovery.KeyDerivationParams createSha256Params(byte[]);
+ method public int describeContents();
+ method public int getAlgorithm();
+ method public byte[] getSalt();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int ALGORITHM_SHA256 = 1; // 0x1
+ field public static final android.os.Parcelable.Creator<android.security.keystore.recovery.KeyDerivationParams> CREATOR;
+ }
+
+ public class LockScreenRequiredException extends java.security.GeneralSecurityException {
+ ctor public LockScreenRequiredException(java.lang.String);
+ }
+
+ public class RecoveryController {
+ method public byte[] generateAndStoreKey(java.lang.String, byte[]) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.LockScreenRequiredException;
+ method public java.util.List<java.lang.String> getAliases(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException;
+ method public static android.security.keystore.recovery.RecoveryController getInstance(android.content.Context);
+ method public int[] getPendingRecoverySecretTypes() throws android.security.keystore.recovery.InternalRecoveryServiceException;
+ method public android.security.keystore.recovery.KeyChainSnapshot getRecoveryData() throws android.security.keystore.recovery.InternalRecoveryServiceException;
+ method public int[] getRecoverySecretTypes() throws android.security.keystore.recovery.InternalRecoveryServiceException;
+ method public int getRecoveryStatus(java.lang.String, java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException;
+ method public void initRecoveryService(java.lang.String, byte[]) throws java.security.cert.CertificateException, android.security.keystore.recovery.InternalRecoveryServiceException;
+ method public void recoverySecretAvailable(android.security.keystore.recovery.KeyChainProtectionParams) throws android.security.keystore.recovery.InternalRecoveryServiceException;
+ method public void removeKey(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException;
+ method public void setRecoverySecretTypes(int[]) throws android.security.keystore.recovery.InternalRecoveryServiceException;
+ method public void setRecoveryStatus(java.lang.String, java.lang.String, int) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.content.pm.PackageManager.NameNotFoundException;
+ method public void setServerParams(byte[]) throws android.security.keystore.recovery.InternalRecoveryServiceException;
+ method public void setSnapshotCreatedPendingIntent(android.app.PendingIntent) throws android.security.keystore.recovery.InternalRecoveryServiceException;
+ field public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2; // 0x2
+ field public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3; // 0x3
+ field public static final int RECOVERY_STATUS_SYNCED = 0; // 0x0
+ field public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1; // 0x1
+ }
+
+ public class RecoverySession implements java.lang.AutoCloseable {
+ method public void close();
+ method public java.util.Map<java.lang.String, byte[]> recoverKeys(byte[], java.util.List<android.security.keystore.recovery.WrappedApplicationKey>) throws android.security.keystore.recovery.DecryptionFailedException, android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.SessionExpiredException;
+ method public byte[] start(byte[], byte[], byte[], java.util.List<android.security.keystore.recovery.KeyChainProtectionParams>) throws java.security.cert.CertificateException, android.security.keystore.recovery.InternalRecoveryServiceException;
+ }
+
+ public class SessionExpiredException extends java.security.GeneralSecurityException {
+ ctor public SessionExpiredException(java.lang.String);
+ }
+
+ public final class WrappedApplicationKey implements android.os.Parcelable {
+ method public int describeContents();
+ method public byte[] getAccount();
+ method public java.lang.String getAlias();
+ method public byte[] getEncryptedKeyMaterial();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.security.keystore.recovery.WrappedApplicationKey> CREATOR;
+ }
+
+ public static class WrappedApplicationKey.Builder {
+ ctor public WrappedApplicationKey.Builder();
+ method public android.security.keystore.recovery.WrappedApplicationKey build();
+ method public android.security.keystore.recovery.WrappedApplicationKey.Builder setAccount(byte[]);
+ method public android.security.keystore.recovery.WrappedApplicationKey.Builder setAlias(java.lang.String);
+ method public android.security.keystore.recovery.WrappedApplicationKey.Builder setEncryptedKeyMaterial(byte[]);
+ }
+
+}
+
package android.service.autofill {
public abstract class AutofillFieldClassificationService extends android.app.Service {
@@ -4390,6 +4536,12 @@
package android.telephony {
+ public static final class AccessNetworkConstants.TransportType {
+ ctor public AccessNetworkConstants.TransportType();
+ field public static final int WLAN = 2; // 0x2
+ field public static final int WWAN = 1; // 0x1
+ }
+
public class CarrierConfigManager {
method public static android.os.PersistableBundle getDefaultConfig();
method public void updateConfigForPhoneId(int, java.lang.String);
diff --git a/api/test-current.txt b/api/test-current.txt
index acc819e..254fc15 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -521,6 +521,7 @@
}
public static final class Settings.Global extends android.provider.Settings.NameValueTable {
+ field public static final java.lang.String LOCATION_GLOBAL_KILL_SWITCH = "location_global_kill_switch";
field public static final java.lang.String LOW_POWER_MODE = "low_power";
field public static final java.lang.String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
}
diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk
index 368a70b..2b00d9e 100644
--- a/cmds/incidentd/Android.mk
+++ b/cmds/incidentd/Android.mk
@@ -91,9 +91,7 @@
gen_src_dir:=
-ifeq ($(BUILD_WITH_INCIDENTD_RC), true)
LOCAL_INIT_RC := incidentd.rc
-endif
include $(BUILD_EXECUTABLE)
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 7a7a2f6..2526400 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -92,10 +92,20 @@
void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const {
std::vector<Field> uidFields;
- findFields(
- event->getFieldValueMap(),
- buildAttributionUidFieldMatcher(event->GetTagId(), Position::ANY),
- &uidFields);
+ if (android::util::kAtomsWithAttributionChain.find(event->GetTagId()) !=
+ android::util::kAtomsWithAttributionChain.end()) {
+ findFields(
+ event->getFieldValueMap(),
+ buildAttributionUidFieldMatcher(event->GetTagId(), Position::ANY),
+ &uidFields);
+ } else if (android::util::kAtomsWithUidField.find(event->GetTagId()) !=
+ android::util::kAtomsWithUidField.end()) {
+ findFields(
+ event->getFieldValueMap(),
+ buildSimpleAtomFieldMatcher(event->GetTagId(), 1 /* uid is always the 1st field. */),
+ &uidFields);
+ }
+
for (size_t i = 0; i < uidFields.size(); ++i) {
DimensionsValue* value = event->findFieldValueOrNull(uidFields[i]);
if (value != nullptr && value->value_case() == DimensionsValue::ValueCase::kValueInt) {
@@ -201,6 +211,10 @@
void StatsLogProcessor::onDumpReport(const ConfigKey& key, vector<uint8_t>* outData) {
std::lock_guard<std::mutex> lock(mMetricsMutex);
+ onDumpReportLocked(key, outData);
+}
+
+void StatsLogProcessor::onDumpReportLocked(const ConfigKey& key, vector<uint8_t>* outData) {
auto it = mMetricsManagers.find(key);
if (it == mMetricsManagers.end()) {
ALOGW("Config source %s does not exist", key.ToString().c_str());
@@ -309,7 +323,7 @@
for (auto& pair : mMetricsManagers) {
const ConfigKey& key = pair.first;
vector<uint8_t> data;
- onDumpReport(key, &data);
+ onDumpReportLocked(key, &data);
// TODO: Add a guardrail to prevent accumulation of file on disk.
string file_name = StringPrintf("%s/%d-%lld-%ld", STATS_DATA_DIR, key.GetUid(),
(long long)key.GetId(), time(nullptr));
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 301e3a5..fb85aa8 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -75,6 +75,8 @@
sp<AnomalyMonitor> mAnomalyMonitor;
+ void onDumpReportLocked(const ConfigKey& key, vector<uint8_t>* outData);
+
/* Check if we should send a broadcast if approaching memory limits and if we're over, we
* actually delete the data. */
void flushIfNecessaryLocked(uint64_t timestampNs, const ConfigKey& key,
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index ca097d0..ba628b8 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -725,7 +725,7 @@
if (checkCallingPermission(String16(kPermissionDump))) {
ConfigKey configKey(ipc->getCallingUid(), key);
StatsdConfig cfg;
- if (!cfg.ParseFromArray(&config[0], config.size())) {
+ if (config.empty() || !cfg.ParseFromArray(&config[0], config.size())) {
*success = false;
return Status::ok();
}
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 7f0ebb4..77b156f8 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -104,7 +104,7 @@
CpuTimePerUidFreq cpu_time_per_uid_freq = 10010;
WifiActivityEnergyInfo wifi_activity_energy_info = 10011;
ModemActivityInfo modem_activity_info = 10012;
- MemoryStat memory_stat = 10013;
+ ProcessMemoryStat process_memory_stat = 10013;
CpuSuspendTime cpu_suspend_time = 10014;
CpuIdleTime cpu_idle_time = 10015;
CpuActiveTime cpu_active_time = 10016;
@@ -1233,12 +1233,12 @@
/*
* Logs the memory stats for a process
*/
-message MemoryStat {
+message ProcessMemoryStat {
// The uid if available. -1 means not available.
optional int32 uid = 1;
- // The app package name.
- optional string pkg_name = 2;
+ // The process name.
+ optional string process_name = 2;
// # of page-faults
optional int64 pgfault = 3;
@@ -1259,19 +1259,20 @@
// The uid if available. -1 means not available.
optional int32 uid = 1;
- // The app package name.
- optional string pkg_name = 2;
+ // The process name.
+ optional string process_name = 2;
// oom adj score.
optional int32 oom_score = 3;
- // Used as start/stop boundaries for the event
- enum State {
- UNKNOWN = 0;
- START = 1;
- END = 2;
- }
- optional State state = 4;
+ // # of page-faults
+ optional int64 pgfault = 4;
+
+ // # of major page-faults
+ optional int64 pgmajfault = 5;
+
+ // RSS+CACHE(+SWAP)
+ optional int64 usage_in_bytes = 6;
}
/*
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index afa26f6..ea6586c 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -154,7 +154,7 @@
}
}
nonSlicedConditionCache[mIndex] = ConditionState::kUnknown;
- ALOGD("CombinationPredicate %lld sliced may changed? %d", (long long)mConditionId,
+ VLOG("CombinationPredicate %lld sliced may changed? %d", (long long)mConditionId,
conditionChangedCache[mIndex] == true);
}
}
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index fdfa32e..3d6984c 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -58,14 +58,14 @@
/**
* Get the timestamp associated with this event.
*/
- uint64_t GetTimestampNs() const { return mTimestampNs; }
+ inline uint64_t GetTimestampNs() const { return mTimestampNs; }
/**
* Get the tag for this event.
*/
- int GetTagId() const { return mTagId; }
+ inline int GetTagId() const { return mTagId; }
- uint32_t GetUid() const {
+ inline uint32_t GetUid() const {
return mLogUid;
}
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index ef27210..0455f6a 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -112,6 +112,9 @@
void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
ProtoOutputStream* protoOutput) {
flushIfNeededLocked(dumpTimeNs);
+ if (mPastBuckets.empty()) {
+ return;
+ }
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 58dd464..e26fe56 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -168,6 +168,9 @@
void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
ProtoOutputStream* protoOutput) {
flushIfNeededLocked(dumpTimeNs);
+ if (mPastBuckets.empty()) {
+ return;
+ }
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 821d8ea..25c86d0 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -102,6 +102,9 @@
void EventMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
ProtoOutputStream* protoOutput) {
+ if (mProto->size() <= 0) {
+ return;
+ }
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)dumpTimeNs);
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 17305e3..24dc5b0 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -125,6 +125,9 @@
VLOG("gauge metric %lld report now...", (long long)mMetricId);
flushIfNeededLocked(dumpTimeNs);
+ if (mPastBuckets.empty()) {
+ return;
+ }
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index e985873..ae0c673 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -138,6 +138,9 @@
ProtoOutputStream* protoOutput) {
VLOG("metric %lld dump report now...", (long long)mMetricId);
flushIfNeededLocked(dumpTimeNs);
+ if (mPastBuckets.empty()) {
+ return;
+ }
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, (long long)mStartTimeNs);
long long protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS);
diff --git a/cmds/uiautomator/instrumentation/Android.mk b/cmds/uiautomator/instrumentation/Android.mk
index ed99f3e..e887539 100644
--- a/cmds/uiautomator/instrumentation/Android.mk
+++ b/cmds/uiautomator/instrumentation/Android.mk
@@ -21,7 +21,7 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under, testrunner-src) \
$(call all-java-files-under, ../library/core-src)
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base.stubs
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_STATIC_JAVA_LIBRARIES := junit
LOCAL_MODULE := uiautomator-instrumentation
# TODO: change this to 18 when it's available
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 0a5b848..cd029c0 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,6 +17,7 @@
package android.app;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+
import static java.lang.Character.MIN_VALUE;
import android.annotation.CallSuper;
@@ -2618,6 +2619,7 @@
* @param id the ID to search for
* @return a view with given ID if found, or {@code null} otherwise
* @see View#findViewById(int)
+ * @see Activity#requireViewById(int)
*/
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
@@ -2625,6 +2627,30 @@
}
/**
+ * Finds a view that was identified by the {@code android:id} XML attribute that was processed
+ * in {@link #onCreate}, or throws an IllegalArgumentException if the ID is invalid, 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 a view with given ID
+ * @see View#requireViewById(int)
+ * @see Activity#findViewById(int)
+ */
+ @NonNull
+ public final <T extends View> T requireViewById(@IdRes int id) {
+ T view = findViewById(id);
+ if (view == null) {
+ throw new IllegalArgumentException("ID does not reference a View inside this Activity");
+ }
+ return view;
+ }
+
+ /**
* Retrieve a reference to this activity's ActionBar.
*
* @return The Activity's ActionBar, or null if it does not have one.
@@ -4671,6 +4697,7 @@
* their launch had come from the original activity.
* @param intent The Intent to start.
* @param options ActivityOptions or null.
+ * @param permissionToken Token received from the system that permits this call to be made.
* @param ignoreTargetSecurity If true, the activity manager will not check whether the
* caller it is doing the start is, is actually allowed to start the target activity.
* If you set this to true, you must set an explicit component in the Intent and do any
@@ -4679,7 +4706,7 @@
* @hide
*/
public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
- boolean ignoreTargetSecurity, int userId) {
+ IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
if (mParent != null) {
throw new RuntimeException("Can't be called from a child");
}
@@ -4687,7 +4714,7 @@
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivityAsCaller(
this, mMainThread.getApplicationThread(), mToken, this,
- intent, -1, options, ignoreTargetSecurity, userId);
+ intent, -1, options, permissionToken, ignoreTargetSecurity, userId);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, -1, ar.getResultCode(),
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 4554584..b5a9412 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -443,6 +443,31 @@
*/
public static final int INTENT_SENDER_FOREGROUND_SERVICE = 5;
+ /**
+ * Extra included on intents that are delegating the call to
+ * ActivityManager#startActivityAsCaller to another app. This token is necessary for that call
+ * to succeed. Type is IBinder.
+ * @hide
+ */
+ public static final String EXTRA_PERMISSION_TOKEN = "android.app.extra.PERMISSION_TOKEN";
+
+ /**
+ * Extra included on intents that contain an EXTRA_INTENT, with options that the contained
+ * intent may want to be started with. Type is Bundle.
+ * TODO: remove once the ChooserActivity moves to systemui
+ * @hide
+ */
+ public static final String EXTRA_OPTIONS = "android.app.extra.OPTIONS";
+
+ /**
+ * Extra included on intents that contain an EXTRA_INTENT, use this boolean value for the
+ * parameter of the same name when starting the contained intent.
+ * TODO: remove once the ChooserActivity moves to systemui
+ * @hide
+ */
+ public static final String EXTRA_IGNORE_TARGET_SECURITY =
+ "android.app.extra.EXTRA_IGNORE_TARGET_SECURITY";
+
/** @hide User operation call: success! */
public static final int USER_OP_SUCCESS = 0;
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 49d5224..cc68c05 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -699,6 +699,26 @@
}
@Override
+ public boolean hasSigningCertificate(
+ String packageName, byte[] certificate, @PackageManager.CertificateInputType int type) {
+ try {
+ return mPM.hasSigningCertificate(packageName, certificate, type);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public boolean hasSigningCertificate(
+ int uid, byte[] certificate, @PackageManager.CertificateInputType int type) {
+ try {
+ return mPM.hasUidSigningCertificate(uid, certificate, type);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
public String[] getPackagesForUid(int uid) {
try {
return mPM.getPackagesForUid(uid);
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index b162cb1..2b648ea 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -16,10 +16,6 @@
package android.app;
-import com.android.internal.R;
-import com.android.internal.app.WindowDecorActionBar;
-import com.android.internal.policy.PhoneWindow;
-
import android.annotation.CallSuper;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
@@ -32,8 +28,8 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.DialogInterface;
-import android.content.res.Configuration;
import android.content.pm.ApplicationInfo;
+import android.content.res.Configuration;
import android.content.res.ResourceId;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -62,6 +58,10 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
+import com.android.internal.R;
+import com.android.internal.app.WindowDecorActionBar;
+import com.android.internal.policy.PhoneWindow;
+
import java.lang.ref.WeakReference;
/**
@@ -512,6 +512,7 @@
* @param id the ID to search for
* @return a view with given ID if found, or {@code null} otherwise
* @see View#findViewById(int)
+ * @see Dialog#requireViewById(int)
*/
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
@@ -519,6 +520,30 @@
}
/**
+ * Finds the first descendant view with the given ID or throws an IllegalArgumentException 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 ID to search for
+ * @return a view with given ID
+ * @see View#requireViewById(int)
+ * @see Dialog#findViewById(int)
+ */
+ @NonNull
+ public final <T extends View> T requireViewById(@IdRes int id) {
+ T view = findViewById(id);
+ if (view == null) {
+ throw new IllegalArgumentException("ID does not reference a View inside this Dialog");
+ }
+ return view;
+ }
+
+ /**
* Set the screen content from a layout resource. The resource will be
* inflated, adding all top-level views to the screen.
*
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 04ee77d..5f5d834 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -438,10 +438,11 @@
boolean isTopOfTask(in IBinder token);
void notifyLaunchTaskBehindComplete(in IBinder token);
void notifyEnterAnimationComplete(in IBinder token);
+ IBinder requestStartActivityPermissionToken(in IBinder delegatorToken);
int startActivityAsCaller(in IApplicationThread caller, in String callingPackage,
in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho,
int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options,
- boolean ignoreTargetSecurity, int userId);
+ in IBinder permissionToken, boolean ignoreTargetSecurity, int userId);
int addAppTask(in IBinder activityToken, in Intent intent,
in ActivityManager.TaskDescription description, in Bitmap thumbnail);
Point getAppTaskThumbnailSize();
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index c5a58f2..3c38a4e 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1874,8 +1874,8 @@
*/
public ActivityResult execStartActivityAsCaller(
Context who, IBinder contextThread, IBinder token, Activity target,
- Intent intent, int requestCode, Bundle options, boolean ignoreTargetSecurity,
- int userId) {
+ Intent intent, int requestCode, Bundle options, IBinder permissionToken,
+ boolean ignoreTargetSecurity, int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
@@ -1906,7 +1906,8 @@
.startActivityAsCaller(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
- requestCode, 0, null, options, ignoreTargetSecurity, userId);
+ requestCode, 0, null, options, permissionToken,
+ ignoreTargetSecurity, userId);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 024dbcb..553099f 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -167,10 +167,11 @@
* clicking this button, the activity returns
* {@link #RESULT_ALTERNATE}
*
- * @return the intent for launching the activity or null if the credential of the previous
- * owner can not be verified (e.g. because there was none, or the device does not support
- * verifying credentials after a factory reset, or device setup has already been completed).
- *
+ * @return the intent for launching the activity or null if the previous owner of the device
+ * did not set a credential.
+ * @throws UnsupportedOperationException if the device does not support factory reset
+ * credentials
+ * @throws IllegalStateException if the device has already been provisioned
* @hide
*/
@SystemApi
@@ -178,14 +179,14 @@
CharSequence title, CharSequence description, CharSequence alternateButtonLabel) {
if (!LockPatternUtils.frpCredentialEnabled(mContext)) {
Log.w(TAG, "Factory reset credentials not supported.");
- return null;
+ throw new UnsupportedOperationException("not supported on this device");
}
// Cannot verify credential if the device is provisioned
if (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
Log.e(TAG, "Factory reset credential cannot be verified after provisioning.");
- return null;
+ throw new IllegalStateException("must not be provisioned yet");
}
// Make sure we have a credential
@@ -194,8 +195,10 @@
ServiceManager.getService(Context.PERSISTENT_DATA_BLOCK_SERVICE));
if (pdb == null) {
Log.e(TAG, "No persistent data block service");
- return null;
+ throw new UnsupportedOperationException("not supported on this device");
}
+ // The following will throw an UnsupportedOperationException if the device does not
+ // support factory reset credentials (or something went wrong retrieving it).
if (!pdb.hasFrpCredentialHandle()) {
Log.i(TAG, "The persistent data block does not have a factory reset credential.");
return null;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 2e3b8af..d6fddfc 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4130,7 +4130,7 @@
final Bundle ex = mN.extras;
updateBackgroundColor(contentView);
bindNotificationHeader(contentView, p.ambient, p.headerTextSecondary);
- bindLargeIcon(contentView, p.hideLargeIcon, p.alwaysShowReply);
+ bindLargeIcon(contentView, p.hideLargeIcon || p.ambient, p.alwaysShowReply);
boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
if (p.title != null) {
contentView.setViewVisibility(R.id.title, View.VISIBLE);
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index c06ad3f..30f2697 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -32,8 +32,6 @@
import com.android.internal.util.Preconditions;
-import com.android.internal.util.Preconditions;
-
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
@@ -936,7 +934,9 @@
}
/** @hide */
- public void toProto(ProtoOutputStream proto) {
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
proto.write(NotificationChannelProto.ID, mId);
proto.write(NotificationChannelProto.NAME, mName);
proto.write(NotificationChannelProto.DESCRIPTION, mDesc);
@@ -959,10 +959,10 @@
proto.write(NotificationChannelProto.IS_DELETED, mDeleted);
proto.write(NotificationChannelProto.GROUP, mGroup);
if (mAudioAttributes != null) {
- long aToken = proto.start(NotificationChannelProto.AUDIO_ATTRIBUTES);
- mAudioAttributes.toProto(proto);
- proto.end(aToken);
+ mAudioAttributes.writeToProto(proto, NotificationChannelProto.AUDIO_ATTRIBUTES);
}
proto.write(NotificationChannelProto.IS_BLOCKABLE_SYSTEM, mBlockableSystem);
+
+ proto.end(token);
}
}
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index 5cb7fb7..16166f7 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -298,13 +298,17 @@
}
/** @hide */
- public void toProto(ProtoOutputStream proto) {
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
proto.write(NotificationChannelGroupProto.ID, mId);
proto.write(NotificationChannelGroupProto.NAME, mName.toString());
proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription);
proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked);
for (NotificationChannel channel : mChannels) {
- channel.toProto(proto);
+ channel.writeToProto(proto, NotificationChannelGroupProto.CHANNELS);
}
+
+ proto.end(token);
}
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 659cf16..49c03ab 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -93,6 +93,18 @@
private static boolean localLOGV = false;
/**
+ * Intent that is broadcast when an application is blocked or unblocked.
+ *
+ * This broadcast is only sent to the app whose block state has changed.
+ *
+ * Input: nothing
+ * Output: nothing
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_APP_BLOCK_STATE_CHANGED =
+ "android.app.action.APP_BLOCK_STATE_CHANGED";
+
+ /**
* Intent that is broadcast when a {@link NotificationChannel} is blocked
* (when {@link NotificationChannel#getImportance()} is {@link #IMPORTANCE_NONE}) or unblocked
* (when {@link NotificationChannel#getImportance()} is anything other than
@@ -1133,7 +1145,7 @@
}
/** @hide */
- public void toProto(ProtoOutputStream proto, long fieldId) {
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long pToken = proto.start(fieldId);
bitwiseToProtoEnum(proto, PolicyProto.PRIORITY_CATEGORIES, priorityCategories);
diff --git a/core/java/android/app/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java
index a295c4c..0ed1b08 100644
--- a/core/java/android/app/ProfilerInfo.java
+++ b/core/java/android/app/ProfilerInfo.java
@@ -20,6 +20,7 @@
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import java.io.IOException;
import java.util.Objects;
@@ -124,6 +125,20 @@
out.writeBoolean(attachAgentDuringBind);
}
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(ProfilerInfoProto.PROFILE_FILE, profileFile);
+ if (profileFd != null) {
+ proto.write(ProfilerInfoProto.PROFILE_FD, profileFd.getFd());
+ }
+ proto.write(ProfilerInfoProto.SAMPLING_INTERVAL, samplingInterval);
+ proto.write(ProfilerInfoProto.AUTO_STOP_PROFILER, autoStopProfiler);
+ proto.write(ProfilerInfoProto.STREAMING_OUTPUT, streamingOutput);
+ proto.write(ProfilerInfoProto.AGENT, agent);
+ proto.end(token);
+ }
+
public static final Parcelable.Creator<ProfilerInfo> CREATOR =
new Parcelable.Creator<ProfilerInfo>() {
@Override
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 256c479..ea0fd75 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -471,14 +471,6 @@
* {@link #onStart} and returns either {@link #START_STICKY}
* or {@link #START_STICKY_COMPATIBILITY}.
*
- * <p>If you need your application to run on platform versions prior to API
- * level 5, you can use the following model to handle the older {@link #onStart}
- * callback in that case. The <code>handleCommand</code> method is implemented by
- * you as appropriate:
- *
- * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
- * start_compatibility}
- *
* <p class="caution">Note that the system calls this on your
* service's main thread. A service's main thread is the same
* thread where UI operations take place for Activities running in the
@@ -687,6 +679,10 @@
* {@link #startService(Intent)} first to tell the system it should keep the service running,
* and then use this method to tell it to keep it running harder.</p>
*
+ * <p>Apps targeting API {@link android.os.Build.VERSION_CODES#P} or later must request
+ * the permission {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use
+ * this API.</p>
+ *
* @param id The identifier for this notification as per
* {@link NotificationManager#notify(int, Notification)
* NotificationManager.notify(int, Notification)}; must not be 0.
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index ffb3aff..28e845a 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -483,19 +483,28 @@
"android.app.action.TRANSFER_OWNERSHIP_COMPLETE";
/**
+ * Broadcast action: notify the device owner that the ownership of one of its affiliated
+ * profiles is transferred.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE =
+ "android.app.action.AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE";
+
+ /**
* A {@link android.os.Parcelable} extra of type {@link android.os.PersistableBundle} that
* allows a mobile device management application to pass data to the management application
* instance after owner transfer.
*
- * <p>
- * If the transfer is successful, the new device owner receives the data in
+ * <p>If the transfer is successful, the new owner receives the data in
* {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)}.
* The bundle is not changed during the ownership transfer.
*
* @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle)
*/
- public static final String EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE =
- "android.app.extra.TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE";
+ public static final String EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE =
+ "android.app.extra.TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE";
/**
* Name under which a device administration component indicates whether it supports transfer of
@@ -994,6 +1003,26 @@
}
/**
+ * Called on the device owner when the ownership of one of its affiliated profiles is
+ * transferred.
+ *
+ * <p>This can be used when transferring both device and profile ownership when using
+ * work profile on a fully managed device. The process would look like this:
+ * <ol>
+ * <li>Transfer profile ownership</li>
+ * <li>The device owner gets notified with this callback</li>
+ * <li>Transfer device ownership</li>
+ * <li>Both profile and device ownerships have been transferred</li>
+ * </ol>
+ *
+ * @param context the running context as per {@link #onReceive}
+ * @param user the {@link UserHandle} of the affiliated user
+ * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle)
+ */
+ public void onTransferAffiliatedProfileOwnershipComplete(Context context, UserHandle user) {
+ }
+
+ /**
* Intercept standard device administrator broadcasts. Implementations
* should not override this method; it is better to implement the
* convenience callbacks for each action.
@@ -1063,8 +1092,11 @@
onUserSwitched(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
} else if (ACTION_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) {
PersistableBundle bundle =
- intent.getParcelableExtra(EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE);
+ intent.getParcelableExtra(EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE);
onTransferOwnershipComplete(context, bundle);
+ } else if (ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) {
+ onTransferAffiliatedProfileOwnershipComplete(context,
+ intent.getParcelableExtra(Intent.EXTRA_USER));
}
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7fccda8..8f76032 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -68,6 +68,7 @@
import android.security.keystore.ParcelableKeyGenParameterSpec;
import android.service.restrictions.RestrictionsReceiver;
import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
import android.util.ArraySet;
import android.util.Log;
@@ -7036,14 +7037,14 @@
* task. From {@link android.os.Build.VERSION_CODES#M} removing packages from the lock task
* package list results in locked tasks belonging to those packages to be finished.
* <p>
- * This function can only be called by the device owner or by a profile owner of a user/profile
- * that is affiliated with the device. See {@link #isAffiliatedUser}. Any packages
- * set via this method will be cleared if the user becomes unaffiliated.
+ * This function can only be called by the device owner, a profile owner of an affiliated user
+ * or profile, or the profile owner when no device owner is set. See {@link #isAffiliatedUser}.
+ * Any package set via this method will be cleared if the user becomes unaffiliated.
*
* @param packages The list of packages allowed to enter lock task mode
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
- * an affiliated user or profile.
+ * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+ * affiliated user or profile, or the profile owner when no device owner is set.
* @see #isAffiliatedUser
* @see Activity#startLockTask()
* @see DeviceAdminReceiver#onLockTaskModeEntering(Context, Intent, String)
@@ -7065,8 +7066,8 @@
/**
* Returns the list of packages allowed to start the lock task mode.
*
- * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
- * an affiliated user or profile.
+ * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+ * affiliated user or profile, or the profile owner when no device owner is set.
* @see #isAffiliatedUser
* @see #setLockTaskPackages
*/
@@ -7106,9 +7107,9 @@
* is in LockTask mode. If this method is not called, none of the features listed here will be
* enabled.
* <p>
- * This function can only be called by the device owner or by a profile owner of a user/profile
- * that is affiliated with the device. See {@link #isAffiliatedUser}. Any features
- * set via this method will be cleared if the user becomes unaffiliated.
+ * This function can only be called by the device owner, a profile owner of an affiliated user
+ * or profile, or the profile owner when no device owner is set. See {@link #isAffiliatedUser}.
+ * Any features set via this method will be cleared if the user becomes unaffiliated.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param flags Bitfield of feature flags:
@@ -7119,9 +7120,10 @@
* {@link #LOCK_TASK_FEATURE_RECENTS},
* {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS},
* {@link #LOCK_TASK_FEATURE_KEYGUARD}
- * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
- * an affiliated user or profile.
+ * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+ * affiliated user or profile, or the profile owner when no device owner is set.
* @see #isAffiliatedUser
+ * @throws SecurityException if {@code admin} is not the device owner or the profile owner.
*/
public void setLockTaskFeatures(@NonNull ComponentName admin, @LockTaskFeature int flags) {
throwIfParentInstance("setLockTaskFeatures");
@@ -7139,8 +7141,8 @@
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @return bitfield of flags. See {@link #setLockTaskFeatures(ComponentName, int)} for a list.
- * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
- * an affiliated user or profile.
+ * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+ * affiliated user or profile, or the profile owner when no device owner is set.
* @see #isAffiliatedUser
* @see #setLockTaskFeatures
*/
@@ -8228,6 +8230,47 @@
}
/**
+ * Called by a device or profile owner to restrict packages from accessing metered data.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with.
+ * @param packageNames the list of package names to be restricted.
+ * @return a list of package names which could not be restricted.
+ * @throws SecurityException if {@code admin} is not a device or profile owner.
+ */
+ public @NonNull List<String> setMeteredDataDisabled(@NonNull ComponentName admin,
+ @NonNull List<String> packageNames) {
+ throwIfParentInstance("setMeteredDataDisabled");
+ if (mService != null) {
+ try {
+ return mService.setMeteredDataDisabled(admin, packageNames);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return packageNames;
+ }
+
+ /**
+ * Called by a device or profile owner to retrieve the list of packages which are restricted
+ * by the admin from accessing metered data.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with.
+ * @return the list of restricted package names.
+ * @throws SecurityException if {@code admin} is not a device or profile owner.
+ */
+ public @NonNull List<String> getMeteredDataDisabled(@NonNull ComponentName admin) {
+ throwIfParentInstance("getMeteredDataDisabled");
+ if (mService != null) {
+ try {
+ return mService.getMeteredDataDisabled(admin);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return new ArrayList<>();
+ }
+
+ /**
* Called by device owners to retrieve device logs from before the device's last reboot.
* <p>
* <strong> This API is not supported on all devices. Calling this API on unsupported devices
@@ -9101,9 +9144,13 @@
* <li>A profile owner can only be transferred to a new profile owner</li>
* </ul>
*
- * <p>Use the {@code bundle} parameter to pass data to the new administrator. The parameters
+ * <p>Use the {@code bundle} parameter to pass data to the new administrator. The data
* will be received in the
- * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)} callback.
+ * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)}
+ * callback of the new administrator.
+ *
+ * <p>The transfer has failed if the original administrator is still the corresponding owner
+ * after calling this method.
*
* <p>The incoming target administrator must have the
* {@link DeviceAdminReceiver#SUPPORT_TRANSFER_OWNERSHIP_META_DATA} <code>meta-data</code> tag
@@ -9114,11 +9161,11 @@
* @param target which {@link DeviceAdminReceiver} we want the new administrator to be
* @param bundle data to be sent to the new administrator
* @throws SecurityException if {@code admin} is not a device owner nor a profile owner
- * @throws IllegalArgumentException if {@code admin} or {@code target} is {@code null},
- * both are components in the same package or {@code target} is not an active admin
+ * @throws IllegalArgumentException if {@code admin} or {@code target} is {@code null}, they
+ * are components in the same package or {@code target} is not an active admin
*/
public void transferOwnership(@NonNull ComponentName admin, @NonNull ComponentName target,
- PersistableBundle bundle) {
+ @Nullable PersistableBundle bundle) {
throwIfParentInstance("transferOwnership");
try {
mService.transferOwnership(admin, target, bundle);
@@ -9243,16 +9290,154 @@
}
/**
- * Returns error message to be displayed when printing is disabled.
+ * Called by device owner to add an override APN.
*
- * Used only by PrintService.
- * @return Localized error message.
- * @hide
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with
+ * @param apnSetting the override APN to insert
+ * @return The {@code id} of inserted override APN. Or {@code -1} when failed to insert into
+ * the database.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ *
+ * @see #setOverrideApnsEnabled(ComponentName, boolean)
*/
- @SystemApi
- public CharSequence getPrintingDisabledReason() {
+ public int addOverrideApn(@NonNull ComponentName admin, @NonNull ApnSetting apnSetting) {
+ throwIfParentInstance("addOverrideApn");
+ if (mService != null) {
+ try {
+ return mService.addOverrideApn(admin, apnSetting);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Called by device owner to update an override APN.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with
+ * @param apnId the {@code id} of the override APN to update
+ * @param apnSetting the override APN to update
+ * @return {@code true} if the required override APN is successfully updated,
+ * {@code false} otherwise.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ *
+ * @see #setOverrideApnsEnabled(ComponentName, boolean)
+ */
+ public boolean updateOverrideApn(@NonNull ComponentName admin, int apnId,
+ @NonNull ApnSetting apnSetting) {
+ throwIfParentInstance("updateOverrideApn");
+ if (mService != null) {
+ try {
+ return mService.updateOverrideApn(admin, apnId, apnSetting);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called by device owner to remove an override APN.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with
+ * @param apnId the {@code id} of the override APN to remove
+ * @return {@code true} if the required override APN is successfully removed, {@code false}
+ * otherwise.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ *
+ * @see #setOverrideApnsEnabled(ComponentName, boolean)
+ */
+ public boolean removeOverrideApn(@NonNull ComponentName admin, int apnId) {
+ throwIfParentInstance("removeOverrideApn");
+ if (mService != null) {
+ try {
+ return mService.removeOverrideApn(admin, apnId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called by device owner to get all override APNs inserted by device owner.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with
+ * @return A list of override APNs inserted by device owner.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ *
+ * @see #setOverrideApnsEnabled(ComponentName, boolean)
+ */
+ public List<ApnSetting> getOverrideApns(@NonNull ComponentName admin) {
+ throwIfParentInstance("getOverrideApns");
+ if (mService != null) {
+ try {
+ return mService.getOverrideApns(admin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Called by device owner to set if override APNs should be enabled.
+ * <p> Override APNs are separated from other APNs on the device, and can only be inserted or
+ * modified by the device owner. When enabled, only override APNs are in use, any other APNs
+ * are ignored.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with
+ * @param enabled {@code true} if override APNs should be enabled, {@code false} otherwise
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public void setOverrideApnsEnabled(@NonNull ComponentName admin, boolean enabled) {
+ throwIfParentInstance("setOverrideApnEnabled");
+ if (mService != null) {
+ try {
+ mService.setOverrideApnsEnabled(admin, enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Called by device owner to check if override APNs are currently enabled.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with
+ * @return {@code true} if override APNs are currently enabled, {@code false} otherwise.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ *
+ * @see #setOverrideApnsEnabled(ComponentName, boolean)
+ */
+ public boolean isOverrideApnEnabled(@NonNull ComponentName admin) {
+ throwIfParentInstance("isOverrideApnEnabled");
+ if (mService != null) {
+ try {
+ return mService.isOverrideApnEnabled(admin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the data passed from the current administrator to the new administrator during an
+ * ownership transfer. This is the same {@code bundle} passed in
+ * {@link #transferOwnership(ComponentName, ComponentName, PersistableBundle)}.
+ *
+ * <p>Returns <code>null</code> if no ownership transfer was started for the calling user.
+ *
+ * @see #transferOwnership
+ * @see DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)
+ */
+ @Nullable
+ public PersistableBundle getTransferOwnershipBundle() {
+ throwIfParentInstance("getTransferOwnershipBundle");
try {
- return mService.getPrintingDisabledReason();
+ return mService.getTransferOwnershipBundle();
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 531bef0..ebaf464 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -132,4 +132,13 @@
* @param userId The user in question
*/
public abstract boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId);
+
+ /**
+ * Return text of error message if printing is disabled.
+ * Called by Print Service when printing is disabled by PO or DO when printing is attempted.
+ *
+ * @param userId The user in question
+ * @return localized error message
+ */
+ public abstract CharSequence getPrintingDisabledReasonForUser(@UserIdInt int userId);
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index d2a2be7..daee6b4 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -38,6 +38,7 @@
import android.os.UserHandle;
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keystore.ParcelableKeyGenParameterSpec;
+import android.telephony.data.ApnSetting;
import java.util.List;
@@ -390,7 +391,9 @@
boolean isLogoutEnabled();
List<String> getDisallowedSystemApps(in ComponentName admin, int userId, String provisioningAction);
+
void transferOwnership(in ComponentName admin, in ComponentName target, in PersistableBundle bundle);
+ PersistableBundle getTransferOwnershipBundle();
void setStartUserSessionMessage(in ComponentName admin, in CharSequence startUserSessionMessage);
void setEndUserSessionMessage(in ComponentName admin, in CharSequence endUserSessionMessage);
@@ -399,5 +402,14 @@
void setPrintingEnabled(in ComponentName admin, boolean enabled);
boolean isPrintingEnabled();
- CharSequence getPrintingDisabledReason();
+
+ List<String> setMeteredDataDisabled(in ComponentName admin, in List<String> packageNames);
+ List<String> getMeteredDataDisabled(in ComponentName admin);
+
+ int addOverrideApn(in ComponentName admin, in ApnSetting apnSetting);
+ boolean updateOverrideApn(in ComponentName admin, int apnId, in ApnSetting apnSetting);
+ boolean removeOverrideApn(in ComponentName admin, int apnId);
+ List<ApnSetting> getOverrideApns(in ComponentName admin);
+ void setOverrideApnsEnabled(in ComponentName admin, boolean enabled);
+ boolean isOverrideApnEnabled(in ComponentName admin);
}
diff --git a/core/java/android/app/backup/BackupManagerMonitor.java b/core/java/android/app/backup/BackupManagerMonitor.java
index ae4a98a..a91aded 100644
--- a/core/java/android/app/backup/BackupManagerMonitor.java
+++ b/core/java/android/app/backup/BackupManagerMonitor.java
@@ -172,6 +172,12 @@
public static final int LOG_EVENT_ID_NO_PACKAGES = 49;
public static final int LOG_EVENT_ID_TRANSPORT_IS_NULL = 50;
+ /**
+ * The transport returned {@link BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED}.
+ * @hide
+ */
+ public static final int LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = 51;
+
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 3558e34..266f58d 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -51,6 +51,20 @@
public static final int AGENT_UNKNOWN = -1004;
public static final int TRANSPORT_QUOTA_EXCEEDED = -1005;
+ /**
+ * Indicates that the transport cannot accept a diff backup for this package.
+ *
+ * <p>Backup manager should clear its state for this package and immediately retry a
+ * non-incremental backup. This might be used if the transport no longer has data for this
+ * package in its backing store.
+ *
+ * <p>This is only valid when backup manager called {@link
+ * #performBackup(PackageInfo, ParcelFileDescriptor, int)} with {@link #FLAG_INCREMENTAL}.
+ *
+ * @hide
+ */
+ public static final int TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = -1006;
+
// Indicates that operation was initiated by user, not a scheduled one.
// Transport should ignore its own moratoriums for call with this flag set.
public static final int FLAG_USER_INITIATED = 1;
@@ -252,6 +266,13 @@
* set then {@link BackupTransport#FLAG_NON_INCREMENTAL} will be set. Before P neither flag will
* be set regardless of whether the backup is incremental or not.
*
+ * <p>If {@link BackupTransport#FLAG_INCREMENTAL} is set and the transport does not have data
+ * for this package in its storage backend then it cannot apply the incremental diff. Thus it
+ * should return {@link BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED} to indicate
+ * that backup manager should delete its state and retry the package as a non-incremental
+ * backup. Before P, or if this is a non-incremental backup, then this return code is equivalent
+ * to {@link BackupTransport#TRANSPORT_ERROR}.
+ *
* @param packageInfo The identity of the application whose data is being backed up.
* This specifically includes the signature list for the package.
* @param inFd Descriptor of file with data that resulted from invoking the application's
@@ -262,9 +283,11 @@
* @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far),
* {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this
* specific package, but allow others to proceed),
- * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), or
- * {@link BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
- * become lost due to inactivity purge or some other reason and needs re-initializing)
+ * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), {@link
+ * BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED} (if the transport cannot accept
+ * an incremental backup for this package), or {@link
+ * BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has become lost due to
+ * inactivity purge or some other reason and needs re-initializing)
*/
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) {
return performBackup(packageInfo, inFd);
diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java
index 5bd3440..5808f8b 100644
--- a/core/java/android/app/slice/Slice.java
+++ b/core/java/android/app/slice/Slice.java
@@ -156,10 +156,11 @@
*/
public static final String HINT_SEE_MORE = "see_more";
/**
- * A hint to tell the system that this slice cares about the return value of
- * {@link SliceProvider#getBindingPackage} and should not cache the result
- * for multiple apps.
- * @hide
+ * A hint used when implementing app-specific slice permissions.
+ * Tells the system that for this slice the return value of
+ * {@link SliceProvider#onBindSlice(Uri, List)} may be different depending on
+ * {@link SliceProvider#getBindingPackage} and should not be cached for multiple
+ * apps.
*/
public static final String HINT_CALLER_NEEDED = "caller_needed";
/**
@@ -429,28 +430,6 @@
* Add a color to the slice being constructed
* @param subType Optional template-specific type information
* @see {@link SliceItem#getSubType()}
- * @deprecated will be removed once supportlib updates
- */
- public Builder addColor(int color, @Nullable String subType, @SliceHint String... hints) {
- mItems.add(new SliceItem(color, SliceItem.FORMAT_INT, subType, hints));
- return this;
- }
-
- /**
- * Add a color to the slice being constructed
- * @param subType Optional template-specific type information
- * @see {@link SliceItem#getSubType()}
- * @deprecated will be removed once supportlib updates
- */
- public Builder addColor(int color, @Nullable String subType,
- @SliceHint List<String> hints) {
- return addColor(color, subType, hints.toArray(new String[hints.size()]));
- }
-
- /**
- * Add a color to the slice being constructed
- * @param subType Optional template-specific type information
- * @see {@link SliceItem#getSubType()}
*/
public Builder addInt(int value, @Nullable String subType, @SliceHint String... hints) {
mItems.add(new SliceItem(value, SliceItem.FORMAT_INT, subType, hints));
diff --git a/core/java/android/app/slice/SliceItem.java b/core/java/android/app/slice/SliceItem.java
index bcfd413..9eb2bb8 100644
--- a/core/java/android/app/slice/SliceItem.java
+++ b/core/java/android/app/slice/SliceItem.java
@@ -98,11 +98,6 @@
*/
public static final String FORMAT_INT = "int";
/**
- * A {@link SliceItem} that contains an int.
- * @deprecated to be removed
- */
- public static final String FORMAT_COLOR = "color";
- /**
* A {@link SliceItem} that contains a timestamp.
*/
public static final String FORMAT_TIMESTAMP = "timestamp";
@@ -231,13 +226,6 @@
}
/**
- * @deprecated to be removed.
- */
- public int getColor() {
- return (Integer) mObj;
- }
-
- /**
* @return The slice held by this {@link #FORMAT_ACTION} or {@link #FORMAT_SLICE} SliceItem
*/
public Slice getSlice() {
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index bd4103f..00e8cca 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -158,7 +158,6 @@
* currently happening. The returned package will have been
* verified to belong to the calling UID. Returns {@code null} if not
* currently performing an {@link #onBindSlice(Uri, List)}.
- * @hide
*/
public final @Nullable String getBindingPackage() {
return mBindingPkg;
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index f04e907..edb992b 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -106,6 +106,12 @@
*/
public static final int NOTIFICATION_SEEN = 10;
+ /**
+ * An event type denoting a change in App Standby Bucket.
+ * @hide
+ */
+ public static final int STANDBY_BUCKET_CHANGED = 11;
+
/** @hide */
public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0;
@@ -170,6 +176,13 @@
*/
public String[] mContentAnnotations;
+ /**
+ * The app standby bucket assigned.
+ * Only present for {@link #STANDBY_BUCKET_CHANGED} event types
+ * {@hide}
+ */
+ public int mBucket;
+
/** @hide */
@EventFlags
public int mFlags;
@@ -189,6 +202,7 @@
mContentType = orig.mContentType;
mContentAnnotations = orig.mContentAnnotations;
mFlags = orig.mFlags;
+ mBucket = orig.mBucket;
}
/**
@@ -399,6 +413,9 @@
p.writeString(event.mContentType);
p.writeStringArray(event.mContentAnnotations);
break;
+ case Event.STANDBY_BUCKET_CHANGED:
+ p.writeInt(event.mBucket);
+ break;
}
}
@@ -442,6 +459,9 @@
eventOut.mContentType = p.readString();
eventOut.mContentAnnotations = p.createStringArray();
break;
+ case Event.STANDBY_BUCKET_CHANGED:
+ eventOut.mBucket = p.readInt();
+ break;
}
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 84cbdb4..746a090 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -34,6 +34,7 @@
import android.text.TextUtils;
import android.util.Printer;
import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.util.ArrayUtils;
@@ -1183,6 +1184,105 @@
super.dumpBack(pw, prefix);
}
+ /** {@hide} */
+ public void writeToProto(ProtoOutputStream proto, long fieldId, int dumpFlags) {
+ long token = proto.start(fieldId);
+ super.writeToProto(proto, ApplicationInfoProto.PACKAGE);
+ proto.write(ApplicationInfoProto.PERMISSION, permission);
+ proto.write(ApplicationInfoProto.PROCESS_NAME, processName);
+ proto.write(ApplicationInfoProto.UID, uid);
+ proto.write(ApplicationInfoProto.FLAGS, flags);
+ proto.write(ApplicationInfoProto.PRIVATE_FLAGS, privateFlags);
+ proto.write(ApplicationInfoProto.THEME, theme);
+ proto.write(ApplicationInfoProto.SOURCE_DIR, sourceDir);
+ if (!Objects.equals(sourceDir, publicSourceDir)) {
+ proto.write(ApplicationInfoProto.PUBLIC_SOURCE_DIR, publicSourceDir);
+ }
+ if (!ArrayUtils.isEmpty(splitSourceDirs)) {
+ for (String dir : splitSourceDirs) {
+ proto.write(ApplicationInfoProto.SPLIT_SOURCE_DIRS, dir);
+ }
+ }
+ if (!ArrayUtils.isEmpty(splitPublicSourceDirs)
+ && !Arrays.equals(splitSourceDirs, splitPublicSourceDirs)) {
+ for (String dir : splitPublicSourceDirs) {
+ proto.write(ApplicationInfoProto.SPLIT_PUBLIC_SOURCE_DIRS, dir);
+ }
+ }
+ if (resourceDirs != null) {
+ for (String dir : resourceDirs) {
+ proto.write(ApplicationInfoProto.RESOURCE_DIRS, dir);
+ }
+ }
+ proto.write(ApplicationInfoProto.DATA_DIR, dataDir);
+ proto.write(ApplicationInfoProto.CLASS_LOADER_NAME, classLoaderName);
+ if (!ArrayUtils.isEmpty(splitClassLoaderNames)) {
+ for (String name : splitClassLoaderNames) {
+ proto.write(ApplicationInfoProto.SPLIT_CLASS_LOADER_NAMES, name);
+ }
+ }
+
+ long versionToken = proto.start(ApplicationInfoProto.VERSION);
+ proto.write(ApplicationInfoProto.Version.ENABLED, enabled);
+ proto.write(ApplicationInfoProto.Version.MIN_SDK_VERSION, minSdkVersion);
+ proto.write(ApplicationInfoProto.Version.TARGET_SDK_VERSION, targetSdkVersion);
+ proto.write(ApplicationInfoProto.Version.VERSION_CODE, versionCode);
+ proto.write(ApplicationInfoProto.Version.TARGET_SANDBOX_VERSION, targetSandboxVersion);
+ proto.end(versionToken);
+
+ if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) {
+ long detailToken = proto.start(ApplicationInfoProto.DETAIL);
+ if (className != null) {
+ proto.write(ApplicationInfoProto.Detail.CLASS_NAME, className);
+ }
+ proto.write(ApplicationInfoProto.Detail.TASK_AFFINITY, taskAffinity);
+ proto.write(ApplicationInfoProto.Detail.REQUIRES_SMALLEST_WIDTH_DP,
+ requiresSmallestWidthDp);
+ proto.write(ApplicationInfoProto.Detail.COMPATIBLE_WIDTH_LIMIT_DP,
+ compatibleWidthLimitDp);
+ proto.write(ApplicationInfoProto.Detail.LARGEST_WIDTH_LIMIT_DP,
+ largestWidthLimitDp);
+ if (seInfo != null) {
+ proto.write(ApplicationInfoProto.Detail.SEINFO, seInfo);
+ proto.write(ApplicationInfoProto.Detail.SEINFO_USER, seInfoUser);
+ }
+ proto.write(ApplicationInfoProto.Detail.DEVICE_PROTECTED_DATA_DIR,
+ deviceProtectedDataDir);
+ proto.write(ApplicationInfoProto.Detail.CREDENTIAL_PROTECTED_DATA_DIR,
+ credentialProtectedDataDir);
+ if (sharedLibraryFiles != null) {
+ for (String f : sharedLibraryFiles) {
+ proto.write(ApplicationInfoProto.Detail.SHARED_LIBRARY_FILES, f);
+ }
+ }
+ if (manageSpaceActivityName != null) {
+ proto.write(ApplicationInfoProto.Detail.MANAGE_SPACE_ACTIVITY_NAME,
+ manageSpaceActivityName);
+ }
+ if (descriptionRes != 0) {
+ proto.write(ApplicationInfoProto.Detail.DESCRIPTION_RES, descriptionRes);
+ }
+ if (uiOptions != 0) {
+ proto.write(ApplicationInfoProto.Detail.UI_OPTIONS, uiOptions);
+ }
+ proto.write(ApplicationInfoProto.Detail.SUPPORTS_RTL, hasRtlSupport());
+ if (fullBackupContent > 0) {
+ proto.write(ApplicationInfoProto.Detail.CONTENT, "@xml/" + fullBackupContent);
+ } else {
+ proto.write(ApplicationInfoProto.Detail.IS_FULL_BACKUP, fullBackupContent == 0);
+ }
+ if (networkSecurityConfigRes != 0) {
+ proto.write(ApplicationInfoProto.Detail.NETWORK_SECURITY_CONFIG_RES,
+ networkSecurityConfigRes);
+ }
+ if (category != CATEGORY_UNDEFINED) {
+ proto.write(ApplicationInfoProto.Detail.CATEGORY, category);
+ }
+ proto.end(detailToken);
+ }
+ proto.end(token);
+ }
+
/**
* @return true if "supportsRtl" has been set to true in the AndroidManifest
* @hide
@@ -1491,6 +1591,13 @@
/**
* @hide
*/
+ public boolean isAllowedToUseHiddenApi() {
+ return isSystemApp();
+ }
+
+ /**
+ * @hide
+ */
@Override
public Drawable loadDefaultIcon(PackageManager pm) {
if ((flags & FLAG_EXTERNAL_STORAGE) != 0
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index cce6b84..379bff4 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -656,4 +656,8 @@
void setHarmfulAppWarning(String packageName, CharSequence warning, int userId);
CharSequence getHarmfulAppWarning(String packageName, int userId);
+
+ boolean hasSigningCertificate(String packageName, in byte[] signingCertificate, int flags);
+
+ boolean hasUidSigningCertificate(int uid, in byte[] signingCertificate, int flags);
}
diff --git a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
new file mode 100644
index 0000000..81041e9
--- /dev/null
+++ b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm;
+
+import android.content.pm.PackageParser.Package;
+import android.os.Build;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+
+/**
+ * Updates a package to ensure that if it targets < P that the org.apache.http.legacy library is
+ * included by default.
+ *
+ * <p>This is separated out so that it can be conditionally included at build time depending on
+ * whether org.apache.http.legacy is on the bootclasspath or not. In order to include this at
+ * build time, and remove org.apache.http.legacy from the bootclasspath pass
+ * REMOVE_OAHL_FROM_BCP=true on the build command line, otherwise this class will not be included
+ * and the
+ *
+ * @hide
+ */
+@VisibleForTesting
+public class OrgApacheHttpLegacyUpdater extends PackageSharedLibraryUpdater {
+
+ private static final String APACHE_HTTP_LEGACY = "org.apache.http.legacy";
+
+ @Override
+ public void updatePackage(Package pkg) {
+ ArrayList<String> usesLibraries = pkg.usesLibraries;
+ ArrayList<String> usesOptionalLibraries = pkg.usesOptionalLibraries;
+
+ // Packages targeted at <= O_MR1 expect the classes in the org.apache.http.legacy library
+ // to be accessible so this maintains backward compatibility by adding the
+ // org.apache.http.legacy library to those packages.
+ if (apkTargetsApiLevelLessThanOrEqualToOMR1(pkg)) {
+ boolean apacheHttpLegacyPresent = isLibraryPresent(
+ usesLibraries, usesOptionalLibraries, APACHE_HTTP_LEGACY);
+ if (!apacheHttpLegacyPresent) {
+ usesLibraries = prefix(usesLibraries, APACHE_HTTP_LEGACY);
+ }
+ }
+
+ pkg.usesLibraries = usesLibraries;
+ pkg.usesOptionalLibraries = usesOptionalLibraries;
+ }
+
+ private static boolean apkTargetsApiLevelLessThanOrEqualToOMR1(Package pkg) {
+ int targetSdkVersion = pkg.applicationInfo.targetSdkVersion;
+ return targetSdkVersion <= Build.VERSION_CODES.O_MR1;
+ }
+}
diff --git a/core/java/android/content/pm/PackageBackwardCompatibility.java b/core/java/android/content/pm/PackageBackwardCompatibility.java
index 8014c94..9bdb78b 100644
--- a/core/java/android/content/pm/PackageBackwardCompatibility.java
+++ b/core/java/android/content/pm/PackageBackwardCompatibility.java
@@ -16,15 +16,14 @@
package android.content.pm;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.pm.PackageParser.Package;
-import android.os.Build;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
+import java.util.List;
/**
* Modifies {@link Package} in order to maintain backwards compatibility.
@@ -32,13 +31,60 @@
* @hide
*/
@VisibleForTesting
-public class PackageBackwardCompatibility {
+public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater {
+
+ private static final String TAG = PackageBackwardCompatibility.class.getSimpleName();
private static final String ANDROID_TEST_MOCK = "android.test.mock";
private static final String ANDROID_TEST_RUNNER = "android.test.runner";
- private static final String APACHE_HTTP_LEGACY = "org.apache.http.legacy";
+ private static final PackageBackwardCompatibility INSTANCE;
+
+ static {
+ String className = "android.content.pm.OrgApacheHttpLegacyUpdater";
+ Class<? extends PackageSharedLibraryUpdater> clazz;
+ try {
+ clazz = (PackageBackwardCompatibility.class.getClassLoader()
+ .loadClass(className)
+ .asSubclass(PackageSharedLibraryUpdater.class));
+ } catch (ClassNotFoundException e) {
+ Log.i(TAG, "Could not find " + className + ", ignoring");
+ clazz = null;
+ }
+
+ boolean hasOrgApacheHttpLegacy = false;
+ final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>();
+ if (clazz == null) {
+ // Add an updater that will remove any references to org.apache.http.library from the
+ // package so that it does not try and load the library when it is on the
+ // bootclasspath.
+ packageUpdaters.add(new RemoveUnnecessaryOrgApacheHttpLegacyLibrary());
+ } else {
+ try {
+ packageUpdaters.add(clazz.getConstructor().newInstance());
+ hasOrgApacheHttpLegacy = true;
+ } catch (ReflectiveOperationException e) {
+ throw new IllegalStateException("Could not create instance of " + className, e);
+ }
+ }
+
+ packageUpdaters.add(new AndroidTestRunnerSplitUpdater());
+
+ PackageSharedLibraryUpdater[] updaterArray = packageUpdaters
+ .toArray(new PackageSharedLibraryUpdater[0]);
+ INSTANCE = new PackageBackwardCompatibility(hasOrgApacheHttpLegacy, updaterArray);
+ }
+
+ private final boolean mRemovedOAHLFromBCP;
+
+ private final PackageSharedLibraryUpdater[] mPackageUpdaters;
+
+ public PackageBackwardCompatibility(boolean removedOAHLFromBCP,
+ PackageSharedLibraryUpdater[] packageUpdaters) {
+ this.mRemovedOAHLFromBCP = removedOAHLFromBCP;
+ this.mPackageUpdaters = packageUpdaters;
+ }
/**
* Modify the shared libraries in the supplied {@link Package} to maintain backwards
@@ -48,52 +94,74 @@
*/
@VisibleForTesting
public static void modifySharedLibraries(Package pkg) {
- ArrayList<String> usesLibraries = pkg.usesLibraries;
- ArrayList<String> usesOptionalLibraries = pkg.usesOptionalLibraries;
+ INSTANCE.updatePackage(pkg);
+ }
- // Packages targeted at <= O_MR1 expect the classes in the org.apache.http.legacy library
- // to be accessible so this maintains backward compatibility by adding the
- // org.apache.http.legacy library to those packages.
- if (apkTargetsApiLevelLessThanOrEqualToOMR1(pkg)) {
- boolean apacheHttpLegacyPresent = isLibraryPresent(
- usesLibraries, usesOptionalLibraries, APACHE_HTTP_LEGACY);
- if (!apacheHttpLegacyPresent) {
- usesLibraries = prefix(usesLibraries, APACHE_HTTP_LEGACY);
+ @Override
+ public void updatePackage(Package pkg) {
+
+ for (PackageSharedLibraryUpdater packageUpdater : mPackageUpdaters) {
+ packageUpdater.updatePackage(pkg);
+ }
+ }
+
+ /**
+ * True if the org.apache.http.legacy has been removed the bootclasspath, false otherwise.
+ */
+ public static boolean removeOAHLFromBCP() {
+ return INSTANCE.mRemovedOAHLFromBCP;
+ }
+
+ /**
+ * Add android.test.mock dependency for any APK that depends on android.test.runner.
+ *
+ * <p>This is needed to maintain backwards compatibility as in previous versions of Android the
+ * android.test.runner library included the classes from android.test.mock which have since
+ * been split out into a separate library.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class AndroidTestRunnerSplitUpdater extends PackageSharedLibraryUpdater {
+
+ @Override
+ public void updatePackage(Package pkg) {
+ ArrayList<String> usesLibraries = pkg.usesLibraries;
+ ArrayList<String> usesOptionalLibraries = pkg.usesOptionalLibraries;
+
+ // android.test.runner has a dependency on android.test.mock so if android.test.runner
+ // is present but android.test.mock is not then add android.test.mock.
+ boolean androidTestMockPresent = isLibraryPresent(
+ usesLibraries, usesOptionalLibraries, ANDROID_TEST_MOCK);
+ if (ArrayUtils.contains(usesLibraries, ANDROID_TEST_RUNNER)
+ && !androidTestMockPresent) {
+ usesLibraries.add(ANDROID_TEST_MOCK);
}
- }
+ if (ArrayUtils.contains(usesOptionalLibraries, ANDROID_TEST_RUNNER)
+ && !androidTestMockPresent) {
+ usesOptionalLibraries.add(ANDROID_TEST_MOCK);
+ }
- // android.test.runner has a dependency on android.test.mock so if android.test.runner
- // is present but android.test.mock is not then add android.test.mock.
- boolean androidTestMockPresent = isLibraryPresent(
- usesLibraries, usesOptionalLibraries, ANDROID_TEST_MOCK);
- if (ArrayUtils.contains(usesLibraries, ANDROID_TEST_RUNNER) && !androidTestMockPresent) {
- usesLibraries.add(ANDROID_TEST_MOCK);
+ pkg.usesLibraries = usesLibraries;
+ pkg.usesOptionalLibraries = usesOptionalLibraries;
}
- if (ArrayUtils.contains(usesOptionalLibraries, ANDROID_TEST_RUNNER)
- && !androidTestMockPresent) {
- usesOptionalLibraries.add(ANDROID_TEST_MOCK);
- }
-
- pkg.usesLibraries = usesLibraries;
- pkg.usesOptionalLibraries = usesOptionalLibraries;
}
- private static boolean apkTargetsApiLevelLessThanOrEqualToOMR1(Package pkg) {
- int targetSdkVersion = pkg.applicationInfo.targetSdkVersion;
- return targetSdkVersion <= Build.VERSION_CODES.O_MR1;
- }
+ /**
+ * Remove any usages of org.apache.http.legacy from the shared library as the library is on the
+ * bootclasspath.
+ */
+ @VisibleForTesting
+ public static class RemoveUnnecessaryOrgApacheHttpLegacyLibrary
+ extends PackageSharedLibraryUpdater {
- private static boolean isLibraryPresent(ArrayList<String> usesLibraries,
- ArrayList<String> usesOptionalLibraries, String apacheHttpLegacy) {
- return ArrayUtils.contains(usesLibraries, apacheHttpLegacy)
- || ArrayUtils.contains(usesOptionalLibraries, apacheHttpLegacy);
- }
+ private static final String APACHE_HTTP_LEGACY = "org.apache.http.legacy";
- private static @NonNull <T> ArrayList<T> prefix(@Nullable ArrayList<T> cur, T val) {
- if (cur == null) {
- cur = new ArrayList<>();
+ @Override
+ public void updatePackage(Package pkg) {
+ pkg.usesLibraries = ArrayUtils.remove(pkg.usesLibraries, APACHE_HTTP_LEGACY);
+ pkg.usesOptionalLibraries =
+ ArrayUtils.remove(pkg.usesOptionalLibraries, APACHE_HTTP_LEGACY);
}
- cur.add(0, val);
- return cur;
}
}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 5a91e94..13ec4fd 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -246,9 +246,44 @@
* equivalent to being signed with certificates B and A. This means that
* in case multiple signatures are reported you cannot assume the one at
* the first position to be the same across updates.
+ *
+ * <strong>Deprecated</strong> This has been replaced by the
+ * {@link PackageInfo#signingCertificateHistory} field, which takes into
+ * account signing certificate rotation. For backwards compatibility in
+ * the event of signing certificate rotation, this will return the oldest
+ * reported signing certificate, so that an application will appear to
+ * callers as though no rotation occurred.
+ *
+ * @deprecated use {@code signingCertificateHistory} instead
*/
+ @Deprecated
public Signature[] signatures;
-
+
+ /**
+ * Array of all signatures arrays read from the package file, potentially
+ * including past signing certificates no longer used after signing
+ * certificate rotation. Though signing certificate rotation is only
+ * available for apps with a single signing certificate, this provides an
+ * array of arrays so that packages signed with multiple signing
+ * certificates can still return all signers. This is only filled in if
+ * the flag {@link PackageManager#GET_SIGNING_CERTIFICATES} was set.
+ *
+ * A package must be singed with at least one certificate, which is at
+ * position zero in the array. An application may be signed by multiple
+ * certificates, which would be in the array at position zero in an
+ * indeterminate order. A package may also have a history of certificates
+ * due to signing certificate rotation. In this case, the array will be
+ * populated by a series of single-entry arrays corresponding to a signing
+ * certificate of the package.
+ *
+ * <strong>Note:</strong> Signature ordering is not guaranteed to be
+ * stable which means that a package signed with certificates A and B is
+ * equivalent to being signed with certificates B and A. This means that
+ * in case multiple signatures are reported you cannot assume the one at
+ * the first position will be the same across updates.
+ */
+ public Signature[][] signingCertificateHistory;
+
/**
* Application specified preferred configuration
* {@link android.R.styleable#AndroidManifestUsesConfiguration
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index 11830c2..2c0c6ad0 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.res.XmlResourceParser;
-
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcel;
@@ -28,6 +27,8 @@
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
+
import java.text.Collator;
import java.util.Comparator;
@@ -386,6 +387,24 @@
dest.writeInt(showUserIcon);
}
+ /**
+ * @hide
+ */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ if (name != null) {
+ proto.write(PackageItemInfoProto.NAME, name);
+ }
+ proto.write(PackageItemInfoProto.PACKAGE_NAME, packageName);
+ if (labelRes != 0 || nonLocalizedLabel != null || icon != 0 || banner != 0) {
+ proto.write(PackageItemInfoProto.LABEL_RES, labelRes);
+ proto.write(PackageItemInfoProto.NON_LOCALIZED_LABEL, nonLocalizedLabel.toString());
+ proto.write(PackageItemInfoProto.ICON, icon);
+ proto.write(PackageItemInfoProto.BANNER, banner);
+ }
+ proto.end(token);
+ }
+
protected PackageItemInfo(Parcel source) {
name = source.readString();
packageName = source.readString();
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b81267a..67c9584 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -133,6 +133,7 @@
GET_SERVICES,
GET_SHARED_LIBRARY_FILES,
GET_SIGNATURES,
+ GET_SIGNING_CERTIFICATES,
GET_URI_PERMISSION_PATTERNS,
MATCH_UNINSTALLED_PACKAGES,
MATCH_DISABLED_COMPONENTS,
@@ -272,7 +273,10 @@
/**
* {@link PackageInfo} flag: return information about the
* signatures included in the package.
+ *
+ * @deprecated use {@code GET_SIGNING_CERTIFICATES} instead
*/
+ @Deprecated
public static final int GET_SIGNATURES = 0x00000040;
/**
@@ -488,6 +492,14 @@
public static final int MATCH_STATIC_SHARED_LIBRARIES = 0x04000000;
/**
+ * {@link PackageInfo} flag: return the signing certificates associated with
+ * this package. Each entry is a signing certificate that the package
+ * has proven it is authorized to use, usually a past signing certificate from
+ * which it has rotated.
+ */
+ public static final int GET_SIGNING_CERTIFICATES = 0x08000000;
+
+ /**
* Internal flag used to indicate that a system component has done their
* homework and verified that they correctly handle packages and components
* that come and go over time. In particular:
@@ -3781,7 +3793,7 @@
public abstract int getInstantAppCookieMaxBytes();
/**
- * @deprecated
+ * deprecated
* @hide
*/
public abstract int getInstantAppCookieMaxSize();
@@ -5914,4 +5926,60 @@
public CharSequence getHarmfulAppWarning(@NonNull String packageName) {
throw new UnsupportedOperationException("getHarmfulAppWarning not implemented in subclass");
}
+
+ /** @hide */
+ @IntDef(prefix = { "CERT_INPUT_" }, value = {
+ CERT_INPUT_RAW_X509,
+ CERT_INPUT_SHA256
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CertificateInputType {}
+
+ /**
+ * Certificate input bytes: the input bytes represent an encoded X.509 Certificate which could
+ * be generated using an {@code CertificateFactory}
+ */
+ public static final int CERT_INPUT_RAW_X509 = 0;
+
+ /**
+ * Certificate input bytes: the input bytes represent the SHA256 output of an encoded X.509
+ * Certificate.
+ */
+ public static final int CERT_INPUT_SHA256 = 1;
+
+ /**
+ * Searches the set of signing certificates by which the given package has proven to have been
+ * signed. This should be used instead of {@code getPackageInfo} with {@code GET_SIGNATURES}
+ * since it takes into account the possibility of signing certificate rotation, except in the
+ * case of packages that are signed by multiple certificates, for which signing certificate
+ * rotation is not supported.
+ *
+ * @param packageName package whose signing certificates to check
+ * @param certificate signing certificate for which to search
+ * @param type representation of the {@code certificate}
+ * @return true if this package was or is signed by exactly the certificate {@code certificate}
+ */
+ public boolean hasSigningCertificate(
+ String packageName, byte[] certificate, @CertificateInputType int type) {
+ throw new UnsupportedOperationException(
+ "hasSigningCertificate not implemented in subclass");
+ }
+
+ /**
+ * Searches the set of signing certificates by which the given uid has proven to have been
+ * signed. This should be used instead of {@code getPackageInfo} with {@code GET_SIGNATURES}
+ * since it takes into account the possibility of signing certificate rotation, except in the
+ * case of packages that are signed by multiple certificates, for which signing certificate
+ * rotation is not supported.
+ *
+ * @param uid package whose signing certificates to check
+ * @param certificate signing certificate for which to search
+ * @param type representation of the {@code certificate}
+ * @return true if this package was or is signed by exactly the certificate {@code certificate}
+ */
+ public boolean hasSigningCertificate(
+ int uid, byte[] certificate, @CertificateInputType int type) {
+ throw new UnsupportedOperationException(
+ "hasSigningCertificate not implemented in subclass");
+ }
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 4a71467..5b5ccf5 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -801,13 +801,40 @@
}
}
}
+ // deprecated method of getting signing certificates
if ((flags&PackageManager.GET_SIGNATURES) != 0) {
- if (p.mSigningDetails.hasSignatures()) {
+ if (p.mSigningDetails.hasPastSigningCertificates()) {
+ // Package has included signing certificate rotation information. Return the oldest
+ // cert so that programmatic checks keep working even if unaware of key rotation.
+ pi.signatures = new Signature[1];
+ pi.signatures[0] = p.mSigningDetails.pastSigningCertificates[0];
+ } else if (p.mSigningDetails.hasSignatures()) {
+ // otherwise keep old behavior
int numberOfSigs = p.mSigningDetails.signatures.length;
pi.signatures = new Signature[numberOfSigs];
System.arraycopy(p.mSigningDetails.signatures, 0, pi.signatures, 0, numberOfSigs);
}
}
+
+ // replacement for GET_SIGNATURES
+ if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
+ if (p.mSigningDetails.hasPastSigningCertificates()) {
+ // Package has included signing certificate rotation information. Convert each
+ // entry to an array
+ int numberOfSigs = p.mSigningDetails.pastSigningCertificates.length;
+ pi.signingCertificateHistory = new Signature[numberOfSigs][];
+ for (int i = 0; i < numberOfSigs; i++) {
+ pi.signingCertificateHistory[i] =
+ new Signature[] { p.mSigningDetails.pastSigningCertificates[i] };
+ }
+ } else if (p.mSigningDetails.hasSignatures()) {
+ // otherwise keep old behavior
+ int numberOfSigs = p.mSigningDetails.signatures.length;
+ pi.signingCertificateHistory = new Signature[1][numberOfSigs];
+ System.arraycopy(p.mSigningDetails.signatures, 0,
+ pi.signingCertificateHistory[0], 0, numberOfSigs);
+ }
+ }
return pi;
}
@@ -5684,23 +5711,74 @@
@Nullable
public final ArraySet<PublicKey> publicKeys;
+ /**
+ * Collection of {@code Signature} objects, each of which is formed from a former signing
+ * certificate of this APK before it was changed by signing certificate rotation.
+ */
+ @Nullable
+ public final Signature[] pastSigningCertificates;
+
+ /**
+ * Flags for the {@code pastSigningCertificates} collection, which indicate the capabilities
+ * the including APK wishes to grant to its past signing certificates.
+ */
+ @Nullable
+ public final int[] pastSigningCertificatesFlags;
+
/** A representation of unknown signing details. Use instead of null. */
public static final SigningDetails UNKNOWN =
- new SigningDetails(null, SignatureSchemeVersion.UNKNOWN, null);
+ new SigningDetails(null, SignatureSchemeVersion.UNKNOWN, null, null, null);
@VisibleForTesting
public SigningDetails(Signature[] signatures,
@SignatureSchemeVersion int signatureSchemeVersion,
- ArraySet<PublicKey> keys) {
+ ArraySet<PublicKey> keys, Signature[] pastSigningCertificates,
+ int[] pastSigningCertificatesFlags) {
this.signatures = signatures;
this.signatureSchemeVersion = signatureSchemeVersion;
this.publicKeys = keys;
+ this.pastSigningCertificates = pastSigningCertificates;
+ this.pastSigningCertificatesFlags = pastSigningCertificatesFlags;
+ }
+
+ public SigningDetails(Signature[] signatures,
+ @SignatureSchemeVersion int signatureSchemeVersion,
+ Signature[] pastSigningCertificates, int[] pastSigningCertificatesFlags)
+ throws CertificateException {
+ this(signatures, signatureSchemeVersion, toSigningKeys(signatures),
+ pastSigningCertificates, pastSigningCertificatesFlags);
}
public SigningDetails(Signature[] signatures,
@SignatureSchemeVersion int signatureSchemeVersion)
throws CertificateException {
- this(signatures, signatureSchemeVersion, toSigningKeys(signatures));
+ this(signatures, signatureSchemeVersion,
+ null, null);
+ }
+
+ public SigningDetails(SigningDetails orig) {
+ if (orig != null) {
+ if (orig.signatures != null) {
+ this.signatures = orig.signatures.clone();
+ } else {
+ this.signatures = null;
+ }
+ this.signatureSchemeVersion = orig.signatureSchemeVersion;
+ this.publicKeys = new ArraySet<>(orig.publicKeys);
+ if (orig.pastSigningCertificates != null) {
+ this.pastSigningCertificates = orig.pastSigningCertificates.clone();
+ this.pastSigningCertificatesFlags = orig.pastSigningCertificatesFlags.clone();
+ } else {
+ this.pastSigningCertificates = null;
+ this.pastSigningCertificatesFlags = null;
+ }
+ } else {
+ this.signatures = null;
+ this.signatureSchemeVersion = SignatureSchemeVersion.UNKNOWN;
+ this.publicKeys = null;
+ this.pastSigningCertificates = null;
+ this.pastSigningCertificatesFlags = null;
+ }
}
/** Returns true if the signing details have one or more signatures. */
@@ -5708,6 +5786,11 @@
return signatures != null && signatures.length > 0;
}
+ /** Returns true if the signing details have past signing certificates. */
+ public boolean hasPastSigningCertificates() {
+ return pastSigningCertificates != null && pastSigningCertificates.length > 0;
+ }
+
/** Returns true if the signatures in this and other match exactly. */
public boolean signaturesMatchExactly(SigningDetails other) {
return Signature.areExactMatch(this.signatures, other.signatures);
@@ -5728,6 +5811,8 @@
dest.writeTypedArray(this.signatures, flags);
dest.writeInt(this.signatureSchemeVersion);
dest.writeArraySet(this.publicKeys);
+ dest.writeTypedArray(this.pastSigningCertificates, flags);
+ dest.writeIntArray(this.pastSigningCertificatesFlags);
}
protected SigningDetails(Parcel in) {
@@ -5735,6 +5820,8 @@
this.signatures = in.createTypedArray(Signature.CREATOR);
this.signatureSchemeVersion = in.readInt();
this.publicKeys = (ArraySet<PublicKey>) in.readArraySet(boot);
+ this.pastSigningCertificates = in.createTypedArray(Signature.CREATOR);
+ this.pastSigningCertificatesFlags = in.createIntArray();
}
public static final Creator<SigningDetails> CREATOR = new Creator<SigningDetails>() {
@@ -5761,8 +5848,23 @@
if (signatureSchemeVersion != that.signatureSchemeVersion) return false;
if (!Signature.areExactMatch(signatures, that.signatures)) return false;
- return publicKeys != null ? publicKeys.equals(that.publicKeys)
- : that.publicKeys == null;
+ if (publicKeys != null) {
+ if (!publicKeys.equals((that.publicKeys))) {
+ return false;
+ }
+ } else if (that.publicKeys != null) {
+ return false;
+ }
+
+ // can't use Signature.areExactMatch() because order matters with the past signing certs
+ if (!Arrays.equals(pastSigningCertificates, that.pastSigningCertificates)) {
+ return false;
+ }
+ if (!Arrays.equals(pastSigningCertificatesFlags, that.pastSigningCertificatesFlags)) {
+ return false;
+ }
+
+ return true;
}
@Override
@@ -5770,8 +5872,77 @@
int result = +Arrays.hashCode(signatures);
result = 31 * result + signatureSchemeVersion;
result = 31 * result + (publicKeys != null ? publicKeys.hashCode() : 0);
+ result = 31 * result + Arrays.hashCode(pastSigningCertificates);
+ result = 31 * result + Arrays.hashCode(pastSigningCertificatesFlags);
return result;
}
+
+ /**
+ * Builder of {@code SigningDetails} instances.
+ */
+ public static class Builder {
+ private Signature[] mSignatures;
+ private int mSignatureSchemeVersion = SignatureSchemeVersion.UNKNOWN;
+ private Signature[] mPastSigningCertificates;
+ private int[] mPastSigningCertificatesFlags;
+
+ public Builder() {
+ }
+
+ /** get signing certificates used to sign the current APK */
+ public Builder setSignatures(Signature[] signatures) {
+ mSignatures = signatures;
+ return this;
+ }
+
+ /** set the signature scheme version used to sign the APK */
+ public Builder setSignatureSchemeVersion(int signatureSchemeVersion) {
+ mSignatureSchemeVersion = signatureSchemeVersion;
+ return this;
+ }
+
+ /** set the signing certificates by which the APK proved it can be authenticated */
+ public Builder setPastSigningCertificates(Signature[] pastSigningCertificates) {
+ mPastSigningCertificates = pastSigningCertificates;
+ return this;
+ }
+
+ /** set the flags for the {@code pastSigningCertificates} */
+ public Builder setPastSigningCertificatesFlags(int[] pastSigningCertificatesFlags) {
+ mPastSigningCertificatesFlags = pastSigningCertificatesFlags;
+ return this;
+ }
+
+ private void checkInvariants() {
+ // must have signatures and scheme version set
+ if (mSignatures == null) {
+ throw new IllegalStateException("SigningDetails requires the current signing"
+ + " certificates.");
+ }
+
+ // pastSigningCerts and flags must match up
+ boolean pastMismatch = false;
+ if (mPastSigningCertificates != null && mPastSigningCertificatesFlags != null) {
+ if (mPastSigningCertificates.length != mPastSigningCertificatesFlags.length) {
+ pastMismatch = true;
+ }
+ } else if (!(mPastSigningCertificates == null
+ && mPastSigningCertificatesFlags == null)) {
+ pastMismatch = true;
+ }
+ if (pastMismatch) {
+ throw new IllegalStateException("SigningDetails must have a one to one mapping "
+ + "between pastSigningCertificates and pastSigningCertificatesFlags");
+ }
+ }
+ /** build a {@code SigningDetails} object */
+ public SigningDetails build()
+ throws CertificateException {
+ checkInvariants();
+ return new SigningDetails(mSignatures, mSignatureSchemeVersion,
+ mPastSigningCertificates, mPastSigningCertificatesFlags);
+ }
+ }
}
/**
diff --git a/core/java/android/content/pm/PackageSharedLibraryUpdater.java b/core/java/android/content/pm/PackageSharedLibraryUpdater.java
new file mode 100644
index 0000000..49d884c
--- /dev/null
+++ b/core/java/android/content/pm/PackageSharedLibraryUpdater.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Base for classes that update a {@link PackageParser.Package}'s shared libraries.
+ *
+ * @hide
+ */
+@VisibleForTesting
+public abstract class PackageSharedLibraryUpdater {
+
+ /**
+ * Update the package's shared libraries.
+ *
+ * @param pkg the package to update.
+ */
+ public abstract void updatePackage(PackageParser.Package pkg);
+
+ static @NonNull
+ <T> ArrayList<T> prefix(@Nullable ArrayList<T> cur, T val) {
+ if (cur == null) {
+ cur = new ArrayList<>();
+ }
+ cur.add(0, val);
+ return cur;
+ }
+
+ static boolean isLibraryPresent(ArrayList<String> usesLibraries,
+ ArrayList<String> usesOptionalLibraries, String apacheHttpLegacy) {
+ return ArrayUtils.contains(usesLibraries, apacheHttpLegacy)
+ || ArrayUtils.contains(usesOptionalLibraries, apacheHttpLegacy);
+ }
+}
diff --git a/core/java/android/content/pm/dex/ArtManager.java b/core/java/android/content/pm/dex/ArtManager.java
index 201cd8d..aa9c46e6 100644
--- a/core/java/android/content/pm/dex/ArtManager.java
+++ b/core/java/android/content/pm/dex/ArtManager.java
@@ -153,4 +153,14 @@
return true;
}
}
+
+ /**
+ * Return the profile name for the given split. If {@code splitName} is null the
+ * method returns the profile name for the base apk.
+ *
+ * @hide
+ */
+ public static String getProfileName(String splitName) {
+ return splitName == null ? "primary.prof" : splitName + ".split.prof";
+ }
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintDialog.java b/core/java/android/hardware/fingerprint/FingerprintDialog.java
new file mode 100644
index 0000000..6b7fab7
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/FingerprintDialog.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.fingerprint;
+
+import static android.Manifest.permission.USE_FINGERPRINT;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
+import android.hardware.fingerprint.IFingerprintDialogReceiver;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.text.TextUtils;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A class that manages a system-provided fingerprint dialog.
+ */
+public class FingerprintDialog {
+
+ /**
+ * @hide
+ */
+ public static final String KEY_TITLE = "title";
+ /**
+ * @hide
+ */
+ public static final String KEY_SUBTITLE = "subtitle";
+ /**
+ * @hide
+ */
+ public static final String KEY_DESCRIPTION = "description";
+ /**
+ * @hide
+ */
+ public static final String KEY_POSITIVE_TEXT = "positive_text";
+ /**
+ * @hide
+ */
+ public static final String KEY_NEGATIVE_TEXT = "negative_text";
+
+ /**
+ * Error/help message will show for this amount of time.
+ * For error messages, the dialog will also be dismissed after this amount of time.
+ * Error messages will be propagated back to the application via AuthenticationCallback
+ * after this amount of time.
+ * @hide
+ */
+ public static final int HIDE_DIALOG_DELAY = 3000; // ms
+ /**
+ * @hide
+ */
+ public static final int DISMISSED_REASON_POSITIVE = 1;
+
+ /**
+ * @hide
+ */
+ public static final int DISMISSED_REASON_NEGATIVE = 2;
+
+ /**
+ * @hide
+ */
+ public static final int DISMISSED_REASON_USER_CANCEL = 3;
+
+ private static class ButtonInfo {
+ Executor executor;
+ DialogInterface.OnClickListener listener;
+ ButtonInfo(Executor ex, DialogInterface.OnClickListener l) {
+ executor = ex;
+ listener = l;
+ }
+ }
+
+ /**
+ * A builder that collects arguments, to be shown on the system-provided fingerprint dialog.
+ **/
+ public static class Builder {
+ private final Bundle bundle;
+ private ButtonInfo positiveButtonInfo;
+ private ButtonInfo negativeButtonInfo;
+
+ /**
+ * Creates a builder for a fingerprint dialog.
+ */
+ public Builder() {
+ bundle = new Bundle();
+ }
+
+ /**
+ * Required: Set the title to display.
+ * @param title
+ * @return
+ */
+ public Builder setTitle(@NonNull CharSequence title) {
+ bundle.putCharSequence(KEY_TITLE, title);
+ return this;
+ }
+
+ /**
+ * Optional: Set the subtitle to display.
+ * @param subtitle
+ * @return
+ */
+ public Builder setSubtitle(@NonNull CharSequence subtitle) {
+ bundle.putCharSequence(KEY_SUBTITLE, subtitle);
+ return this;
+ }
+
+ /**
+ * Optional: Set the description to display.
+ * @param description
+ * @return
+ */
+ public Builder setDescription(@NonNull CharSequence description) {
+ bundle.putCharSequence(KEY_DESCRIPTION, description);
+ return this;
+ }
+
+ /**
+ * Optional: Set the text for the positive button. If not set, the positive button
+ * will not show.
+ * @param text
+ * @return
+ * @hide
+ */
+ public Builder setPositiveButton(@NonNull CharSequence text,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull DialogInterface.OnClickListener listener) {
+ if (TextUtils.isEmpty(text)) {
+ throw new IllegalArgumentException("Text must be set and non-empty");
+ }
+ if (executor == null) {
+ throw new IllegalArgumentException("Executor must not be null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("Listener must not be null");
+ }
+ bundle.putCharSequence(KEY_POSITIVE_TEXT, text);
+ positiveButtonInfo = new ButtonInfo(executor, listener);
+ return this;
+ }
+
+ /**
+ * Required: Set the text for the negative button.
+ * @param text
+ * @return
+ */
+ public Builder setNegativeButton(@NonNull CharSequence text,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull DialogInterface.OnClickListener listener) {
+ if (TextUtils.isEmpty(text)) {
+ throw new IllegalArgumentException("Text must be set and non-empty");
+ }
+ if (executor == null) {
+ throw new IllegalArgumentException("Executor must not be null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("Listener must not be null");
+ }
+ bundle.putCharSequence(KEY_NEGATIVE_TEXT, text);
+ negativeButtonInfo = new ButtonInfo(executor, listener);
+ return this;
+ }
+
+ /**
+ * Creates a {@link FingerprintDialog} with the arguments supplied to this builder.
+ * @param context
+ * @return a {@link FingerprintDialog}
+ * @throws IllegalArgumentException if any of the required fields are not set.
+ */
+ public FingerprintDialog build(Context context) {
+ final CharSequence title = bundle.getCharSequence(KEY_TITLE);
+ final CharSequence negative = bundle.getCharSequence(KEY_NEGATIVE_TEXT);
+
+ if (TextUtils.isEmpty(title)) {
+ throw new IllegalArgumentException("Title must be set and non-empty");
+ } else if (TextUtils.isEmpty(negative)) {
+ throw new IllegalArgumentException("Negative text must be set and non-empty");
+ }
+ return new FingerprintDialog(context, bundle, positiveButtonInfo, negativeButtonInfo);
+ }
+ }
+
+ private FingerprintManager mFingerprintManager;
+ private Bundle mBundle;
+ private ButtonInfo mPositiveButtonInfo;
+ private ButtonInfo mNegativeButtonInfo;
+
+ IFingerprintDialogReceiver mDialogReceiver = new IFingerprintDialogReceiver.Stub() {
+ @Override
+ public void onDialogDismissed(int reason) {
+ // Check the reason and invoke OnClickListener(s) if necessary
+ if (reason == DISMISSED_REASON_POSITIVE) {
+ mPositiveButtonInfo.executor.execute(() -> {
+ mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE);
+ });
+ } else if (reason == DISMISSED_REASON_NEGATIVE) {
+ mNegativeButtonInfo.executor.execute(() -> {
+ mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE);
+ });
+ }
+ }
+ };
+
+ private FingerprintDialog(Context context, Bundle bundle,
+ ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo) {
+ mBundle = bundle;
+ mPositiveButtonInfo = positiveButtonInfo;
+ mNegativeButtonInfo = negativeButtonInfo;
+ mFingerprintManager = context.getSystemService(FingerprintManager.class);
+ }
+
+ /**
+ * This call warms up the fingerprint hardware, displays a system-provided dialog,
+ * and starts scanning for a fingerprint. It terminates when
+ * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when
+ * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called,
+ * when {@link AuthenticationCallback#onAuthenticationFailed()} is called or when the user
+ * dismisses the system-provided dialog, at which point the crypto object becomes invalid.
+ * This operation can be canceled by using the provided cancel object. The application will
+ * receive authentication errors through {@link AuthenticationCallback}, and button events
+ * through the corresponding callback set in
+ * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.
+ * It is safe to reuse the {@link FingerprintDialog} object, and calling
+ * {@link FingerprintDialog#authenticate(CancellationSignal, Executor, AuthenticationCallback)}
+ * while an existing authentication attempt is occurring will stop the previous client and
+ * start a new authentication. The interrupted client will receive a cancelled notification
+ * through {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}.
+ *
+ * @throws IllegalArgumentException if any of the arguments are null
+ *
+ * @param crypto object associated with the call
+ * @param cancel an object that can be used to cancel authentication
+ * @param executor an executor to handle callback events
+ * @param callback an object to receive authentication events
+ */
+ @RequiresPermission(USE_FINGERPRINT)
+ public void authenticate(@NonNull FingerprintManager.CryptoObject crypto,
+ @NonNull CancellationSignal cancel,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull FingerprintManager.AuthenticationCallback callback) {
+ mFingerprintManager.authenticate(crypto, cancel, mBundle, executor, mDialogReceiver,
+ callback);
+ }
+
+ /**
+ * This call warms up the fingerprint hardware, displays a system-provided dialog,
+ * and starts scanning for a fingerprint. It terminates when
+ * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when
+ * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called,
+ * when {@link AuthenticationCallback#onAuthenticationFailed()} is called or when the user
+ * dismisses the system-provided dialog. This operation can be canceled by using the provided
+ * cancel object. The application will receive authentication errors through
+ * {@link AuthenticationCallback}, and button events through the corresponding callback set in
+ * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.
+ * It is safe to reuse the {@link FingerprintDialog} object, and calling
+ * {@link FingerprintDialog#authenticate(CancellationSignal, Executor, AuthenticationCallback)}
+ * while an existing authentication attempt is occurring will stop the previous client and
+ * start a new authentication. The interrupted client will receive a cancelled notification
+ * through {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}.
+ *
+ * @throws IllegalArgumentException if any of the arguments are null
+ *
+ * @param cancel an object that can be used to cancel authentication
+ * @param executor an executor to handle callback events
+ * @param callback an object to receive authentication events
+ */
+ @RequiresPermission(USE_FINGERPRINT)
+ public void authenticate(@NonNull CancellationSignal cancel,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull FingerprintManager.AuthenticationCallback callback) {
+ mFingerprintManager.authenticate(cancel, mBundle, executor, mDialogReceiver, callback);
+ }
+}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 987718a..62d92c4 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -16,6 +16,11 @@
package android.hardware.fingerprint;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.MANAGE_FINGERPRINT;
+import static android.Manifest.permission.USE_FINGERPRINT;
+
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -23,6 +28,7 @@
import android.app.ActivityManager;
import android.content.Context;
import android.os.Binder;
+import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
import android.os.Handler;
@@ -38,14 +44,11 @@
import java.security.Signature;
import java.util.List;
+import java.util.concurrent.Executor;
import javax.crypto.Cipher;
import javax.crypto.Mac;
-import static android.Manifest.permission.INTERACT_ACROSS_USERS;
-import static android.Manifest.permission.MANAGE_FINGERPRINT;
-import static android.Manifest.permission.USE_FINGERPRINT;
-
/**
* A class that coordinates access to the fingerprint hardware.
*/
@@ -204,6 +207,7 @@
private CryptoObject mCryptoObject;
private Fingerprint mRemovalFingerprint;
private Handler mHandler;
+ private Executor mExecutor;
private class OnEnrollCancelListener implements OnCancelListener {
@Override
@@ -505,7 +509,9 @@
}
/**
- * Per-user version
+ * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject,
+ * CancellationSignal, int, AuthenticationCallback, Handler)}
+ * @param userId the user ID that the fingerprint hardware will authenticate for.
* @hide
*/
@RequiresPermission(USE_FINGERPRINT)
@@ -530,7 +536,7 @@
mCryptoObject = crypto;
long sessionId = crypto != null ? crypto.getOpId() : 0;
mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags,
- mContext.getOpPackageName());
+ mContext.getOpPackageName(), null /* bundle */, null /* receiver */);
} catch (RemoteException e) {
Log.w(TAG, "Remote exception while authenticating: ", e);
if (callback != null) {
@@ -543,6 +549,111 @@
}
/**
+ * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject,
+ * CancellationSignal, Bundle, Executor, IFingerprintDialogReceiver, AuthenticationCallback)}
+ * @param userId the user ID that the fingerprint hardware will authenticate for.
+ */
+ private void authenticate(int userId,
+ @Nullable CryptoObject crypto,
+ @NonNull CancellationSignal cancel,
+ @NonNull Bundle bundle,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull IFingerprintDialogReceiver receiver,
+ @NonNull AuthenticationCallback callback) {
+ mCryptoObject = crypto;
+ if (cancel.isCanceled()) {
+ Log.w(TAG, "authentication already canceled");
+ return;
+ } else {
+ cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
+ }
+
+ if (mService != null) {
+ try {
+ mExecutor = executor;
+ mAuthenticationCallback = callback;
+ final long sessionId = crypto != null ? crypto.getOpId() : 0;
+ mService.authenticate(mToken, sessionId, userId, mServiceReceiver,
+ 0 /* flags */, mContext.getOpPackageName(), bundle, receiver);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Remote exception while authenticating", e);
+ mExecutor.execute(() -> {
+ callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
+ });
+ }
+ }
+ }
+
+ /**
+ * Private method, see {@link FingerprintDialog#authenticate(CancellationSignal, Executor,
+ * AuthenticationCallback)}
+ * @param cancel
+ * @param executor
+ * @param callback
+ * @hide
+ */
+ public void authenticate(
+ @NonNull CancellationSignal cancel,
+ @NonNull Bundle bundle,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull IFingerprintDialogReceiver receiver,
+ @NonNull AuthenticationCallback callback) {
+ if (cancel == null) {
+ throw new IllegalArgumentException("Must supply a cancellation signal");
+ }
+ if (bundle == null) {
+ throw new IllegalArgumentException("Must supply a bundle");
+ }
+ if (executor == null) {
+ throw new IllegalArgumentException("Must supply an executor");
+ }
+ if (receiver == null) {
+ throw new IllegalArgumentException("Must supply a receiver");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("Must supply a calback");
+ }
+ authenticate(UserHandle.myUserId(), null, cancel, bundle, executor, receiver, callback);
+ }
+
+ /**
+ * Private method, see {@link FingerprintDialog#authenticate(CryptoObject, CancellationSignal,
+ * Executor, AuthenticationCallback)}
+ * @param crypto
+ * @param cancel
+ * @param executor
+ * @param callback
+ * @hide
+ */
+ public void authenticate(@NonNull CryptoObject crypto,
+ @NonNull CancellationSignal cancel,
+ @NonNull Bundle bundle,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull IFingerprintDialogReceiver receiver,
+ @NonNull AuthenticationCallback callback) {
+ if (crypto == null) {
+ throw new IllegalArgumentException("Must supply a crypto object");
+ }
+ if (cancel == null) {
+ throw new IllegalArgumentException("Must supply a cancellation signal");
+ }
+ if (bundle == null) {
+ throw new IllegalArgumentException("Must supply a bundle");
+ }
+ if (executor == null) {
+ throw new IllegalArgumentException("Must supply an executor");
+ }
+ if (receiver == null) {
+ throw new IllegalArgumentException("Must supply a receiver");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("Must supply a calback");
+ }
+ authenticate(UserHandle.myUserId(), crypto, cancel, bundle, executor, receiver, callback);
+ }
+
+ /**
* Request fingerprint enrollment. This call warms up the fingerprint hardware
* and starts scanning for fingerprints. Progress will be indicated by callbacks to the
* {@link EnrollmentCallback} object. It terminates when
@@ -929,64 +1040,64 @@
}
}
- private void sendErrorResult(long deviceId, int errMsgId, int vendorCode) {
- // emulate HAL 2.1 behavior and send real errMsgId
- final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR
- ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId;
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
- getErrorString(errMsgId, vendorCode));
- } else if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
- getErrorString(errMsgId, vendorCode));
- } else if (mRemovalCallback != null) {
- mRemovalCallback.onRemovalError(mRemovalFingerprint, clientErrMsgId,
- getErrorString(errMsgId, vendorCode));
- } else if (mEnumerateCallback != null) {
- mEnumerateCallback.onEnumerateError(clientErrMsgId,
- getErrorString(errMsgId, vendorCode));
- }
- }
-
private void sendEnrollResult(Fingerprint fp, int remaining) {
if (mEnrollmentCallback != null) {
mEnrollmentCallback.onEnrollmentProgress(remaining);
}
}
-
- private void sendAuthenticatedSucceeded(Fingerprint fp, int userId) {
- if (mAuthenticationCallback != null) {
- final AuthenticationResult result =
- new AuthenticationResult(mCryptoObject, fp, userId);
- mAuthenticationCallback.onAuthenticationSucceeded(result);
- }
- }
-
- private void sendAuthenticatedFailed() {
- if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationFailed();
- }
- }
-
- private void sendAcquiredResult(long deviceId, int acquireInfo, int vendorCode) {
- if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
- }
- final String msg = getAcquiredString(acquireInfo, vendorCode);
- if (msg == null) {
- return;
- }
- // emulate HAL 2.1 behavior and send real acquiredInfo
- final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR
- ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo;
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
- } else if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
- }
- }
};
+ private void sendAuthenticatedSucceeded(Fingerprint fp, int userId) {
+ if (mAuthenticationCallback != null) {
+ final AuthenticationResult result =
+ new AuthenticationResult(mCryptoObject, fp, userId);
+ mAuthenticationCallback.onAuthenticationSucceeded(result);
+ }
+ }
+
+ private void sendAuthenticatedFailed() {
+ if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationFailed();
+ }
+ }
+
+ private void sendAcquiredResult(long deviceId, int acquireInfo, int vendorCode) {
+ if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
+ }
+ final String msg = getAcquiredString(acquireInfo, vendorCode);
+ if (msg == null) {
+ return;
+ }
+ // emulate HAL 2.1 behavior and send real acquiredInfo
+ final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR
+ ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo;
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
+ } else if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
+ }
+ }
+
+ private void sendErrorResult(long deviceId, int errMsgId, int vendorCode) {
+ // emulate HAL 2.1 behavior and send real errMsgId
+ final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR
+ ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId;
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
+ getErrorString(errMsgId, vendorCode));
+ } else if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
+ getErrorString(errMsgId, vendorCode));
+ } else if (mRemovalCallback != null) {
+ mRemovalCallback.onRemovalError(mRemovalFingerprint, clientErrMsgId,
+ getErrorString(errMsgId, vendorCode));
+ } else if (mEnumerateCallback != null) {
+ mEnumerateCallback.onEnumerateError(clientErrMsgId,
+ getErrorString(errMsgId, vendorCode));
+ }
+ }
+
/**
* @hide
*/
@@ -1023,7 +1134,10 @@
}
}
- private String getErrorString(int errMsg, int vendorCode) {
+ /**
+ * @hide
+ */
+ public String getErrorString(int errMsg, int vendorCode) {
switch (errMsg) {
case FINGERPRINT_ERROR_UNABLE_TO_PROCESS:
return mContext.getString(
@@ -1043,6 +1157,9 @@
case FINGERPRINT_ERROR_LOCKOUT_PERMANENT:
return mContext.getString(
com.android.internal.R.string.fingerprint_error_lockout_permanent);
+ case FINGERPRINT_ERROR_USER_CANCELED:
+ return mContext.getString(
+ com.android.internal.R.string.fingerprint_error_user_canceled);
case FINGERPRINT_ERROR_VENDOR: {
String[] msgArray = mContext.getResources().getStringArray(
com.android.internal.R.array.fingerprint_error_vendor);
@@ -1055,7 +1172,10 @@
return null;
}
- private String getAcquiredString(int acquireInfo, int vendorCode) {
+ /**
+ * @hide
+ */
+ public String getAcquiredString(int acquireInfo, int vendorCode) {
switch (acquireInfo) {
case FINGERPRINT_ACQUIRED_GOOD:
return null;
@@ -1096,22 +1216,47 @@
@Override // binder call
public void onAcquired(long deviceId, int acquireInfo, int vendorCode) {
- mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode, deviceId).sendToTarget();
+ if (mExecutor != null) {
+ mExecutor.execute(() -> {
+ sendAcquiredResult(deviceId, acquireInfo, vendorCode);
+ });
+ } else {
+ mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode,
+ deviceId).sendToTarget();
+ }
}
@Override // binder call
public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, fp).sendToTarget();
+ if (mExecutor != null) {
+ mExecutor.execute(() -> {
+ sendAuthenticatedSucceeded(fp, userId);
+ });
+ } else {
+ mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, fp).sendToTarget();
+ }
}
@Override // binder call
public void onAuthenticationFailed(long deviceId) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
+ if (mExecutor != null) {
+ mExecutor.execute(() -> {
+ sendAuthenticatedFailed();
+ });
+ } else {
+ mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
+ }
}
@Override // binder call
public void onError(long deviceId, int error, int vendorCode) {
- mHandler.obtainMessage(MSG_ERROR, error, vendorCode, deviceId).sendToTarget();
+ if (mExecutor != null) {
+ mExecutor.execute(() -> {
+ sendErrorResult(deviceId, error, vendorCode);
+ });
+ } else {
+ mHandler.obtainMessage(MSG_ERROR, error, vendorCode, deviceId).sendToTarget();
+ }
}
@Override // binder call
diff --git a/core/java/android/hardware/fingerprint/IFingerprintDialogReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintDialogReceiver.aidl
new file mode 100644
index 0000000..13e7974
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/IFingerprintDialogReceiver.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.fingerprint;
+
+import android.hardware.fingerprint.Fingerprint;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+/**
+ * Communication channel from the FingerprintDialog (SysUI) back to AuthenticationClient.
+ * @hide
+ */
+oneway interface IFingerprintDialogReceiver {
+ void onDialogDismissed(int reason);
+}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 4879d54..f1502e4 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -17,6 +17,7 @@
import android.os.Bundle;
import android.hardware.fingerprint.IFingerprintClientActiveCallback;
+import android.hardware.fingerprint.IFingerprintDialogReceiver;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.IFingerprintServiceLockoutResetCallback;
import android.hardware.fingerprint.Fingerprint;
@@ -29,7 +30,8 @@
interface IFingerprintService {
// Authenticate the given sessionId with a fingerprint
void authenticate(IBinder token, long sessionId, int userId,
- IFingerprintServiceReceiver receiver, int flags, String opPackageName);
+ IFingerprintServiceReceiver receiver, int flags, String opPackageName,
+ in Bundle bundle, IFingerprintDialogReceiver dialogReceiver);
// Cancel authentication for the given sessionId
void cancelAuthentication(IBinder token, String opPackageName);
diff --git a/core/java/android/security/keystore/recovery/KeychainSnapshot.aidl b/core/java/android/hardware/radio/Announcement.aidl
similarity index 79%
copy from core/java/android/security/keystore/recovery/KeychainSnapshot.aidl
copy to core/java/android/hardware/radio/Announcement.aidl
index 7822f39..eeb5951 100644
--- a/core/java/android/security/keystore/recovery/KeychainSnapshot.aidl
+++ b/core/java/android/hardware/radio/Announcement.aidl
@@ -1,5 +1,5 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
+/**
+ * Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.security.keystore.recovery;
+package android.hardware.radio;
-/* @hide */
-parcelable KeychainSnapshot;
+/** @hide */
+parcelable Announcement;
diff --git a/core/java/android/hardware/radio/Announcement.java b/core/java/android/hardware/radio/Announcement.java
new file mode 100644
index 0000000..166fe60
--- /dev/null
+++ b/core/java/android/hardware/radio/Announcement.java
@@ -0,0 +1,133 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class Announcement implements Parcelable {
+
+ /** DAB alarm, RDS emergency program type (PTY 31). */
+ public static final int TYPE_EMERGENCY = 1;
+ /** DAB warning. */
+ public static final int TYPE_WARNING = 2;
+ /** DAB road traffic, RDS TA, HD Radio transportation. */
+ public static final int TYPE_TRAFFIC = 3;
+ /** Weather. */
+ public static final int TYPE_WEATHER = 4;
+ /** News. */
+ public static final int TYPE_NEWS = 5;
+ /** DAB event, special event. */
+ public static final int TYPE_EVENT = 6;
+ /** DAB sport report, RDS sports. */
+ public static final int TYPE_SPORT = 7;
+ /** All others. */
+ public static final int TYPE_MISC = 8;
+ /** @hide */
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_EMERGENCY,
+ TYPE_WARNING,
+ TYPE_TRAFFIC,
+ TYPE_WEATHER,
+ TYPE_NEWS,
+ TYPE_EVENT,
+ TYPE_SPORT,
+ TYPE_MISC,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {}
+
+ /**
+ * Listener of announcement list events.
+ */
+ public interface OnListUpdatedListener {
+ /**
+ * An event called whenever a list of active announcements change.
+ *
+ * The entire list is sent each time a new announcement appears or any ends broadcasting.
+ *
+ * @param activeAnnouncements a full list of active announcements
+ */
+ void onListUpdated(Collection<Announcement> activeAnnouncements);
+ }
+
+ @NonNull private final ProgramSelector mSelector;
+ @Type private final int mType;
+ @NonNull private final Map<String, String> mVendorInfo;
+
+ /** @hide */
+ public Announcement(@NonNull ProgramSelector selector, @Type int type,
+ @NonNull Map<String, String> vendorInfo) {
+ mSelector = Objects.requireNonNull(selector);
+ mType = Objects.requireNonNull(type);
+ mVendorInfo = Objects.requireNonNull(vendorInfo);
+ }
+
+ private Announcement(@NonNull Parcel in) {
+ mSelector = in.readTypedObject(ProgramSelector.CREATOR);
+ mType = in.readInt();
+ mVendorInfo = Utils.readStringMap(in);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedObject(mSelector, 0);
+ dest.writeInt(mType);
+ Utils.writeStringMap(dest, mVendorInfo);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<Announcement> CREATOR =
+ new Parcelable.Creator<Announcement>() {
+ public Announcement createFromParcel(Parcel in) {
+ return new Announcement(in);
+ }
+
+ public Announcement[] newArray(int size) {
+ return new Announcement[size];
+ }
+ };
+
+ public @NonNull ProgramSelector getSelector() {
+ return mSelector;
+ }
+
+ public @Type int getType() {
+ return mType;
+ }
+
+ public @NonNull Map<String, String> getVendorInfo() {
+ return mVendorInfo;
+ }
+}
diff --git a/core/java/android/security/keystore/recovery/KeychainProtectionParams.aidl b/core/java/android/hardware/radio/IAnnouncementListener.aidl
similarity index 67%
copy from core/java/android/security/keystore/recovery/KeychainProtectionParams.aidl
copy to core/java/android/hardware/radio/IAnnouncementListener.aidl
index 5385738..b4d974a 100644
--- a/core/java/android/security/keystore/recovery/KeychainProtectionParams.aidl
+++ b/core/java/android/hardware/radio/IAnnouncementListener.aidl
@@ -1,5 +1,5 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
+/**
+ * Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,11 @@
* limitations under the License.
*/
-package android.security.keystore.recovery;
+package android.hardware.radio;
-/* @hide */
-parcelable KeychainProtectionParams;
+import android.hardware.radio.Announcement;
+
+/** {@hide} */
+oneway interface IAnnouncementListener {
+ void onListUpdated(in List<Announcement> activeAnnouncements);
+}
diff --git a/core/java/android/security/keystore/recovery/KeychainProtectionParams.aidl b/core/java/android/hardware/radio/ICloseHandle.aidl
similarity index 78%
copy from core/java/android/security/keystore/recovery/KeychainProtectionParams.aidl
copy to core/java/android/hardware/radio/ICloseHandle.aidl
index 5385738..576c03b 100644
--- a/core/java/android/security/keystore/recovery/KeychainProtectionParams.aidl
+++ b/core/java/android/hardware/radio/ICloseHandle.aidl
@@ -1,5 +1,5 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
+/**
+ * Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package android.security.keystore.recovery;
+package android.hardware.radio;
-/* @hide */
-parcelable KeychainProtectionParams;
+/** {@hide} */
+interface ICloseHandle {
+ void close();
+}
diff --git a/core/java/android/hardware/radio/IRadioService.aidl b/core/java/android/hardware/radio/IRadioService.aidl
index c43fd26..9349cf7 100644
--- a/core/java/android/hardware/radio/IRadioService.aidl
+++ b/core/java/android/hardware/radio/IRadioService.aidl
@@ -16,6 +16,8 @@
package android.hardware.radio;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
@@ -30,4 +32,7 @@
ITuner openTuner(int moduleId, in RadioManager.BandConfig bandConfig, boolean withAudio,
in ITunerCallback callback);
+
+ ICloseHandle addAnnouncementListener(in int[] enabledTypes,
+ in IAnnouncementListener listener);
}
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 0cf7605..0294a29 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -27,6 +27,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
@@ -363,6 +364,38 @@
}
/**
+ * Creates an equivalent ProgramSelector with a given secondary identifier preferred.
+ *
+ * Used to point to a specific physical identifier for technologies that may broadcast the same
+ * program on different channels. For example, with a DAB program broadcasted over multiple
+ * ensembles, the radio hardware may select the one with the strongest signal. The UI may select
+ * preferred ensemble though, so the radio hardware may try to use it in the first place.
+ *
+ * This is a best-effort hint for the tuner, not a guaranteed behavior.
+ *
+ * Setting the given secondary identifier as preferred means filtering out other secondary
+ * identifiers of its type and adding it to the list.
+ *
+ * @param preferred preferred secondary identifier
+ * @return a new ProgramSelector with a given secondary identifier preferred
+ */
+ public @NonNull ProgramSelector withSecondaryPreferred(@NonNull Identifier preferred) {
+ int preferredType = preferred.getType();
+ Identifier[] secondaryIds = Stream.concat(
+ // remove other identifiers of that type
+ Arrays.stream(mSecondaryIds).filter(id -> id.getType() != preferredType),
+ // add preferred identifier instead
+ Stream.of(preferred)).toArray(Identifier[]::new);
+
+ return new ProgramSelector(
+ mProgramType,
+ mPrimaryId,
+ secondaryIds,
+ mVendorIds
+ );
+ }
+
+ /**
* Builds new ProgramSelector for AM/FM frequency.
*
* @param band the band.
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 56668ac..b00f603 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -17,8 +17,10 @@
package android.hardware.radio;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -32,14 +34,19 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.stream.Collectors;
/**
@@ -1380,35 +1387,44 @@
};
}
- /** Radio program information returned by
- * {@link RadioTuner#getProgramInformation(RadioManager.ProgramInfo[])} */
+ /** Radio program information. */
public static class ProgramInfo implements Parcelable {
- // sourced from hardware/interfaces/broadcastradio/1.1/types.hal
+ // sourced from hardware/interfaces/broadcastradio/2.0/types.hal
private static final int FLAG_LIVE = 1 << 0;
private static final int FLAG_MUTED = 1 << 1;
private static final int FLAG_TRAFFIC_PROGRAM = 1 << 2;
private static final int FLAG_TRAFFIC_ANNOUNCEMENT = 1 << 3;
+ private static final int FLAG_TUNED = 1 << 4;
+ private static final int FLAG_STEREO = 1 << 5;
@NonNull private final ProgramSelector mSelector;
- private final boolean mTuned; // TODO(b/69958777): replace with mFlags
- private final boolean mStereo;
- private final boolean mDigital;
- private final int mFlags;
- private final int mSignalStrength;
- private final RadioMetadata mMetadata;
+ @Nullable private final ProgramSelector.Identifier mLogicallyTunedTo;
+ @Nullable private final ProgramSelector.Identifier mPhysicallyTunedTo;
+ @NonNull private final Collection<ProgramSelector.Identifier> mRelatedContent;
+ private final int mInfoFlags;
+ private final int mSignalQuality;
+ @Nullable private final RadioMetadata mMetadata;
@NonNull private final Map<String, String> mVendorInfo;
/** @hide */
- public ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo,
- boolean digital, int signalStrength, RadioMetadata metadata, int flags,
- Map<String, String> vendorInfo) {
- mSelector = selector;
- mTuned = tuned;
- mStereo = stereo;
- mDigital = digital;
- mFlags = flags;
- mSignalStrength = signalStrength;
+ public ProgramInfo(@NonNull ProgramSelector selector,
+ @Nullable ProgramSelector.Identifier logicallyTunedTo,
+ @Nullable ProgramSelector.Identifier physicallyTunedTo,
+ @Nullable Collection<ProgramSelector.Identifier> relatedContent,
+ int infoFlags, int signalQuality, @Nullable RadioMetadata metadata,
+ @Nullable Map<String, String> vendorInfo) {
+ mSelector = Objects.requireNonNull(selector);
+ mLogicallyTunedTo = logicallyTunedTo;
+ mPhysicallyTunedTo = physicallyTunedTo;
+ if (relatedContent == null) {
+ mRelatedContent = Collections.emptyList();
+ } else {
+ Preconditions.checkCollectionElementsNotNull(relatedContent, "relatedContent");
+ mRelatedContent = relatedContent;
+ }
+ mInfoFlags = infoFlags;
+ mSignalQuality = signalQuality;
mMetadata = metadata;
mVendorInfo = (vendorInfo == null) ? new HashMap<>() : vendorInfo;
}
@@ -1422,6 +1438,51 @@
return mSelector;
}
+ /**
+ * Identifier currently used for program selection.
+ *
+ * This identifier can be used to determine which technology is
+ * currently being used for reception.
+ *
+ * Some program selectors contain tuning information for different radio
+ * technologies (i.e. FM RDS and DAB). For example, user may tune using
+ * a ProgramSelector with RDS_PI primary identifier, but the tuner hardware
+ * may choose to use DAB technology to make actual tuning. This identifier
+ * must reflect that.
+ */
+ public @Nullable ProgramSelector.Identifier getLogicallyTunedTo() {
+ return mLogicallyTunedTo;
+ }
+
+ /**
+ * Identifier currently used by hardware to physically tune to a channel.
+ *
+ * Some radio technologies broadcast the same program on multiple channels,
+ * i.e. with RDS AF the same program may be broadcasted on multiple
+ * alternative frequencies; the same DAB program may be broadcast on
+ * multiple ensembles. This identifier points to the channel to which the
+ * radio hardware is physically tuned to.
+ */
+ public @Nullable ProgramSelector.Identifier getPhysicallyTunedTo() {
+ return mPhysicallyTunedTo;
+ }
+
+ /**
+ * Primary identifiers of related contents.
+ *
+ * Some radio technologies provide pointers to other programs that carry
+ * related content (i.e. DAB soft-links). This field is a list of pointers
+ * to other programs on the program list.
+ *
+ * Please note, that these identifiers does not have to exist on the program
+ * list - i.e. DAB tuner may provide information on FM RDS alternatives
+ * despite not supporting FM RDS. If the system has multiple tuners, another
+ * one may have it on its list.
+ */
+ public @Nullable Collection<ProgramSelector.Identifier> getRelatedContent() {
+ return mRelatedContent;
+ }
+
/** Main channel expressed in units according to band type.
* Currently all defined band types express channels as frequency in kHz
* @return the program channel
@@ -1456,19 +1517,28 @@
* @return {@code true} if currently tuned, {@code false} otherwise.
*/
public boolean isTuned() {
- return mTuned;
+ return (mInfoFlags & FLAG_TUNED) != 0;
}
+
/** {@code true} if the received program is stereo
* @return {@code true} if stereo, {@code false} otherwise.
*/
public boolean isStereo() {
- return mStereo;
+ return (mInfoFlags & FLAG_STEREO) != 0;
}
+
/** {@code true} if the received program is digital (e.g HD radio)
* @return {@code true} if digital, {@code false} otherwise.
+ * @deprecated Use {@link getLogicallyTunedTo()} instead.
*/
+ @Deprecated
public boolean isDigital() {
- return mDigital;
+ ProgramSelector.Identifier id = mLogicallyTunedTo;
+ if (id == null) id = mSelector.getPrimaryId();
+
+ int type = id.getType();
+ return (type != ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY
+ && type != ProgramSelector.IDENTIFIER_TYPE_RDS_PI);
}
/**
@@ -1477,7 +1547,7 @@
* usually targetted at reduced latency.
*/
public boolean isLive() {
- return (mFlags & FLAG_LIVE) != 0;
+ return (mInfoFlags & FLAG_LIVE) != 0;
}
/**
@@ -1487,7 +1557,7 @@
* It does NOT mean the user has muted audio.
*/
public boolean isMuted() {
- return (mFlags & FLAG_MUTED) != 0;
+ return (mInfoFlags & FLAG_MUTED) != 0;
}
/**
@@ -1495,7 +1565,7 @@
* regularily.
*/
public boolean isTrafficProgram() {
- return (mFlags & FLAG_TRAFFIC_PROGRAM) != 0;
+ return (mInfoFlags & FLAG_TRAFFIC_PROGRAM) != 0;
}
/**
@@ -1503,15 +1573,18 @@
* at the very moment.
*/
public boolean isTrafficAnnouncementActive() {
- return (mFlags & FLAG_TRAFFIC_ANNOUNCEMENT) != 0;
+ return (mInfoFlags & FLAG_TRAFFIC_ANNOUNCEMENT) != 0;
}
- /** Signal strength indicator from 0 (no signal) to 100 (excellent)
- * @return the signal strength indication.
+ /**
+ * Signal quality (as opposed to the name) indication from 0 (no signal)
+ * to 100 (excellent)
+ * @return the signal quality indication.
*/
public int getSignalStrength() {
- return mSignalStrength;
+ return mSignalQuality;
}
+
/** Metadata currently received from this station.
* null if no metadata have been received
* @return current meta data received from this program.
@@ -1535,17 +1608,13 @@
}
private ProgramInfo(Parcel in) {
- mSelector = in.readParcelable(null);
- mTuned = in.readByte() == 1;
- mStereo = in.readByte() == 1;
- mDigital = in.readByte() == 1;
- mSignalStrength = in.readInt();
- if (in.readByte() == 1) {
- mMetadata = RadioMetadata.CREATOR.createFromParcel(in);
- } else {
- mMetadata = null;
- }
- mFlags = in.readInt();
+ mSelector = Objects.requireNonNull(in.readTypedObject(ProgramSelector.CREATOR));
+ mLogicallyTunedTo = in.readTypedObject(ProgramSelector.Identifier.CREATOR);
+ mPhysicallyTunedTo = in.readTypedObject(ProgramSelector.Identifier.CREATOR);
+ mRelatedContent = in.createTypedArrayList(ProgramSelector.Identifier.CREATOR);
+ mInfoFlags = in.readInt();
+ mSignalQuality = in.readInt();
+ mMetadata = in.readTypedObject(RadioMetadata.CREATOR);
mVendorInfo = Utils.readStringMap(in);
}
@@ -1562,18 +1631,13 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(mSelector, 0);
- dest.writeByte((byte)(mTuned ? 1 : 0));
- dest.writeByte((byte)(mStereo ? 1 : 0));
- dest.writeByte((byte)(mDigital ? 1 : 0));
- dest.writeInt(mSignalStrength);
- if (mMetadata == null) {
- dest.writeByte((byte)0);
- } else {
- dest.writeByte((byte)1);
- mMetadata.writeToParcel(dest, flags);
- }
- dest.writeInt(mFlags);
+ dest.writeTypedObject(mSelector, flags);
+ dest.writeTypedObject(mLogicallyTunedTo, flags);
+ dest.writeTypedObject(mPhysicallyTunedTo, flags);
+ Utils.writeTypedCollection(dest, mRelatedContent);
+ dest.writeInt(mInfoFlags);
+ dest.writeInt(mSignalQuality);
+ dest.writeTypedObject(mMetadata, flags);
Utils.writeStringMap(dest, mVendorInfo);
}
@@ -1584,52 +1648,38 @@
@Override
public String toString() {
- return "ProgramInfo [mSelector=" + mSelector
- + ", mTuned=" + mTuned + ", mStereo=" + mStereo + ", mDigital=" + mDigital
- + ", mFlags=" + mFlags + ", mSignalStrength=" + mSignalStrength
- + ((mMetadata == null) ? "" : (", mMetadata=" + mMetadata.toString()))
+ return "ProgramInfo"
+ + " [selector=" + mSelector
+ + ", logicallyTunedTo=" + Objects.toString(mLogicallyTunedTo)
+ + ", physicallyTunedTo=" + Objects.toString(mPhysicallyTunedTo)
+ + ", relatedContent=" + mRelatedContent.size()
+ + ", infoFlags=" + mInfoFlags
+ + ", mSignalQuality=" + mSignalQuality
+ + ", mMetadata=" + Objects.toString(mMetadata)
+ "]";
}
@Override
public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + mSelector.hashCode();
- result = prime * result + (mTuned ? 1 : 0);
- result = prime * result + (mStereo ? 1 : 0);
- result = prime * result + (mDigital ? 1 : 0);
- result = prime * result + mFlags;
- result = prime * result + mSignalStrength;
- result = prime * result + ((mMetadata == null) ? 0 : mMetadata.hashCode());
- result = prime * result + mVendorInfo.hashCode();
- return result;
+ return Objects.hash(mSelector, mLogicallyTunedTo, mPhysicallyTunedTo,
+ mRelatedContent, mInfoFlags, mSignalQuality, mMetadata, mVendorInfo);
}
@Override
public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (!(obj instanceof ProgramInfo))
- return false;
+ if (this == obj) return true;
+ if (!(obj instanceof ProgramInfo)) return false;
ProgramInfo other = (ProgramInfo) obj;
- if (!mSelector.equals(other.getSelector())) return false;
- if (mTuned != other.isTuned())
- return false;
- if (mStereo != other.isStereo())
- return false;
- if (mDigital != other.isDigital())
- return false;
- if (mFlags != other.mFlags)
- return false;
- if (mSignalStrength != other.getSignalStrength())
- return false;
- if (mMetadata == null) {
- if (other.getMetadata() != null)
- return false;
- } else if (!mMetadata.equals(other.getMetadata()))
- return false;
- if (!mVendorInfo.equals(other.mVendorInfo)) return false;
+
+ if (!Objects.equals(mSelector, other.mSelector)) return false;
+ if (!Objects.equals(mLogicallyTunedTo, other.mLogicallyTunedTo)) return false;
+ if (!Objects.equals(mPhysicallyTunedTo, other.mPhysicallyTunedTo)) return false;
+ if (!Objects.equals(mRelatedContent, other.mRelatedContent)) return false;
+ if (mInfoFlags != other.mInfoFlags) return false;
+ if (mSignalQuality != other.mSignalQuality) return false;
+ if (!Objects.equals(mMetadata, other.mMetadata)) return false;
+ if (!Objects.equals(mVendorInfo, other.mVendorInfo)) return false;
+
return true;
}
}
@@ -1713,6 +1763,68 @@
config != null ? config.getType() : BAND_INVALID);
}
+ private final Map<Announcement.OnListUpdatedListener, ICloseHandle> mAnnouncementListeners =
+ new HashMap<>();
+
+ /**
+ * Adds new announcement listener.
+ *
+ * @param enabledAnnouncementTypes a set of announcement types to listen to
+ * @param listener announcement listener
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ public void addAnnouncementListener(@NonNull Set<Integer> enabledAnnouncementTypes,
+ @NonNull Announcement.OnListUpdatedListener listener) {
+ addAnnouncementListener(cmd -> cmd.run(), enabledAnnouncementTypes, listener);
+ }
+
+ /**
+ * Adds new announcement listener with executor.
+ *
+ * @param executor the executor
+ * @param enabledAnnouncementTypes a set of announcement types to listen to
+ * @param listener announcement listener
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ public void addAnnouncementListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Set<Integer> enabledAnnouncementTypes,
+ @NonNull Announcement.OnListUpdatedListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ int[] types = enabledAnnouncementTypes.stream().mapToInt(Integer::intValue).toArray();
+ IAnnouncementListener listenerIface = new IAnnouncementListener.Stub() {
+ public void onListUpdated(List<Announcement> activeAnnouncements) {
+ executor.execute(() -> listener.onListUpdated(activeAnnouncements));
+ }
+ };
+ synchronized (mAnnouncementListeners) {
+ ICloseHandle closeHandle = null;
+ try {
+ closeHandle = mService.addAnnouncementListener(types, listenerIface);
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ Objects.requireNonNull(closeHandle);
+ ICloseHandle oldCloseHandle = mAnnouncementListeners.put(listener, closeHandle);
+ if (oldCloseHandle != null) Utils.close(oldCloseHandle);
+ }
+ }
+
+ /**
+ * Removes previously registered announcement listener.
+ *
+ * @param listener announcement listener, previously registered with
+ * {@link addAnnouncementListener}
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ public void removeAnnouncementListener(@NonNull Announcement.OnListUpdatedListener listener) {
+ Objects.requireNonNull(listener);
+ synchronized (mAnnouncementListeners) {
+ ICloseHandle closeHandle = mAnnouncementListeners.remove(listener);
+ if (closeHandle != null) Utils.close(closeHandle);
+ }
+ }
+
@NonNull private final Context mContext;
@NonNull private final IRadioService mService;
diff --git a/core/java/android/hardware/radio/Utils.java b/core/java/android/hardware/radio/Utils.java
index 09bf8fe..f1b5897 100644
--- a/core/java/android/hardware/radio/Utils.java
+++ b/core/java/android/hardware/radio/Utils.java
@@ -20,7 +20,10 @@
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -28,6 +31,8 @@
import java.util.Set;
final class Utils {
+ private static final String TAG = "BroadcastRadio.utils";
+
static void writeStringMap(@NonNull Parcel dest, @Nullable Map<String, String> map) {
if (map == null) {
dest.writeInt(0);
@@ -89,4 +94,25 @@
}
});
}
+
+ static <T extends Parcelable> void writeTypedCollection(@NonNull Parcel dest,
+ @Nullable Collection<T> coll) {
+ ArrayList<T> list = null;
+ if (coll != null) {
+ if (coll instanceof ArrayList) {
+ list = (ArrayList) coll;
+ } else {
+ list = new ArrayList<>(coll);
+ }
+ }
+ dest.writeTypedList(list);
+ }
+
+ static void close(ICloseHandle handle) {
+ try {
+ handle.close();
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl
index eeb30e2..3ce0283 100644
--- a/core/java/android/net/IIpSecService.aidl
+++ b/core/java/android/net/IIpSecService.aidl
@@ -21,6 +21,7 @@
import android.net.IpSecUdpEncapResponse;
import android.net.IpSecSpiResponse;
import android.net.IpSecTransformResponse;
+import android.net.IpSecTunnelInterfaceResponse;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
@@ -39,11 +40,29 @@
void closeUdpEncapsulationSocket(int resourceId);
+ IpSecTunnelInterfaceResponse createTunnelInterface(
+ in String localAddr,
+ in String remoteAddr,
+ in Network underlyingNetwork,
+ in IBinder binder);
+
+ void addAddressToTunnelInterface(
+ int tunnelResourceId,
+ String localAddr);
+
+ void removeAddressFromTunnelInterface(
+ int tunnelResourceId,
+ String localAddr);
+
+ void deleteTunnelInterface(int resourceId);
+
IpSecTransformResponse createTransform(in IpSecConfig c, in IBinder binder);
void deleteTransform(int transformId);
void applyTransportModeTransform(in ParcelFileDescriptor socket, int direction, int transformId);
+ void applyTunnelModeTransform(int tunnelResourceId, int direction, int transformResourceId);
+
void removeTransportModeTransforms(in ParcelFileDescriptor socket);
}
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index 80b0af3..6a262e2 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -65,6 +65,10 @@
// An interval, in seconds between the NattKeepalive packets
private int mNattKeepaliveInterval;
+ // XFRM mark and mask
+ private int mMarkValue;
+ private int mMarkMask;
+
/** Set the mode for this IPsec transform */
public void setMode(int mode) {
mMode = mode;
@@ -121,6 +125,14 @@
mNattKeepaliveInterval = interval;
}
+ public void setMarkValue(int mark) {
+ mMarkValue = mark;
+ }
+
+ public void setMarkMask(int mask) {
+ mMarkMask = mask;
+ }
+
// Transport or Tunnel
public int getMode() {
return mMode;
@@ -170,6 +182,14 @@
return mNattKeepaliveInterval;
}
+ public int getMarkValue() {
+ return mMarkValue;
+ }
+
+ public int getMarkMask() {
+ return mMarkMask;
+ }
+
// Parcelable Methods
@Override
@@ -191,6 +211,8 @@
out.writeInt(mEncapSocketResourceId);
out.writeInt(mEncapRemotePort);
out.writeInt(mNattKeepaliveInterval);
+ out.writeInt(mMarkValue);
+ out.writeInt(mMarkMask);
}
@VisibleForTesting
@@ -212,6 +234,8 @@
mEncapSocketResourceId = in.readInt();
mEncapRemotePort = in.readInt();
mNattKeepaliveInterval = in.readInt();
+ mMarkValue = in.readInt();
+ mMarkMask = in.readInt();
}
@Override
@@ -242,6 +266,10 @@
.append(mAuthentication)
.append(", mAuthenticatedEncryption=")
.append(mAuthenticatedEncryption)
+ .append(", mMarkValue=")
+ .append(mMarkValue)
+ .append(", mMarkMask=")
+ .append(mMarkMask)
.append("}");
return strBuilder.toString();
@@ -275,6 +303,8 @@
&& IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption)
&& IpSecAlgorithm.equals(
lhs.mAuthenticatedEncryption, rhs.mAuthenticatedEncryption)
- && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication));
+ && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication)
+ && lhs.mMarkValue == rhs.mMarkValue
+ && lhs.mMarkMask == rhs.mMarkMask);
}
}
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index f04f03f6..24a078f 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -685,7 +685,30 @@
mLocalAddress = localAddress;
mRemoteAddress = remoteAddress;
mUnderlyingNetwork = underlyingNetwork;
- // TODO: Call IpSecService
+
+ try {
+ IpSecTunnelInterfaceResponse result =
+ mService.createTunnelInterface(
+ localAddress.getHostAddress(),
+ remoteAddress.getHostAddress(),
+ underlyingNetwork,
+ new Binder());
+ switch (result.status) {
+ case Status.OK:
+ break;
+ case Status.RESOURCE_UNAVAILABLE:
+ throw new ResourceUnavailableException(
+ "No more tunnel interfaces may be allocated by this requester.");
+ default:
+ throw new RuntimeException(
+ "Unknown status returned by IpSecService: " + result.status);
+ }
+ mResourceId = result.resourceId;
+ mInterfaceName = result.interfaceName;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mCloseGuard.open("constructor");
}
/**
@@ -697,12 +720,12 @@
*/
@Override
public void close() {
- // try {
- // TODO: Call IpSecService
- mResourceId = INVALID_RESOURCE_ID;
- // } catch (RemoteException e) {
- // throw e.rethrowFromSystemServer();
- // }
+ try {
+ mService.deleteTunnelInterface(mResourceId);
+ mResourceId = INVALID_RESOURCE_ID;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
mCloseGuard.close();
}
@@ -714,11 +737,20 @@
}
close();
}
+
+ /** @hide */
+ @VisibleForTesting
+ public int getResourceId() {
+ return mResourceId;
+ }
}
/**
* Create a new IpSecTunnelInterface as a local endpoint for tunneled IPsec traffic.
*
+ * <p>An application that creates tunnels is responsible for cleaning up the tunnel when the
+ * underlying network goes away, and the onLost() callback is received.
+ *
* @param localAddress The local addres of the tunnel
* @param remoteAddress The local addres of the tunnel
* @param underlyingNetwork the {@link Network} that will carry traffic for this tunnel.
@@ -748,9 +780,14 @@
* @hide
*/
@SystemApi
- void applyTunnelModeTransform(IpSecTunnelInterface tunnel, int direction,
+ public void applyTunnelModeTransform(IpSecTunnelInterface tunnel, int direction,
IpSecTransform transform) throws IOException {
- // TODO: call IpSecService
+ try {
+ mService.applyTunnelModeTransform(
+ tunnel.getResourceId(), direction, transform.getResourceId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
* Construct an instance of IpSecManager within an application context.
diff --git a/core/java/android/security/keystore/recovery/KeychainSnapshot.aidl b/core/java/android/net/IpSecTunnelInterfaceResponse.aidl
similarity index 79%
copy from core/java/android/security/keystore/recovery/KeychainSnapshot.aidl
copy to core/java/android/net/IpSecTunnelInterfaceResponse.aidl
index 7822f39..7239221 100644
--- a/core/java/android/security/keystore/recovery/KeychainSnapshot.aidl
+++ b/core/java/android/net/IpSecTunnelInterfaceResponse.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.security.keystore.recovery;
+package android.net;
-/* @hide */
-parcelable KeychainSnapshot;
+/** @hide */
+parcelable IpSecTunnelInterfaceResponse;
diff --git a/core/java/android/net/IpSecTunnelInterfaceResponse.java b/core/java/android/net/IpSecTunnelInterfaceResponse.java
new file mode 100644
index 0000000..c23d831
--- /dev/null
+++ b/core/java/android/net/IpSecTunnelInterfaceResponse.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to return an IpSecTunnelInterface resource Id and and corresponding status
+ * from the IpSecService to an IpSecTunnelInterface object.
+ *
+ * @hide
+ */
+public final class IpSecTunnelInterfaceResponse implements Parcelable {
+ private static final String TAG = "IpSecTunnelInterfaceResponse";
+
+ public final int resourceId;
+ public final String interfaceName;
+ public final int status;
+ // Parcelable Methods
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(status);
+ out.writeInt(resourceId);
+ out.writeString(interfaceName);
+ }
+
+ public IpSecTunnelInterfaceResponse(int inStatus) {
+ if (inStatus == IpSecManager.Status.OK) {
+ throw new IllegalArgumentException("Valid status implies other args must be provided");
+ }
+ status = inStatus;
+ resourceId = IpSecManager.INVALID_RESOURCE_ID;
+ interfaceName = "";
+ }
+
+ public IpSecTunnelInterfaceResponse(int inStatus, int inResourceId, String inInterfaceName) {
+ status = inStatus;
+ resourceId = inResourceId;
+ interfaceName = inInterfaceName;
+ }
+
+ private IpSecTunnelInterfaceResponse(Parcel in) {
+ status = in.readInt();
+ resourceId = in.readInt();
+ interfaceName = in.readString();
+ }
+
+ public static final Parcelable.Creator<IpSecTunnelInterfaceResponse> CREATOR =
+ new Parcelable.Creator<IpSecTunnelInterfaceResponse>() {
+ public IpSecTunnelInterfaceResponse createFromParcel(Parcel in) {
+ return new IpSecTunnelInterfaceResponse(in);
+ }
+
+ public IpSecTunnelInterfaceResponse[] newArray(int size) {
+ return new IpSecTunnelInterfaceResponse[size];
+ }
+ };
+}
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 2dacf8f..52a2354 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -17,6 +17,7 @@
package android.net;
import android.content.Context;
+import android.net.ConnectivityManager.PacketKeepalive;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -26,7 +27,6 @@
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
-import android.net.ConnectivityManager.PacketKeepalive;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -101,20 +101,6 @@
public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4;
/**
- * Sent by the NetworkAgent to ConnectivityService to add new UID ranges
- * to be forced into this Network. For VPNs only.
- * obj = UidRange[] to forward
- */
- public static final int EVENT_UID_RANGES_ADDED = BASE + 5;
-
- /**
- * Sent by the NetworkAgent to ConnectivityService to remove UID ranges
- * from being forced into this Network. For VPNs only.
- * obj = UidRange[] to stop forwarding
- */
- public static final int EVENT_UID_RANGES_REMOVED = BASE + 6;
-
- /**
* Sent by ConnectivityService to the NetworkAgent to inform the agent of the
* networks status - whether we could use the network or could not, due to
* either a bad network configuration (no internet link) or captive portal.
@@ -390,22 +376,6 @@
}
/**
- * Called by the VPN code when it wants to add ranges of UIDs to be routed
- * through the VPN network.
- */
- public void addUidRanges(UidRange[] ranges) {
- queueOrSendMessage(EVENT_UID_RANGES_ADDED, ranges);
- }
-
- /**
- * Called by the VPN code when it wants to remove ranges of UIDs from being routed
- * through the VPN network.
- */
- public void removeUidRanges(UidRange[] ranges) {
- queueOrSendMessage(EVENT_UID_RANGES_REMOVED, ranges);
- }
-
- /**
* Called by the bearer to indicate this network was manually selected by the user.
* This should be called before the NetworkInfo is marked CONNECTED so that this
* Network can be given special treatment at that time. If {@code acceptUnvalidated} is
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 214ff64..1a4765b 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -20,6 +20,7 @@
import android.net.ConnectivityManager.NetworkCallback;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArraySet;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
@@ -29,6 +30,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
+import java.util.Set;
import java.util.StringJoiner;
/**
@@ -47,6 +49,7 @@
*/
public final class NetworkCapabilities implements Parcelable {
private static final String TAG = "NetworkCapabilities";
+ private static final int INVALID_UID = -1;
/**
* @hide
@@ -64,6 +67,8 @@
mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps;
mNetworkSpecifier = nc.mNetworkSpecifier;
mSignalStrength = nc.mSignalStrength;
+ mUids = nc.mUids;
+ mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid;
}
}
@@ -77,6 +82,8 @@
mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
mNetworkSpecifier = null;
mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
+ mUids = null;
+ mEstablishingVpnAppUid = INVALID_UID;
}
/**
@@ -619,6 +626,29 @@
}
/**
+ * UID of the app that manages this network, or INVALID_UID if none/unknown.
+ *
+ * This field keeps track of the UID of the app that created this network and is in charge
+ * of managing it. In the practice, it is used to store the UID of VPN apps so it is named
+ * accordingly, but it may be renamed if other mechanisms are offered for third party apps
+ * to create networks.
+ *
+ * Because this field is only used in the services side (and to avoid apps being able to
+ * set this to whatever they want), this field is not parcelled and will not be conserved
+ * across the IPC boundary.
+ * @hide
+ */
+ private int mEstablishingVpnAppUid = INVALID_UID;
+
+ /**
+ * Set the UID of the managing app.
+ * @hide
+ */
+ public void setEstablishingVpnAppUid(final int uid) {
+ mEstablishingVpnAppUid = uid;
+ }
+
+ /**
* Value indicating that link bandwidth is unspecified.
* @hide
*/
@@ -837,6 +867,174 @@
}
/**
+ * List of UIDs this network applies to. No restriction if null.
+ * <p>
+ * This is typically (and at this time, only) used by VPN. This network is only available to
+ * the UIDs in this list, and it is their default network. Apps in this list that wish to
+ * bypass the VPN can do so iff the VPN app allows them to or if they are privileged. If this
+ * member is null, then the network is not restricted by app UID. If it's an empty list, then
+ * it means nobody can use it.
+ * As a special exception, the app managing this network (as identified by its UID stored in
+ * mEstablishingVpnAppUid) can always see this network. This is embodied by a special check in
+ * satisfiedByUids. That still does not mean the network necessarily <strong>applies</strong>
+ * to the app that manages it as determined by #appliesToUid.
+ * <p>
+ * Please note that in principle a single app can be associated with multiple UIDs because
+ * each app will have a different UID when it's run as a different (macro-)user. A single
+ * macro user can only have a single active VPN app at any given time however.
+ * <p>
+ * Also please be aware this class does not try to enforce any normalization on this. Callers
+ * can only alter the UIDs by setting them wholesale : this class does not provide any utility
+ * to add or remove individual UIDs or ranges. If callers have any normalization needs on
+ * their own (like requiring sortedness or no overlap) they need to enforce it
+ * themselves. Some of the internal methods also assume this is normalized as in no adjacent
+ * or overlapping ranges are present.
+ *
+ * @hide
+ */
+ private Set<UidRange> mUids = null;
+
+ /**
+ * Convenience method to set the UIDs this network applies to to a single UID.
+ * @hide
+ */
+ public NetworkCapabilities setSingleUid(int uid) {
+ final ArraySet<UidRange> identity = new ArraySet<>(1);
+ identity.add(new UidRange(uid, uid));
+ setUids(identity);
+ return this;
+ }
+
+ /**
+ * Set the list of UIDs this network applies to.
+ * This makes a copy of the set so that callers can't modify it after the call.
+ * @hide
+ */
+ public NetworkCapabilities setUids(Set<UidRange> uids) {
+ if (null == uids) {
+ mUids = null;
+ } else {
+ mUids = new ArraySet<>(uids);
+ }
+ return this;
+ }
+
+ /**
+ * Get the list of UIDs this network applies to.
+ * This returns a copy of the set so that callers can't modify the original object.
+ * @hide
+ */
+ public Set<UidRange> getUids() {
+ return null == mUids ? null : new ArraySet<>(mUids);
+ }
+
+ /**
+ * Test whether this network applies to this UID.
+ * @hide
+ */
+ public boolean appliesToUid(int uid) {
+ if (null == mUids) return true;
+ for (UidRange range : mUids) {
+ if (range.contains(uid)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Tests if the set of UIDs that this network applies to is the same of the passed set of UIDs.
+ * <p>
+ * This test only checks whether equal range objects are in both sets. It will
+ * return false if the ranges are not exactly the same, even if the covered UIDs
+ * are for an equivalent result.
+ * <p>
+ * Note that this method is not very optimized, which is fine as long as it's not used very
+ * often.
+ * <p>
+ * nc is assumed nonnull.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public boolean equalsUids(NetworkCapabilities nc) {
+ Set<UidRange> comparedUids = nc.mUids;
+ if (null == comparedUids) return null == mUids;
+ if (null == mUids) return false;
+ // Make a copy so it can be mutated to check that all ranges in mUids
+ // also are in uids.
+ final Set<UidRange> uids = new ArraySet<>(mUids);
+ for (UidRange range : comparedUids) {
+ if (!uids.contains(range)) {
+ return false;
+ }
+ uids.remove(range);
+ }
+ return uids.isEmpty();
+ }
+
+ /**
+ * Test whether the passed NetworkCapabilities satisfies the UIDs this capabilities require.
+ *
+ * This method is called on the NetworkCapabilities embedded in a request with the
+ * capabilities of an available network. It checks whether all the UIDs from this listen
+ * (representing the UIDs that must have access to the network) are satisfied by the UIDs
+ * in the passed nc (representing the UIDs that this network is available to).
+ * <p>
+ * As a special exception, the UID that created the passed network (as represented by its
+ * mEstablishingVpnAppUid field) always satisfies a NetworkRequest requiring it (of LISTEN
+ * or REQUEST types alike), even if the network does not apply to it. That is so a VPN app
+ * can see its own network when it listens for it.
+ * <p>
+ * nc is assumed nonnull. Else, NPE.
+ * @see #appliesToUid
+ * @hide
+ */
+ public boolean satisfiedByUids(NetworkCapabilities nc) {
+ if (null == nc.mUids) return true; // The network satisfies everything.
+ if (null == mUids) return false; // Not everything allowed but requires everything
+ for (UidRange requiredRange : mUids) {
+ if (requiredRange.contains(nc.mEstablishingVpnAppUid)) return true;
+ if (!nc.appliesToUidRange(requiredRange)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns whether this network applies to the passed ranges.
+ * This assumes that to apply, the passed range has to be entirely contained
+ * within one of the ranges this network applies to. If the ranges are not normalized,
+ * this method may return false even though all required UIDs are covered because no
+ * single range contained them all.
+ * @hide
+ */
+ @VisibleForTesting
+ public boolean appliesToUidRange(UidRange requiredRange) {
+ if (null == mUids) return true;
+ for (UidRange uidRange : mUids) {
+ if (uidRange.containsRange(requiredRange)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Combine the UIDs this network currently applies to with the UIDs the passed
+ * NetworkCapabilities apply to.
+ * nc is assumed nonnull.
+ */
+ private void combineUids(NetworkCapabilities nc) {
+ if (null == nc.mUids || null == mUids) {
+ mUids = null;
+ return;
+ }
+ mUids.addAll(nc.mUids);
+ }
+
+ /**
* Combine a set of Capabilities to this one. Useful for coming up with the complete set
* @hide
*/
@@ -846,6 +1044,7 @@
combineLinkBandwidths(nc);
combineSpecifiers(nc);
combineSignalStrength(nc);
+ combineUids(nc);
}
/**
@@ -858,12 +1057,13 @@
* @hide
*/
private boolean satisfiedByNetworkCapabilities(NetworkCapabilities nc, boolean onlyImmutable) {
- return (nc != null &&
- satisfiedByNetCapabilities(nc, onlyImmutable) &&
- satisfiedByTransportTypes(nc) &&
- (onlyImmutable || satisfiedByLinkBandwidths(nc)) &&
- satisfiedBySpecifier(nc) &&
- (onlyImmutable || satisfiedBySignalStrength(nc)));
+ return (nc != null
+ && satisfiedByNetCapabilities(nc, onlyImmutable)
+ && satisfiedByTransportTypes(nc)
+ && (onlyImmutable || satisfiedByLinkBandwidths(nc))
+ && satisfiedBySpecifier(nc)
+ && (onlyImmutable || satisfiedBySignalStrength(nc))
+ && (onlyImmutable || satisfiedByUids(nc)));
}
/**
@@ -944,24 +1144,26 @@
@Override
public boolean equals(Object obj) {
if (obj == null || (obj instanceof NetworkCapabilities == false)) return false;
- NetworkCapabilities that = (NetworkCapabilities)obj;
- return (equalsNetCapabilities(that) &&
- equalsTransportTypes(that) &&
- equalsLinkBandwidths(that) &&
- equalsSignalStrength(that) &&
- equalsSpecifier(that));
+ NetworkCapabilities that = (NetworkCapabilities) obj;
+ return (equalsNetCapabilities(that)
+ && equalsTransportTypes(that)
+ && equalsLinkBandwidths(that)
+ && equalsSignalStrength(that)
+ && equalsSpecifier(that)
+ && equalsUids(that));
}
@Override
public int hashCode() {
- return ((int)(mNetworkCapabilities & 0xFFFFFFFF) +
- ((int)(mNetworkCapabilities >> 32) * 3) +
- ((int)(mTransportTypes & 0xFFFFFFFF) * 5) +
- ((int)(mTransportTypes >> 32) * 7) +
- (mLinkUpBandwidthKbps * 11) +
- (mLinkDownBandwidthKbps * 13) +
- Objects.hashCode(mNetworkSpecifier) * 17 +
- (mSignalStrength * 19));
+ return ((int) (mNetworkCapabilities & 0xFFFFFFFF)
+ + ((int) (mNetworkCapabilities >> 32) * 3)
+ + ((int) (mTransportTypes & 0xFFFFFFFF) * 5)
+ + ((int) (mTransportTypes >> 32) * 7)
+ + (mLinkUpBandwidthKbps * 11)
+ + (mLinkDownBandwidthKbps * 13)
+ + Objects.hashCode(mNetworkSpecifier) * 17
+ + (mSignalStrength * 19)
+ + Objects.hashCode(mUids) * 23);
}
@Override
@@ -976,6 +1178,7 @@
dest.writeInt(mLinkDownBandwidthKbps);
dest.writeParcelable((Parcelable) mNetworkSpecifier, flags);
dest.writeInt(mSignalStrength);
+ dest.writeArraySet(new ArraySet<>(mUids));
}
public static final Creator<NetworkCapabilities> CREATOR =
@@ -990,6 +1193,8 @@
netCap.mLinkDownBandwidthKbps = in.readInt();
netCap.mNetworkSpecifier = in.readParcelable(null);
netCap.mSignalStrength = in.readInt();
+ netCap.mUids = (ArraySet<UidRange>) in.readArraySet(
+ null /* ClassLoader, null for default */);
return netCap;
}
@Override
@@ -1022,7 +1227,12 @@
String signalStrength = (hasSignalStrength() ? " SignalStrength: " + mSignalStrength : "");
- return "[" + transports + capabilities + upBand + dnBand + specifier + signalStrength + "]";
+ String uids = (null != mUids ? " Uids: <" + mUids + ">" : "");
+
+ String establishingAppUid = " EstablishingAppUid: " + mEstablishingVpnAppUid;
+
+ return "[" + transports + capabilities + upBand + dnBand + specifier + signalStrength
+ + uids + establishingAppUid + "]";
}
/** @hide */
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 48f5684..fc78861 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -894,6 +894,14 @@
/**
* P.
+ *
+ * <p>Applications targeting this or a later release will get these
+ * new changes in behavior:</p>
+ * <ul>
+ * <li>{@link android.app.Service#startForeground Service.startForeground} requires
+ * that apps hold the permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE}.</li>
+ * </ul>
*/
public static final int P = CUR_DEVELOPMENT; // STOPSHIP Replace with the real version.
}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index b1794a6..62731e8 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -292,6 +292,16 @@
}
/** {@hide} */
+ public static File getDataVendorCeDirectory(int userId) {
+ return buildPath(getDataDirectory(), "vendor_ce", String.valueOf(userId));
+ }
+
+ /** {@hide} */
+ public static File getDataVendorDeDirectory(int userId) {
+ return buildPath(getDataDirectory(), "vendor_de", String.valueOf(userId));
+ }
+
+ /** {@hide} */
public static File getProfileSnapshotPath(String packageName, String codePath) {
return buildPath(buildPath(getDataDirectory(), "misc", "profiles", "ref", packageName,
"primary.prof.snapshot"));
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 5c5e351..fc88e90 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -202,7 +202,8 @@
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
- "Can't create handler inside thread that has not called Looper.prepare()");
+ "Can't create handler inside thread " + Thread.currentThread()
+ + " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 3798a5e..61172e1 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -23,6 +23,7 @@
import android.annotation.SystemService;
import android.content.Context;
import android.util.Log;
+import android.util.proto.ProtoOutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1652,6 +1653,21 @@
}
}
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ synchronized (mToken) {
+ final long token = proto.start(fieldId);
+ proto.write(PowerManagerProto.WakeLockProto.HEX_STRING,
+ Integer.toHexString(System.identityHashCode(this)));
+ proto.write(PowerManagerProto.WakeLockProto.HELD, mHeld);
+ proto.write(PowerManagerProto.WakeLockProto.INTERNAL_COUNT, mInternalCount);
+ if (mWorkSource != null) {
+ mWorkSource.writeToProto(proto, PowerManagerProto.WakeLockProto.WORK_SOURCE);
+ }
+ proto.end(token);
+ }
+ }
+
/**
* Wraps a Runnable such that this method immediately acquires the wake lock and then
* once the Runnable is done the wake lock is released.
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 3ef0961..c7d89b0 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -71,6 +71,24 @@
}
/**
+ * Converts platform constants to proto enums.
+ */
+ public static int wakefulnessToProtoEnum(int wakefulness) {
+ switch (wakefulness) {
+ case WAKEFULNESS_ASLEEP:
+ return PowerManagerInternalProto.WAKEFULNESS_ASLEEP;
+ case WAKEFULNESS_AWAKE:
+ return PowerManagerInternalProto.WAKEFULNESS_AWAKE;
+ case WAKEFULNESS_DREAMING:
+ return PowerManagerInternalProto.WAKEFULNESS_DREAMING;
+ case WAKEFULNESS_DOZING:
+ return PowerManagerInternalProto.WAKEFULNESS_DOZING;
+ default:
+ return wakefulness;
+ }
+ }
+
+ /**
* Returns true if the wakefulness state represents an interactive state
* as defined by {@link android.os.PowerManager#isInteractive}.
*/
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 7683880..6833908 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -574,6 +574,14 @@
}
/**
+ * Returns whether the given uid belongs to a system core component or not.
+ * @hide
+ */
+ public static boolean isCoreUid(int uid) {
+ return UserHandle.isCore(uid);
+ }
+
+ /**
* Returns whether the given uid belongs to an application.
* @param uid A kernel uid.
* @return Whether the uid corresponds to an application sandbox running in
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 6381b56..5be72bc 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -126,7 +126,10 @@
return getAppId(uid1) == getAppId(uid2);
}
- /** @hide */
+ /**
+ * Whether a UID is an "isolated" UID.
+ * @hide
+ */
public static boolean isIsolated(int uid) {
if (uid > 0) {
final int appId = getAppId(uid);
@@ -136,7 +139,11 @@
}
}
- /** @hide */
+ /**
+ * Whether a UID belongs to a regular app. *Note* "Not a regular app" does not mean
+ * "it's system", because of isolated UIDs. Use {@link #isCore} for that.
+ * @hide
+ */
public static boolean isApp(int uid) {
if (uid > 0) {
final int appId = getAppId(uid);
@@ -147,6 +154,19 @@
}
/**
+ * Whether a UID belongs to a system core component or not.
+ * @hide
+ */
+ public static boolean isCore(int uid) {
+ if (uid > 0) {
+ final int appId = getAppId(uid);
+ return appId < Process.FIRST_APPLICATION_UID;
+ } else {
+ return false;
+ }
+ }
+
+ /**
* Returns the user for a given uid.
* @param uid A uid for an application running in a particular user.
* @return A {@link UserHandle} for that user.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 228a86d..daf6bd5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1479,6 +1479,21 @@
public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE =
"android.settings.REQUEST_SET_AUTOFILL_SERVICE";
+ /**
+ * Activity Action: Show screen for controlling which apps have access on volume directories.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ * <p>
+ * Applications typically use this action to ask the user to revert the "Do not ask again"
+ * status of directory access requests made by
+ * {@link android.os.storage.StorageVolume#createAccessIntent(String)}.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_STORAGE_VOLUME_ACCESS_SETTINGS =
+ "android.settings.STORAGE_VOLUME_ACCESS_SETTINGS";
+
// End of Intent actions for Settings
/**
@@ -5486,37 +5501,54 @@
* Note: do not rely on this value being present in settings.db or on ContentObserver
* notifications for the corresponding Uri. Use {@link LocationManager#MODE_CHANGED_ACTION}
* to receive changes in this value.
+ *
+ * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
+ * get the status of a location provider, use
+ * {@link LocationManager#isProviderEnabled(String)}.
*/
+ @Deprecated
public static final String LOCATION_MODE = "location_mode";
- /**
- * Stores the previous location mode when {@link #LOCATION_MODE} is set to
- * {@link #LOCATION_MODE_OFF}
- * @hide
- */
- public static final String LOCATION_PREVIOUS_MODE = "location_previous_mode";
/**
- * Sets all location providers to the previous states before location was turned off.
- * @hide
- */
- public static final int LOCATION_MODE_PREVIOUS = -1;
- /**
* Location access disabled.
+ *
+ * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
+ * get the status of a location provider, use
+ * {@link LocationManager#isProviderEnabled(String)}.
*/
+ @Deprecated
public static final int LOCATION_MODE_OFF = 0;
+
/**
* Network Location Provider disabled, but GPS and other sensors enabled.
+ *
+ * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
+ * get the status of a location provider, use
+ * {@link LocationManager#isProviderEnabled(String)}.
*/
+ @Deprecated
public static final int LOCATION_MODE_SENSORS_ONLY = 1;
+
/**
* Reduced power usage, such as limiting the number of GPS updates per hour. Requests
* with {@link android.location.Criteria#POWER_HIGH} may be downgraded to
* {@link android.location.Criteria#POWER_MEDIUM}.
+ *
+ * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
+ * get the status of a location provider, use
+ * {@link LocationManager#isProviderEnabled(String)}.
*/
+ @Deprecated
public static final int LOCATION_MODE_BATTERY_SAVING = 2;
+
/**
* Best-effort location computation allowed.
+ *
+ * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
+ * get the status of a location provider, use
+ * {@link LocationManager#isProviderEnabled(String)}.
*/
+ @Deprecated
public static final int LOCATION_MODE_HIGH_ACCURACY = 3;
/**
@@ -7570,6 +7602,13 @@
public static final String BACKUP_MANAGER_CONSTANTS = "backup_manager_constants";
/**
+ * Flag to set if the system should predictively attempt to re-enable Bluetooth while
+ * the user is driving.
+ * @hide
+ */
+ public static final String BLUETOOTH_ON_WHILE_DRIVING = "bluetooth_on_while_driving";
+
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
@@ -7843,7 +7882,6 @@
CLONE_TO_MANAGED_PROFILE.add(ENABLED_ACCESSIBILITY_SERVICES);
CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
- CLONE_TO_MANAGED_PROFILE.add(LOCATION_PREVIOUS_MODE);
CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED);
CLONE_TO_MANAGED_PROFILE.add(SELECTED_INPUT_METHOD_SUBTYPE);
}
@@ -7894,8 +7932,7 @@
* @param provider the location provider to query
* @return true if the provider is enabled
*
- * @deprecated use {@link #LOCATION_MODE} or
- * {@link LocationManager#isProviderEnabled(String)}
+ * @deprecated use {@link LocationManager#isProviderEnabled(String)}
*/
@Deprecated
public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) {
@@ -7908,12 +7945,13 @@
* @param provider the location provider to query
* @param userId the userId to query
* @return true if the provider is enabled
- * @deprecated use {@link #LOCATION_MODE} or
- * {@link LocationManager#isProviderEnabled(String)}
+ *
+ * @deprecated use {@link LocationManager#isProviderEnabled(String)}
* @hide
*/
@Deprecated
- public static final boolean isLocationProviderEnabledForUser(ContentResolver cr, String provider, int userId) {
+ public static final boolean isLocationProviderEnabledForUser(
+ ContentResolver cr, String provider, int userId) {
String allowedProviders = Settings.Secure.getStringForUser(cr,
LOCATION_PROVIDERS_ALLOWED, userId);
return TextUtils.delimitedStringContains(allowedProviders, ',', provider);
@@ -7924,7 +7962,8 @@
* @param cr the content resolver to use
* @param provider the location provider to enable or disable
* @param enabled true if the provider should be enabled
- * @deprecated use {@link #putInt(ContentResolver, String, int)} and {@link #LOCATION_MODE}
+ * @deprecated This API is deprecated. It requires WRITE_SECURE_SETTINGS permission to
+ * change location settings.
*/
@Deprecated
public static final void setLocationProviderEnabled(ContentResolver cr,
@@ -7940,8 +7979,8 @@
* @param enabled true if the provider should be enabled
* @param userId the userId for which to enable/disable providers
* @return true if the value was set, false on database errors
- * @deprecated use {@link #putIntForUser(ContentResolver, String, int, int)} and
- * {@link #LOCATION_MODE}
+ *
+ * @deprecated use {@link LocationManager#setProviderEnabledForUser(String, boolean, int)}
* @hide
*/
@Deprecated
@@ -7962,28 +8001,6 @@
}
/**
- * Saves the current location mode into {@link #LOCATION_PREVIOUS_MODE}.
- */
- private static final boolean saveLocationModeForUser(ContentResolver cr, int userId) {
- final int mode = getLocationModeForUser(cr, userId);
- return putIntForUser(cr, Settings.Secure.LOCATION_PREVIOUS_MODE, mode, userId);
- }
-
- /**
- * Restores the current location mode from {@link #LOCATION_PREVIOUS_MODE}.
- */
- private static final boolean restoreLocationModeForUser(ContentResolver cr, int userId) {
- int mode = getIntForUser(cr, Settings.Secure.LOCATION_PREVIOUS_MODE,
- LOCATION_MODE_HIGH_ACCURACY, userId);
- // Make sure that the previous mode is never "off". Otherwise the user won't be able to
- // turn on location any longer.
- if (mode == LOCATION_MODE_OFF) {
- mode = LOCATION_MODE_HIGH_ACCURACY;
- }
- return setLocationModeForUser(cr, mode, userId);
- }
-
- /**
* Thread-safe method for setting the location mode to one of
* {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
* {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}.
@@ -7996,18 +8013,20 @@
* @return true if the value was set, false on database errors
*
* @throws IllegalArgumentException if mode is not one of the supported values
+ *
+ * @deprecated To enable/disable location, use
+ * {@link LocationManager#setLocationEnabledForUser(boolean, int)}.
+ * To enable/disable a specific location provider, use
+ * {@link LocationManager#setProviderEnabledForUser(String, boolean, int)}.
*/
- private static final boolean setLocationModeForUser(ContentResolver cr, int mode,
- int userId) {
+ @Deprecated
+ private static boolean setLocationModeForUser(
+ ContentResolver cr, int mode, int userId) {
synchronized (mLocationSettingsLock) {
boolean gps = false;
boolean network = false;
switch (mode) {
- case LOCATION_MODE_PREVIOUS:
- // Retrieve the actual mode and set to that mode.
- return restoreLocationModeForUser(cr, userId);
case LOCATION_MODE_OFF:
- saveLocationModeForUser(cr, userId);
break;
case LOCATION_MODE_SENSORS_ONLY:
gps = true;
@@ -8022,15 +8041,7 @@
default:
throw new IllegalArgumentException("Invalid location mode: " + mode);
}
- // Note it's important that we set the NLP mode first. The Google implementation
- // of NLP clears its NLP consent setting any time it receives a
- // LocationManager.PROVIDERS_CHANGED_ACTION broadcast and NLP is disabled. Also,
- // it shows an NLP consent dialog any time it receives the broadcast, NLP is
- // enabled, and the NLP consent is not set. If 1) we were to enable GPS first,
- // 2) a setup wizard has its own NLP consent UI that sets the NLP consent setting,
- // and 3) the receiver happened to complete before we enabled NLP, then the Google
- // NLP would detect the attempt to enable NLP and show a redundant NLP consent
- // dialog. Then the people who wrote the setup wizard would be sad.
+
boolean nlpSuccess = Settings.Secure.setLocationProviderEnabledForUser(
cr, LocationManager.NETWORK_PROVIDER, network, userId);
boolean gpsSuccess = Settings.Secure.setLocationProviderEnabledForUser(
@@ -11170,10 +11181,20 @@
*
* @hide
*/
+ @TestApi
public static final String LOCATION_GLOBAL_KILL_SWITCH =
"location_global_kill_switch";
/**
+ * If set to 1, SettingsProvider's restoreAnyVersion="true" attribute will be ignored
+ * and restoring to lower version of platform API will be skipped.
+ *
+ * @hide
+ */
+ public static final String OVERRIDE_SETTINGS_PROVIDER_RESTORE_ANY_VERSION =
+ "override_settings_provider_restore_any_version";
+
+ /**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
*
@@ -11892,6 +11913,19 @@
* @hide
*/
public static final String SHOW_FIRST_CRASH_DIALOG = "show_first_crash_dialog";
+
+ /**
+ * If nonzero, crash dialogs will show an option to restart the app.
+ * @hide
+ */
+ public static final String SHOW_RESTART_IN_CRASH_DIALOG = "show_restart_in_crash_dialog";
+
+ /**
+ * If nonzero, crash dialogs will show an option to mute all future crash dialogs for
+ * this app.
+ * @hide
+ */
+ public static final String SHOW_MUTE_IN_CRASH_DIALOG = "show_mute_in_crash_dialog";
}
/**
diff --git a/core/java/android/provider/SettingsValidators.java b/core/java/android/provider/SettingsValidators.java
index 84c9e88..5885b6b 100644
--- a/core/java/android/provider/SettingsValidators.java
+++ b/core/java/android/provider/SettingsValidators.java
@@ -100,7 +100,7 @@
String[] subparts = value.split("\\.");
boolean isValidPackageName = true;
for (String subpart : subparts) {
- isValidPackageName |= isSubpartValidForPackageName(subpart);
+ isValidPackageName &= isSubpartValidForPackageName(subpart);
if (!isValidPackageName) break;
}
return isValidPackageName;
@@ -110,7 +110,7 @@
if (subpart.length() == 0) return false;
boolean isValidSubpart = Character.isLetter(subpart.charAt(0));
for (int i = 1; i < subpart.length(); i++) {
- isValidSubpart |= (Character.isLetterOrDigit(subpart.charAt(i))
+ isValidSubpart &= (Character.isLetterOrDigit(subpart.charAt(i))
|| (subpart.charAt(i) == '_'));
if (!isValidSubpart) break;
}
diff --git a/core/java/android/security/keystore/BackwardsCompat.java b/core/java/android/security/keystore/BackwardsCompat.java
index 24921f0..69558c4 100644
--- a/core/java/android/security/keystore/BackwardsCompat.java
+++ b/core/java/android/security/keystore/BackwardsCompat.java
@@ -30,7 +30,7 @@
static KeychainProtectionParams toLegacyKeychainProtectionParams(
- android.security.keystore.recovery.KeychainProtectionParams keychainProtectionParams
+ android.security.keystore.recovery.KeyChainProtectionParams keychainProtectionParams
) {
return new KeychainProtectionParams.Builder()
.setUserSecretType(keychainProtectionParams.getUserSecretType())
@@ -80,15 +80,15 @@
return map(wrappedApplicationKeys, BackwardsCompat::fromLegacyWrappedApplicationKey);
}
- static List<android.security.keystore.recovery.KeychainProtectionParams>
+ static List<android.security.keystore.recovery.KeyChainProtectionParams>
fromLegacyKeychainProtectionParams(
List<KeychainProtectionParams> keychainProtectionParams) {
return map(keychainProtectionParams, BackwardsCompat::fromLegacyKeychainProtectionParam);
}
- static android.security.keystore.recovery.KeychainProtectionParams
+ static android.security.keystore.recovery.KeyChainProtectionParams
fromLegacyKeychainProtectionParam(KeychainProtectionParams keychainProtectionParams) {
- return new android.security.keystore.recovery.KeychainProtectionParams.Builder()
+ return new android.security.keystore.recovery.KeyChainProtectionParams.Builder()
.setUserSecretType(keychainProtectionParams.getUserSecretType())
.setSecret(keychainProtectionParams.getSecret())
.setLockScreenUiFormat(keychainProtectionParams.getLockScreenUiFormat())
@@ -99,7 +99,7 @@
}
static KeychainSnapshot toLegacyKeychainSnapshot(
- android.security.keystore.recovery.KeychainSnapshot keychainSnapshot
+ android.security.keystore.recovery.KeyChainSnapshot keychainSnapshot
) {
return new KeychainSnapshot.Builder()
.setCounterId(keychainSnapshot.getCounterId())
@@ -109,7 +109,7 @@
.setMaxAttempts(keychainSnapshot.getMaxAttempts())
.setServerParams(keychainSnapshot.getServerParams())
.setKeychainProtectionParams(
- map(keychainSnapshot.getKeychainProtectionParams(),
+ map(keychainSnapshot.getKeyChainProtectionParams(),
BackwardsCompat::toLegacyKeychainProtectionParams))
.setWrappedApplicationKeys(
map(keychainSnapshot.getWrappedApplicationKeys(),
diff --git a/core/java/android/security/keystore/KeychainProtectionParams.java b/core/java/android/security/keystore/KeychainProtectionParams.java
index a3cd431..a940fdc 100644
--- a/core/java/android/security/keystore/KeychainProtectionParams.java
+++ b/core/java/android/security/keystore/KeychainProtectionParams.java
@@ -260,9 +260,6 @@
}
};
- /**
- * @hide
- */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mUserSecretType);
diff --git a/core/java/android/security/keystore/KeychainSnapshot.java b/core/java/android/security/keystore/KeychainSnapshot.java
index e03dd4a..23aec25 100644
--- a/core/java/android/security/keystore/KeychainSnapshot.java
+++ b/core/java/android/security/keystore/KeychainSnapshot.java
@@ -151,6 +151,8 @@
/**
* Builder for creating {@link KeychainSnapshot}.
+ *
+ * @hide
*/
public static class Builder {
private KeychainSnapshot mInstance = new KeychainSnapshot();
@@ -263,9 +265,6 @@
}
}
- /**
- * @hide
- */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mSnapshotVersion);
diff --git a/core/java/android/security/keystore/recovery/BadCertificateFormatException.java b/core/java/android/security/keystore/recovery/BadCertificateFormatException.java
index fda3387..e0781a5 100644
--- a/core/java/android/security/keystore/recovery/BadCertificateFormatException.java
+++ b/core/java/android/security/keystore/recovery/BadCertificateFormatException.java
@@ -20,6 +20,7 @@
* Error thrown when the recovery agent supplies an invalid X509 certificate.
*
* @hide
+ * Deprecated
*/
public class BadCertificateFormatException extends RecoveryControllerException {
public BadCertificateFormatException(String msg) {
diff --git a/core/java/android/security/keystore/recovery/DecryptionFailedException.java b/core/java/android/security/keystore/recovery/DecryptionFailedException.java
index b414dc5..af00e05 100644
--- a/core/java/android/security/keystore/recovery/DecryptionFailedException.java
+++ b/core/java/android/security/keystore/recovery/DecryptionFailedException.java
@@ -16,14 +16,18 @@
package android.security.keystore.recovery;
+import android.annotation.SystemApi;
+
+import java.security.GeneralSecurityException;
+
/**
* Error thrown when decryption failed, due to an agent error. i.e., using the incorrect key,
* trying to decrypt garbage data, trying to decrypt data that has somehow been corrupted, etc.
*
* @hide
*/
-public class DecryptionFailedException extends RecoveryControllerException {
-
+@SystemApi
+public class DecryptionFailedException extends GeneralSecurityException {
public DecryptionFailedException(String msg) {
super(msg);
}
diff --git a/core/java/android/security/keystore/recovery/InternalRecoveryServiceException.java b/core/java/android/security/keystore/recovery/InternalRecoveryServiceException.java
index 07a540c..218d26e 100644
--- a/core/java/android/security/keystore/recovery/InternalRecoveryServiceException.java
+++ b/core/java/android/security/keystore/recovery/InternalRecoveryServiceException.java
@@ -16,6 +16,9 @@
package android.security.keystore.recovery;
+import android.annotation.SystemApi;
+
+import java.security.GeneralSecurityException;
/**
* An error thrown when something went wrong internally in the recovery service.
*
@@ -24,7 +27,8 @@
*
* @hide
*/
-public class InternalRecoveryServiceException extends RecoveryControllerException {
+@SystemApi
+public class InternalRecoveryServiceException extends GeneralSecurityException {
public InternalRecoveryServiceException(String msg) {
super(msg);
}
diff --git a/core/java/android/security/keystore/recovery/KeychainProtectionParams.aidl b/core/java/android/security/keystore/recovery/KeyChainProtectionParams.aidl
similarity index 94%
rename from core/java/android/security/keystore/recovery/KeychainProtectionParams.aidl
rename to core/java/android/security/keystore/recovery/KeyChainProtectionParams.aidl
index 5385738..58edc84 100644
--- a/core/java/android/security/keystore/recovery/KeychainProtectionParams.aidl
+++ b/core/java/android/security/keystore/recovery/KeyChainProtectionParams.aidl
@@ -17,4 +17,4 @@
package android.security.keystore.recovery;
/* @hide */
-parcelable KeychainProtectionParams;
+parcelable KeyChainProtectionParams;
diff --git a/core/java/android/security/keystore/recovery/KeychainProtectionParams.java b/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java
similarity index 81%
rename from core/java/android/security/keystore/recovery/KeychainProtectionParams.java
rename to core/java/android/security/keystore/recovery/KeyChainProtectionParams.java
index 445815b..a43952a 100644
--- a/core/java/android/security/keystore/recovery/KeychainProtectionParams.java
+++ b/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -28,7 +29,7 @@
import java.util.Arrays;
/**
- * A {@link KeychainSnapshot} is protected with a key derived from the user's lock screen. This
+ * A {@link KeyChainSnapshot} is protected with a key derived from the user's lock screen. This
* class wraps all the data necessary to derive the same key on a recovering device:
*
* <ul>
@@ -38,7 +39,7 @@
* <li>The algorithm used to derive a key from the user's lock screen, e.g. SHA-256 with a salt.
* </ul>
*
- * <p>As such, this data is sent along with the {@link KeychainSnapshot} when syncing the current
+ * <p>As such, this data is sent along with the {@link KeyChainSnapshot} when syncing the current
* version of the keychain.
*
* <p>For now, the recoverable keychain only supports a single layer of protection, which is the
@@ -47,10 +48,11 @@
*
* @hide
*/
-public final class KeychainProtectionParams implements Parcelable {
+@SystemApi
+public final class KeyChainProtectionParams implements Parcelable {
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef({TYPE_LOCKSCREEN, TYPE_CUSTOM_PASSWORD})
+ @IntDef(prefix = {"TYPE_"}, value = {TYPE_LOCKSCREEN, TYPE_CUSTOM_PASSWORD})
public @interface UserSecretType {
}
@@ -66,24 +68,24 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef({TYPE_PIN, TYPE_PASSWORD, TYPE_PATTERN})
+ @IntDef(prefix = {"UI_FORMAT_"}, value = {UI_FORMAT_PIN, UI_FORMAT_PASSWORD, UI_FORMAT_PATTERN})
public @interface LockScreenUiFormat {
}
/**
* Pin with digits only.
*/
- public static final int TYPE_PIN = 1;
+ public static final int UI_FORMAT_PIN = 1;
/**
* Password. String with latin-1 characters only.
*/
- public static final int TYPE_PASSWORD = 2;
+ public static final int UI_FORMAT_PASSWORD = 2;
/**
* Pattern with 3 by 3 grid.
*/
- public static final int TYPE_PATTERN = 3;
+ public static final int UI_FORMAT_PATTERN = 3;
@UserSecretType
private Integer mUserSecretType;
@@ -102,7 +104,7 @@
* @link {#clearSecret} to overwrite its value in memory.
* @hide
*/
- public KeychainProtectionParams(@UserSecretType int userSecretType,
+ public KeyChainProtectionParams(@UserSecretType int userSecretType,
@LockScreenUiFormat int lockScreenUiFormat,
@NonNull KeyDerivationParams keyDerivationParams,
@NonNull byte[] secret) {
@@ -112,7 +114,7 @@
mSecret = Preconditions.checkNotNull(secret);
}
- private KeychainProtectionParams() {
+ private KeyChainProtectionParams() {
}
@@ -126,11 +128,11 @@
/**
* Specifies UX shown to user during recovery.
- * Default value is {@code TYPE_LOCKSCREEN}
+ * Default value is {@code UI_FORMAT_LOCKSCREEN}
*
- * @see TYPE_PIN
- * @see TYPE_PASSWORD
- * @see TYPE_PATTERN
+ * @see UI_FORMAT_PIN
+ * @see UI_FORMAT_PASSWORD
+ * @see UI_FORMAT_PATTERN
*/
public @LockScreenUiFormat int getLockScreenUiFormat() {
return mLockScreenUiFormat;
@@ -140,7 +142,7 @@
* Specifies function used to derive symmetric key from user input
* Format is defined in separate util class.
*/
- @NonNull public KeyDerivationParams getKeyDerivationParams() {
+ public @NonNull KeyDerivationParams getKeyDerivationParams() {
return mKeyDerivationParams;
}
@@ -155,11 +157,10 @@
}
/**
- * Builder for creating {@link KeychainProtectionParams}.
+ * Builder for creating {@link KeyChainProtectionParams}.
*/
public static class Builder {
- private KeychainProtectionParams
- mInstance = new KeychainProtectionParams();
+ private KeyChainProtectionParams mInstance = new KeyChainProtectionParams();
/**
* Sets user secret type.
@@ -177,9 +178,9 @@
/**
* Sets UI format.
*
- * @see TYPE_PIN
- * @see TYPE_PASSWORD
- * @see TYPE_PATTERN
+ * @see UI_FORMAT_PIN
+ * @see UI_FORMAT_PASSWORD
+ * @see UI_FORMAT_PATTERN
* @param lockScreenUiFormat The UI format
* @return This builder.
*/
@@ -213,14 +214,14 @@
/**
- * Creates a new {@link KeychainProtectionParams} instance.
+ * Creates a new {@link KeyChainProtectionParams} instance.
* The instance will include default values, if {@link setSecret}
* or {@link setUserSecretType} were not called.
*
* @return new instance
* @throws NullPointerException if some required fields were not set.
*/
- @NonNull public KeychainProtectionParams build() {
+ @NonNull public KeyChainProtectionParams build() {
if (mInstance.mUserSecretType == null) {
mInstance.mUserSecretType = TYPE_LOCKSCREEN;
}
@@ -250,20 +251,17 @@
Arrays.fill(mSecret, (byte) 0);
}
- public static final Creator<KeychainProtectionParams> CREATOR =
- new Creator<KeychainProtectionParams>() {
- public KeychainProtectionParams createFromParcel(Parcel in) {
- return new KeychainProtectionParams(in);
+ public static final Parcelable.Creator<KeyChainProtectionParams> CREATOR =
+ new Parcelable.Creator<KeyChainProtectionParams>() {
+ public KeyChainProtectionParams createFromParcel(Parcel in) {
+ return new KeyChainProtectionParams(in);
}
- public KeychainProtectionParams[] newArray(int length) {
- return new KeychainProtectionParams[length];
+ public KeyChainProtectionParams[] newArray(int length) {
+ return new KeyChainProtectionParams[length];
}
};
- /**
- * @hide
- */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mUserSecretType);
@@ -275,7 +273,7 @@
/**
* @hide
*/
- protected KeychainProtectionParams(Parcel in) {
+ protected KeyChainProtectionParams(Parcel in) {
mUserSecretType = in.readInt();
mLockScreenUiFormat = in.readInt();
mKeyDerivationParams = in.readTypedObject(KeyDerivationParams.CREATOR);
diff --git a/core/java/android/security/keystore/recovery/KeychainSnapshot.aidl b/core/java/android/security/keystore/recovery/KeyChainSnapshot.aidl
similarity index 95%
rename from core/java/android/security/keystore/recovery/KeychainSnapshot.aidl
rename to core/java/android/security/keystore/recovery/KeyChainSnapshot.aidl
index 7822f39..d02a2ea 100644
--- a/core/java/android/security/keystore/recovery/KeychainSnapshot.aidl
+++ b/core/java/android/security/keystore/recovery/KeyChainSnapshot.aidl
@@ -17,4 +17,4 @@
package android.security.keystore.recovery;
/* @hide */
-parcelable KeychainSnapshot;
+parcelable KeyChainSnapshot;
diff --git a/core/java/android/security/keystore/recovery/KeychainSnapshot.java b/core/java/android/security/keystore/recovery/KeyChainSnapshot.java
similarity index 85%
rename from core/java/android/security/keystore/recovery/KeychainSnapshot.java
rename to core/java/android/security/keystore/recovery/KeyChainSnapshot.java
index a8e2725..df535ed 100644
--- a/core/java/android/security/keystore/recovery/KeychainSnapshot.java
+++ b/core/java/android/security/keystore/recovery/KeyChainSnapshot.java
@@ -17,6 +17,7 @@
package android.security.keystore.recovery;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -42,7 +43,8 @@
*
* @hide
*/
-public final class KeychainSnapshot implements Parcelable {
+@SystemApi
+public final class KeyChainSnapshot implements Parcelable {
private static final int DEFAULT_MAX_ATTEMPTS = 10;
private static final long DEFAULT_COUNTER_ID = 1L;
@@ -51,7 +53,7 @@
private long mCounterId = DEFAULT_COUNTER_ID;
private byte[] mServerParams;
private byte[] mPublicKey;
- private List<KeychainProtectionParams> mKeychainProtectionParams;
+ private List<KeyChainProtectionParams> mKeyChainProtectionParams;
private List<WrappedApplicationKey> mEntryRecoveryData;
private byte[] mEncryptedRecoveryKeyBlob;
@@ -59,21 +61,21 @@
* @hide
* Deprecated, consider using builder.
*/
- public KeychainSnapshot(
+ public KeyChainSnapshot(
int snapshotVersion,
- @NonNull List<KeychainProtectionParams> keychainProtectionParams,
+ @NonNull List<KeyChainProtectionParams> keyChainProtectionParams,
@NonNull List<WrappedApplicationKey> wrappedApplicationKeys,
@NonNull byte[] encryptedRecoveryKeyBlob) {
mSnapshotVersion = snapshotVersion;
- mKeychainProtectionParams =
- Preconditions.checkCollectionElementsNotNull(keychainProtectionParams,
- "keychainProtectionParams");
+ mKeyChainProtectionParams =
+ Preconditions.checkCollectionElementsNotNull(keyChainProtectionParams,
+ "KeyChainProtectionParams");
mEntryRecoveryData = Preconditions.checkCollectionElementsNotNull(wrappedApplicationKeys,
"wrappedApplicationKeys");
mEncryptedRecoveryKeyBlob = Preconditions.checkNotNull(encryptedRecoveryKeyBlob);
}
- private KeychainSnapshot() {
+ private KeyChainSnapshot() {
}
@@ -119,8 +121,8 @@
/**
* UI and key derivation parameters. Note that combination of secrets may be used.
*/
- public @NonNull List<KeychainProtectionParams> getKeychainProtectionParams() {
- return mKeychainProtectionParams;
+ public @NonNull List<KeyChainProtectionParams> getKeyChainProtectionParams() {
+ return mKeyChainProtectionParams;
}
/**
@@ -138,23 +140,23 @@
return mEncryptedRecoveryKeyBlob;
}
- public static final Creator<KeychainSnapshot> CREATOR =
- new Creator<KeychainSnapshot>() {
- public KeychainSnapshot createFromParcel(Parcel in) {
- return new KeychainSnapshot(in);
+ public static final Creator<KeyChainSnapshot> CREATOR =
+ new Creator<KeyChainSnapshot>() {
+ public KeyChainSnapshot createFromParcel(Parcel in) {
+ return new KeyChainSnapshot(in);
}
- public KeychainSnapshot[] newArray(int length) {
- return new KeychainSnapshot[length];
+ public KeyChainSnapshot[] newArray(int length) {
+ return new KeyChainSnapshot[length];
}
};
/**
- * Builder for creating {@link KeychainSnapshot}.
+ * Builder for creating {@link KeyChainSnapshot}.
+ * @hide
*/
public static class Builder {
- private KeychainSnapshot
- mInstance = new KeychainSnapshot();
+ private KeyChainSnapshot mInstance = new KeyChainSnapshot();
/**
* Snapshot version for given account.
@@ -217,9 +219,9 @@
* @param recoveryMetadata The UI and key derivation parameters
* @return This builder.
*/
- public Builder setKeychainProtectionParams(
- @NonNull List<KeychainProtectionParams> recoveryMetadata) {
- mInstance.mKeychainProtectionParams = recoveryMetadata;
+ public Builder setKeyChainProtectionParams(
+ @NonNull List<KeyChainProtectionParams> recoveryMetadata) {
+ mInstance.mKeyChainProtectionParams = recoveryMetadata;
return this;
}
@@ -247,13 +249,13 @@
/**
- * Creates a new {@link KeychainSnapshot} instance.
+ * Creates a new {@link KeyChainSnapshot} instance.
*
* @return new instance
* @throws NullPointerException if some required fields were not set.
*/
- @NonNull public KeychainSnapshot build() {
- Preconditions.checkCollectionElementsNotNull(mInstance.mKeychainProtectionParams,
+ @NonNull public KeyChainSnapshot build() {
+ Preconditions.checkCollectionElementsNotNull(mInstance.mKeyChainProtectionParams,
"recoveryMetadata");
Preconditions.checkCollectionElementsNotNull(mInstance.mEntryRecoveryData,
"entryRecoveryData");
@@ -264,13 +266,10 @@
}
}
- /**
- * @hide
- */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mSnapshotVersion);
- out.writeTypedList(mKeychainProtectionParams);
+ out.writeTypedList(mKeyChainProtectionParams);
out.writeByteArray(mEncryptedRecoveryKeyBlob);
out.writeTypedList(mEntryRecoveryData);
out.writeInt(mMaxAttempts);
@@ -282,9 +281,9 @@
/**
* @hide
*/
- protected KeychainSnapshot(Parcel in) {
+ protected KeyChainSnapshot(Parcel in) {
mSnapshotVersion = in.readInt();
- mKeychainProtectionParams = in.createTypedArrayList(KeychainProtectionParams.CREATOR);
+ mKeyChainProtectionParams = in.createTypedArrayList(KeyChainProtectionParams.CREATOR);
mEncryptedRecoveryKeyBlob = in.createByteArray();
mEntryRecoveryData = in.createTypedArrayList(WrappedApplicationKey.CREATOR);
mMaxAttempts = in.readInt();
diff --git a/core/java/android/security/keystore/recovery/KeyDerivationParams.java b/core/java/android/security/keystore/recovery/KeyDerivationParams.java
index 9061395..fc909a0 100644
--- a/core/java/android/security/keystore/recovery/KeyDerivationParams.java
+++ b/core/java/android/security/keystore/recovery/KeyDerivationParams.java
@@ -18,9 +18,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
@@ -32,6 +34,7 @@
*
* @hide
*/
+@SystemApi
public final class KeyDerivationParams implements Parcelable {
private final int mAlgorithm;
private byte[] mSalt;
@@ -61,6 +64,9 @@
return new KeyDerivationParams(ALGORITHM_SHA256, salt);
}
+ /**
+ * @hide
+ */
// TODO: Make private once legacy API is removed
public KeyDerivationParams(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt) {
mAlgorithm = algorithm;
@@ -81,8 +87,8 @@
return mSalt;
}
- public static final Creator<KeyDerivationParams> CREATOR =
- new Creator<KeyDerivationParams>() {
+ public static final Parcelable.Creator<KeyDerivationParams> CREATOR =
+ new Parcelable.Creator<KeyDerivationParams>() {
public KeyDerivationParams createFromParcel(Parcel in) {
return new KeyDerivationParams(in);
}
@@ -92,9 +98,6 @@
}
};
- /**
- * @hide
- */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mAlgorithm);
diff --git a/core/java/android/security/keystore/recovery/LockScreenRequiredException.java b/core/java/android/security/keystore/recovery/LockScreenRequiredException.java
index ced2368..0062d29 100644
--- a/core/java/android/security/keystore/recovery/LockScreenRequiredException.java
+++ b/core/java/android/security/keystore/recovery/LockScreenRequiredException.java
@@ -16,6 +16,10 @@
package android.security.keystore.recovery;
+import android.annotation.SystemApi;
+
+import java.security.GeneralSecurityException;
+
/**
* Error thrown when trying to generate keys for a profile that has no lock screen set.
*
@@ -23,7 +27,8 @@
*
* @hide
*/
-public class LockScreenRequiredException extends RecoveryControllerException {
+@SystemApi
+public class LockScreenRequiredException extends GeneralSecurityException {
public LockScreenRequiredException(String msg) {
super(msg);
}
diff --git a/core/java/android/security/keystore/recovery/RecoveryClaim.java b/core/java/android/security/keystore/recovery/RecoveryClaim.java
index 11385d8..45c6b4ff 100644
--- a/core/java/android/security/keystore/recovery/RecoveryClaim.java
+++ b/core/java/android/security/keystore/recovery/RecoveryClaim.java
@@ -20,6 +20,7 @@
* An attempt to recover a keychain protected by remote secure hardware.
*
* @hide
+ * Deprecated
*/
public class RecoveryClaim {
diff --git a/core/java/android/security/keystore/recovery/RecoveryController.java b/core/java/android/security/keystore/recovery/RecoveryController.java
index 1908ce2..71a36f1 100644
--- a/core/java/android/security/keystore/recovery/RecoveryController.java
+++ b/core/java/android/security/keystore/recovery/RecoveryController.java
@@ -18,15 +18,19 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.app.PendingIntent;
+import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
-import android.util.Log;
import com.android.internal.widget.ILockSettings;
+import java.security.cert.CertificateException;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -49,6 +53,7 @@
*
* @hide
*/
+@SystemApi
public class RecoveryController {
private static final String TAG = "RecoveryController";
@@ -114,9 +119,18 @@
}
/**
+ * Internal method used by {@code RecoverySession}.
+ *
+ * @hide
+ */
+ ILockSettings getBinder() {
+ return mBinder;
+ }
+
+ /**
* Gets a new instance of the class.
*/
- public static RecoveryController getInstance() {
+ public static RecoveryController getInstance(Context context) {
ILockSettings lockSettings =
ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
return new RecoveryController(lockSettings);
@@ -136,38 +150,39 @@
*
* @param rootCertificateAlias alias of a root certificate preinstalled on the device
* @param signedPublicKeyList binary blob a list of X509 certificates and signature
- * @throws BadCertificateFormatException if the {@code signedPublicKeyList} is in a bad format.
+ * @throws CertificateException if the {@code signedPublicKeyList} is in a bad format.
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
+ @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
public void initRecoveryService(
@NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
- throws BadCertificateFormatException, InternalRecoveryServiceException {
+ throws CertificateException, InternalRecoveryServiceException {
try {
mBinder.initRecoveryService(rootCertificateAlias, signedPublicKeyList);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT) {
- throw new BadCertificateFormatException(e.getMessage());
+ throw new CertificateException(e.getMessage());
}
throw wrapUnexpectedServiceSpecificException(e);
}
}
/**
- * Returns data necessary to store all recoverable keys for given account. Key material is
+ * Returns data necessary to store all recoverable keys. Key material is
* encrypted with user secret and recovery public key.
*
- * @param account specific to Recovery agent.
* @return Data necessary to recover keystore.
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
- @NonNull public KeychainSnapshot getRecoveryData(@NonNull byte[] account)
+ @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
+ public @NonNull KeyChainSnapshot getRecoveryData()
throws InternalRecoveryServiceException {
try {
- return mBinder.getRecoveryData(account);
+ return mBinder.getRecoveryData(/*account=*/ new byte[]{});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
@@ -188,6 +203,7 @@
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
+ @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
throws InternalRecoveryServiceException {
try {
@@ -200,32 +216,8 @@
}
/**
- * Returns a map from recovery agent accounts to corresponding KeyStore recovery snapshot
- * version. Version zero is used, if no snapshots were created for the account.
- *
- * @return Map from recovery agent accounts to snapshot versions.
- * @see KeychainSnapshot#getSnapshotVersion
- * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
- * service.
- */
- public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
- throws InternalRecoveryServiceException {
- try {
- // IPC doesn't support generic Maps.
- @SuppressWarnings("unchecked")
- Map<byte[], Integer> result =
- (Map<byte[], Integer>) mBinder.getRecoverySnapshotVersions();
- return result;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- throw wrapUnexpectedServiceSpecificException(e);
- }
- }
-
- /**
* Server parameters used to generate new recovery key blobs. This value will be included in
- * {@code KeychainSnapshot.getEncryptedRecoveryKeyBlob()}. The same value must be included
+ * {@code KeyChainSnapshot.getEncryptedRecoveryKeyBlob()}. The same value must be included
* in vaultParams {@link #startRecoverySession}
*
* @param serverParams included in recovery key blob.
@@ -233,6 +225,7 @@
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
+ @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
public void setServerParams(byte[] serverParams) throws InternalRecoveryServiceException {
try {
mBinder.setServerParams(serverParams);
@@ -244,21 +237,43 @@
}
/**
- * Updates recovery status for given keys. It is used to notify keystore that key was
+ * Gets aliases of recoverable keys for the application.
+ *
+ * @param packageName which recoverable keys' aliases will be returned.
+ *
+ * @return {@code List} of all aliases.
+ */
+ public List<String> getAliases(@Nullable String packageName)
+ throws InternalRecoveryServiceException {
+ try {
+ // TODO: update aidl
+ Map<String, Integer> allStatuses = mBinder.getRecoveryStatus(packageName);
+ return new ArrayList<>(allStatuses.keySet());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw wrapUnexpectedServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Updates recovery status for given key. It is used to notify keystore that key was
* successfully stored on the server or there were an error. Application can check this value
* using {@code getRecoveyStatus}.
*
- * @param packageName Application whose recoverable keys' statuses are to be updated.
- * @param aliases List of application-specific key aliases. If the array is empty, updates the
- * status for all existing recoverable keys.
+ * @param packageName Application whose recoverable key's status are to be updated.
+ * @param alias Application-specific key alias.
* @param status Status specific to recovery agent.
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
+ @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
public void setRecoveryStatus(
- @NonNull String packageName, @Nullable String[] aliases, int status)
+ @NonNull String packageName, String alias, int status)
throws NameNotFoundException, InternalRecoveryServiceException {
try {
+ // TODO: update aidl
+ String[] aliases = alias == null ? null : new String[]{alias};
mBinder.setRecoveryStatus(packageName, aliases, status);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -268,7 +283,7 @@
}
/**
- * Returns a {@code Map} from Application's KeyStore key aliases to their recovery status.
+ * Returns recovery status for Application's KeyStore key.
* Negative status values are reserved for recovery agent specific codes. List of common codes:
*
* <ul>
@@ -278,18 +293,24 @@
* <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE}
* </ul>
*
- * @return {@code Map} from KeyStore alias to recovery status.
+ * @param packageName Application whose recoverable key status is returned.
+ * @param alias Application-specific key alias.
+ * @return Recovery status.
* @see #setRecoveryStatus
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
- public Map<String, Integer> getRecoveryStatus() throws InternalRecoveryServiceException {
+ public int getRecoveryStatus(String packageName, String alias)
+ throws InternalRecoveryServiceException {
try {
- // IPC doesn't support generic Maps.
- @SuppressWarnings("unchecked")
- Map<String, Integer> result =
- (Map<String, Integer>) mBinder.getRecoveryStatus(/*packageName=*/ null);
- return result;
+ // TODO: update aidl
+ Map<String, Integer> allStatuses = mBinder.getRecoveryStatus(packageName);
+ Integer status = allStatuses.get(alias);
+ if (status == null) {
+ return RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE;
+ } else {
+ return status;
+ }
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (ServiceSpecificException e) {
@@ -301,13 +322,13 @@
* Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them
* is necessary to recover data.
*
- * @param secretTypes {@link KeychainProtectionParams#TYPE_LOCKSCREEN} or {@link
- * KeychainProtectionParams#TYPE_CUSTOM_PASSWORD}
+ * @param secretTypes {@link KeyChainProtectionParams#TYPE_LOCKSCREEN} or {@link
+ * KeyChainProtectionParams#TYPE_CUSTOM_PASSWORD}
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
public void setRecoverySecretTypes(
- @NonNull @KeychainProtectionParams.UserSecretType int[] secretTypes)
+ @NonNull @KeyChainProtectionParams.UserSecretType int[] secretTypes)
throws InternalRecoveryServiceException {
try {
mBinder.setRecoverySecretTypes(secretTypes);
@@ -320,14 +341,14 @@
/**
* Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is
- * necessary to generate KeychainSnapshot.
+ * necessary to generate KeyChainSnapshot.
*
* @return list of recovery secret types
- * @see KeychainSnapshot
+ * @see KeyChainSnapshot
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
- public @NonNull @KeychainProtectionParams.UserSecretType int[] getRecoverySecretTypes()
+ public @NonNull @KeyChainProtectionParams.UserSecretType int[] getRecoverySecretTypes()
throws InternalRecoveryServiceException {
try {
return mBinder.getRecoverySecretTypes();
@@ -348,7 +369,7 @@
* service.
*/
@NonNull
- public @KeychainProtectionParams.UserSecretType int[] getPendingRecoverySecretTypes()
+ public @KeyChainProtectionParams.UserSecretType int[] getPendingRecoverySecretTypes()
throws InternalRecoveryServiceException {
try {
return mBinder.getPendingRecoverySecretTypes();
@@ -362,7 +383,7 @@
/**
* Method notifies KeyStore that a user-generated secret is available. This method generates a
* symmetric session key which a trusted remote device can use to return a recovery key. Caller
- * should use {@link KeychainProtectionParams#clearSecret} to override the secret value in
+ * should use {@link KeyChainProtectionParams#clearSecret} to override the secret value in
* memory.
*
* @param recoverySecret user generated secret together with parameters necessary to regenerate
@@ -370,7 +391,7 @@
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
*/
- public void recoverySecretAvailable(@NonNull KeychainProtectionParams recoverySecret)
+ public void recoverySecretAvailable(@NonNull KeyChainProtectionParams recoverySecret)
throws InternalRecoveryServiceException {
try {
mBinder.recoverySecretAvailable(recoverySecret);
@@ -382,117 +403,21 @@
}
/**
- * Initializes recovery session and returns a blob with proof of recovery secrets possession.
- * The method generates symmetric key for a session, which trusted remote device can use to
- * return recovery key.
- *
- * @param verifierPublicKey Encoded {@code java.security.cert.X509Certificate} with Public key
- * used to create the recovery blob on the source device.
- * Keystore will verify the certificate using root of trust.
- * @param vaultParams Must match the parameters in the corresponding field in the recovery blob.
- * Used to limit number of guesses.
- * @param vaultChallenge Data passed from server for this recovery session and used to prevent
- * replay attacks
- * @param secrets Secrets provided by user, the method only uses type and secret fields.
- * @return The recovery claim. Claim provides a b binary blob with recovery claim. It is
- * encrypted with verifierPublicKey and contains a proof of user secrets, session symmetric
- * key and parameters necessary to identify the counter with the number of failed recovery
- * attempts.
- * @throws BadCertificateFormatException if the {@code verifierPublicKey} is in an incorrect
- * format.
- * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
- * service.
- */
- @NonNull public RecoveryClaim startRecoverySession(
- @NonNull byte[] verifierPublicKey,
- @NonNull byte[] vaultParams,
- @NonNull byte[] vaultChallenge,
- @NonNull List<KeychainProtectionParams> secrets)
- throws BadCertificateFormatException, InternalRecoveryServiceException {
- try {
- RecoverySession recoverySession = RecoverySession.newInstance(this);
- byte[] recoveryClaim =
- mBinder.startRecoverySession(
- recoverySession.getSessionId(),
- verifierPublicKey,
- vaultParams,
- vaultChallenge,
- secrets);
- return new RecoveryClaim(recoverySession, recoveryClaim);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- if (e.errorCode == ERROR_BAD_CERTIFICATE_FORMAT) {
- throw new BadCertificateFormatException(e.getMessage());
- }
- throw wrapUnexpectedServiceSpecificException(e);
- }
- }
-
- /**
- * Imports keys.
- *
- * @param session Related recovery session, as originally created by invoking
- * {@link #startRecoverySession(byte[], byte[], byte[], List)}.
- * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session.
- * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob
- * and session. KeyStore only uses package names from the application info in {@link
- * WrappedApplicationKey}. Caller is responsibility to perform certificates check.
- * @return Map from alias to raw key material.
- * @throws SessionExpiredException if {@code session} has since been closed.
- * @throws DecryptionFailedException if unable to decrypt the snapshot.
- * @throws InternalRecoveryServiceException if an error occurs internal to the recovery service.
- */
- public Map<String, byte[]> recoverKeys(
- @NonNull RecoverySession session,
- @NonNull byte[] recoveryKeyBlob,
- @NonNull List<WrappedApplicationKey> applicationKeys)
- throws SessionExpiredException, DecryptionFailedException,
- InternalRecoveryServiceException {
- try {
- return (Map<String, byte[]>) mBinder.recoverKeys(
- session.getSessionId(), recoveryKeyBlob, applicationKeys);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- if (e.errorCode == ERROR_DECRYPTION_FAILED) {
- throw new DecryptionFailedException(e.getMessage());
- }
- if (e.errorCode == ERROR_SESSION_EXPIRED) {
- throw new SessionExpiredException(e.getMessage());
- }
- throw wrapUnexpectedServiceSpecificException(e);
- }
- }
-
- /**
- * Deletes all data associated with {@code session}. Should not be invoked directly but via
- * {@link RecoverySession#close()}.
- *
- * @hide
- */
- void closeSession(RecoverySession session) {
- try {
- mBinder.closeSession(session.getSessionId());
- } catch (RemoteException | ServiceSpecificException e) {
- Log.e(TAG, "Unexpected error trying to close session", e);
- }
- }
-
- /**
- * Generates a key called {@code alias} and loads it into the recoverable key store. Returns the
- * raw material of the key.
+ * Generates a AES256/GCM/NoPADDING key called {@code alias} and loads it into the recoverable
+ * key store. Returns the raw material of the key.
*
* @param alias The key alias.
+ * @param account The account associated with the key
* @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
* service.
* @throws LockScreenRequiredException if the user has not set a lock screen. This is required
* to generate recoverable keys, as the snapshots are encrypted using a key derived from the
* lock screen.
*/
- public byte[] generateAndStoreKey(@NonNull String alias)
+ public byte[] generateAndStoreKey(@NonNull String alias, byte[] account)
throws InternalRecoveryServiceException, LockScreenRequiredException {
try {
+ // TODO: add account
return mBinder.generateAndStoreKey(alias);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -521,7 +446,7 @@
}
}
- private InternalRecoveryServiceException wrapUnexpectedServiceSpecificException(
+ InternalRecoveryServiceException wrapUnexpectedServiceSpecificException(
ServiceSpecificException e) {
if (e.errorCode == ERROR_SERVICE_INTERNAL_ERROR) {
return new InternalRecoveryServiceException(e.getMessage());
diff --git a/core/java/android/security/keystore/recovery/RecoveryControllerException.java b/core/java/android/security/keystore/recovery/RecoveryControllerException.java
index 0fb7c07..2733aca 100644
--- a/core/java/android/security/keystore/recovery/RecoveryControllerException.java
+++ b/core/java/android/security/keystore/recovery/RecoveryControllerException.java
@@ -22,6 +22,7 @@
* Base exception for errors thrown by {@link RecoveryController}.
*
* @hide
+ * Deprecated
*/
public abstract class RecoveryControllerException extends GeneralSecurityException {
RecoveryControllerException() { }
diff --git a/core/java/android/security/keystore/recovery/RecoverySession.java b/core/java/android/security/keystore/recovery/RecoverySession.java
index 89f8945..4db5d6e 100644
--- a/core/java/android/security/keystore/recovery/RecoverySession.java
+++ b/core/java/android/security/keystore/recovery/RecoverySession.java
@@ -16,15 +16,27 @@
package android.security.keystore.recovery;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.util.List;
+import java.util.Map;
/**
- * Session to recover a {@link KeychainSnapshot} from the remote trusted hardware, initiated by a
+ * Session to recover a {@link KeyChainSnapshot} from the remote trusted hardware, initiated by a
* recovery agent.
*
* @hide
*/
+@SystemApi
public class RecoverySession implements AutoCloseable {
+ private static final String TAG = "RecoverySession";
private static final int SESSION_ID_LENGTH_BYTES = 16;
@@ -39,6 +51,7 @@
/**
* A new session, started by {@code recoveryManager}.
*/
+ @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
static RecoverySession newInstance(RecoveryController recoveryController) {
return new RecoverySession(recoveryController, newSessionId());
}
@@ -58,14 +71,107 @@
}
/**
+ * Starts a recovery session and returns a blob with proof of recovery secret possession.
+ * The method generates a symmetric key for a session, which trusted remote device can use to
+ * return recovery key.
+ *
+ * @param verifierPublicKey Encoded {@code java.security.cert.X509Certificate} with Public key
+ * used to create the recovery blob on the source device.
+ * Keystore will verify the certificate using root of trust.
+ * @param vaultParams Must match the parameters in the corresponding field in the recovery blob.
+ * Used to limit number of guesses.
+ * @param vaultChallenge Data passed from server for this recovery session and used to prevent
+ * replay attacks
+ * @param secrets Secrets provided by user, the method only uses type and secret fields.
+ * @return The recovery claim. Claim provides a b binary blob with recovery claim. It is
+ * encrypted with verifierPublicKey and contains a proof of user secrets, session symmetric
+ * key and parameters necessary to identify the counter with the number of failed recovery
+ * attempts.
+ * @throws CertificateException if the {@code verifierPublicKey} is in an incorrect
+ * format.
+ * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
+ * service.
+ */
+ @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
+ @NonNull public byte[] start(
+ @NonNull byte[] verifierPublicKey,
+ @NonNull byte[] vaultParams,
+ @NonNull byte[] vaultChallenge,
+ @NonNull List<KeyChainProtectionParams> secrets)
+ throws CertificateException, InternalRecoveryServiceException {
+ try {
+ byte[] recoveryClaim =
+ mRecoveryController.getBinder().startRecoverySession(
+ mSessionId,
+ verifierPublicKey,
+ vaultParams,
+ vaultChallenge,
+ secrets);
+ return recoveryClaim;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode == RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT) {
+ throw new CertificateException(e.getMessage());
+ }
+ throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Imports keys.
+ *
+ * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session.
+ * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob
+ * and session. KeyStore only uses package names from the application info in {@link
+ * WrappedApplicationKey}. Caller is responsibility to perform certificates check.
+ * @return Map from alias to raw key material.
+ * @throws SessionExpiredException if {@code session} has since been closed.
+ * @throws DecryptionFailedException if unable to decrypt the snapshot.
+ * @throws InternalRecoveryServiceException if an error occurs internal to the recovery service.
+ */
+ @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
+ public Map<String, byte[]> recoverKeys(
+ @NonNull byte[] recoveryKeyBlob,
+ @NonNull List<WrappedApplicationKey> applicationKeys)
+ throws SessionExpiredException, DecryptionFailedException,
+ InternalRecoveryServiceException {
+ try {
+ return (Map<String, byte[]>) mRecoveryController.getBinder().recoverKeys(
+ mSessionId, recoveryKeyBlob, applicationKeys);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode == RecoveryController.ERROR_DECRYPTION_FAILED) {
+ throw new DecryptionFailedException(e.getMessage());
+ }
+ if (e.errorCode == RecoveryController.ERROR_SESSION_EXPIRED) {
+ throw new SessionExpiredException(e.getMessage());
+ }
+ throw mRecoveryController.wrapUnexpectedServiceSpecificException(e);
+ }
+ }
+
+ /**
* An internal session ID, used by the framework to match recovery claims to snapshot responses.
+ *
+ * @hide
*/
String getSessionId() {
return mSessionId;
}
+ /**
+ * Deletes all data associated with {@code session}. Should not be invoked directly but via
+ * {@link RecoverySession#close()}.
+ */
+ @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
@Override
public void close() {
- mRecoveryController.closeSession(this);
+ try {
+ mRecoveryController.getBinder().closeSession(mSessionId);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Unexpected error trying to close session", e);
+ }
}
}
diff --git a/core/java/android/security/keystore/recovery/SessionExpiredException.java b/core/java/android/security/keystore/recovery/SessionExpiredException.java
index 7fc2b05..8c18e41 100644
--- a/core/java/android/security/keystore/recovery/SessionExpiredException.java
+++ b/core/java/android/security/keystore/recovery/SessionExpiredException.java
@@ -16,12 +16,17 @@
package android.security.keystore.recovery;
+import android.annotation.SystemApi;
+
+import java.security.GeneralSecurityException;
+
/**
* Error thrown when attempting to use a {@link RecoverySession} that has since expired.
*
* @hide
*/
-public class SessionExpiredException extends RecoveryControllerException {
+@SystemApi
+public class SessionExpiredException extends GeneralSecurityException {
public SessionExpiredException(String msg) {
super(msg);
}
diff --git a/core/java/android/security/keystore/recovery/WrappedApplicationKey.java b/core/java/android/security/keystore/recovery/WrappedApplicationKey.java
index bca03b3..f360bbe9 100644
--- a/core/java/android/security/keystore/recovery/WrappedApplicationKey.java
+++ b/core/java/android/security/keystore/recovery/WrappedApplicationKey.java
@@ -17,6 +17,8 @@
package android.security.keystore.recovery;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
import android.os.Parcel;
import android.os.Parcelable;
@@ -27,6 +29,7 @@
*
* <ul>
* <li>Alias - Keystore alias of the key.
+ * <li>Account Recovery Agent specific account associated with the key.
* <li>Encrypted key material.
* </ul>
*
@@ -35,17 +38,18 @@
*
* @hide
*/
+@SystemApi
public final class WrappedApplicationKey implements Parcelable {
private String mAlias;
// The only supported format is AES-256 symmetric key.
private byte[] mEncryptedKeyMaterial;
+ private byte[] mAccount;
/**
* Builder for creating {@link WrappedApplicationKey}.
*/
public static class Builder {
- private WrappedApplicationKey
- mInstance = new WrappedApplicationKey();
+ private WrappedApplicationKey mInstance = new WrappedApplicationKey();
/**
* Sets Application-specific alias of the key.
@@ -59,6 +63,17 @@
}
/**
+ * Sets Recovery agent specific account.
+ *
+ * @param account The account.
+ * @return This builder.
+ */
+ public Builder setAccount(@NonNull byte[] account) {
+ mInstance.mAccount = account;
+ return this;
+ }
+
+ /**
* Sets key material encrypted by recovery key.
*
* @param encryptedKeyMaterial The key material
@@ -79,12 +94,14 @@
@NonNull public WrappedApplicationKey build() {
Preconditions.checkNotNull(mInstance.mAlias);
Preconditions.checkNotNull(mInstance.mEncryptedKeyMaterial);
+ if (mInstance.mAccount == null) {
+ mInstance.mAccount = new byte[]{};
+ }
return mInstance;
}
}
private WrappedApplicationKey() {
-
}
/**
@@ -110,8 +127,16 @@
return mEncryptedKeyMaterial;
}
- public static final Creator<WrappedApplicationKey> CREATOR =
- new Creator<WrappedApplicationKey>() {
+ /** Account, default value is empty array */
+ public @NonNull byte[] getAccount() {
+ if (mAccount == null) {
+ return new byte[]{};
+ }
+ return mAccount;
+ }
+
+ public static final Parcelable.Creator<WrappedApplicationKey> CREATOR =
+ new Parcelable.Creator<WrappedApplicationKey>() {
public WrappedApplicationKey createFromParcel(Parcel in) {
return new WrappedApplicationKey(in);
}
@@ -121,13 +146,11 @@
}
};
- /**
- * @hide
- */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(mAlias);
out.writeByteArray(mEncryptedKeyMaterial);
+ out.writeByteArray(mAccount);
}
/**
@@ -136,6 +159,7 @@
protected WrappedApplicationKey(Parcel in) {
mAlias = in.readString();
mEncryptedKeyMaterial = in.createByteArray();
+ mAccount = in.createByteArray();
}
@Override
diff --git a/core/java/android/service/autofill/AutofillFieldClassificationService.java b/core/java/android/service/autofill/AutofillFieldClassificationService.java
index df63a91..1ef6100 100644
--- a/core/java/android/service/autofill/AutofillFieldClassificationService.java
+++ b/core/java/android/service/autofill/AutofillFieldClassificationService.java
@@ -172,6 +172,7 @@
* {@hide}
*/
public static final class Scores implements Parcelable {
+ @NonNull
public final float[][] scores;
private Scores(Parcel parcel) {
@@ -185,11 +186,23 @@
}
}
- private Scores(float[][] scores) {
+ private Scores(@NonNull float[][] scores) {
this.scores = scores;
}
@Override
+ public String toString() {
+ final int size1 = scores.length;
+ final int size2 = size1 > 0 ? scores[0].length : 0;
+ final StringBuilder builder = new StringBuilder("Scores [")
+ .append(size1).append("x").append(size2).append("] ");
+ for (int i = 0; i < size1; i++) {
+ builder.append(i).append(": ").append(Arrays.toString(scores[i])).append(' ');
+ }
+ return builder.toString();
+ }
+
+ @Override
public int describeContents() {
return 0;
}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 2a245d0..99e2c62 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -17,6 +17,7 @@
import android.annotation.IdRes;
import android.annotation.LayoutRes;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -54,7 +55,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.List;
/**
* Extend this class to implement a custom dream (available to the user as a "Daydream").
@@ -458,8 +458,16 @@
* was processed in {@link #onCreate}.
*
* <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p>
+ * <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 if found or null otherwise.
+ * @see View#findViewById(int)
+ * @see DreamService#requireViewById(int)
*/
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
@@ -467,6 +475,33 @@
}
/**
+ * Finds a view that was identified by the id attribute from the XML that was processed in
+ * {@link #onCreate}, or throws an IllegalArgumentException if the ID is invalid or there is no
+ * matching view in the hierarchy.
+ *
+ * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p>
+ * <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 a view with given ID
+ * @see View#requireViewById(int)
+ * @see DreamService#findViewById(int)
+ */
+ @NonNull
+ public final <T extends View> T requireViewById(@IdRes int id) {
+ T view = findViewById(id);
+ if (view == null) {
+ throw new IllegalArgumentException(
+ "ID does not reference a View inside this DreamService");
+ }
+ return view;
+ }
+
+ /**
* Marks this dream as interactive to receive input events.
*
* <p>Non-interactive dreams (default) will dismiss on the first input event.</p>
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 70d6486..80d7ef5 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -928,8 +928,6 @@
}
}
- // The parameters that are not changed in the method are marked as final to make the code
- // easier to understand.
private int out(final CharSequence text, final int start, final int end, int above, int below,
int top, int bottom, int v, final float spacingmult, final float spacingadd,
final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
@@ -958,21 +956,29 @@
mLineDirections = grow;
}
- lines[off + START] = start;
- lines[off + TOP] = v;
+ if (chooseHt != null) {
+ fm.ascent = above;
+ fm.descent = below;
+ fm.top = top;
+ fm.bottom = bottom;
- // Information about hyphenation, tabs, and directions are needed for determining
- // ellipsization, so the values should be assigned before ellipsization.
+ for (int i = 0; i < chooseHt.length; i++) {
+ if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
+ ((LineHeightSpan.WithDensity) chooseHt[i])
+ .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
+ } else {
+ chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
+ }
+ }
- // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
- // one bit for start field
- lines[off + TAB] |= flags & TAB_MASK;
- lines[off + HYPHEN] = flags;
- lines[off + DIR] |= dir << DIR_SHIFT;
- mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
+ above = fm.ascent;
+ below = fm.descent;
+ top = fm.top;
+ bottom = fm.bottom;
+ }
- final boolean firstLine = (j == 0);
- final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
+ boolean firstLine = (j == 0);
+ boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
if (ellipsize != null) {
// If there is only one line, then do any type of ellipsis except when it is MARQUEE
@@ -985,9 +991,9 @@
(!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
ellipsize == TextUtils.TruncateAt.END);
if (doEllipsis) {
- calculateEllipsis(text, start, end, widths, widthStart,
- ellipsisWidth - getTotalInsets(j), ellipsize, j,
- textWidth, paint, forceEllipsis, dir);
+ calculateEllipsis(start, end, widths, widthStart,
+ ellipsisWidth, ellipsize, j,
+ textWidth, paint, forceEllipsis);
}
}
@@ -1006,28 +1012,6 @@
}
}
- if (chooseHt != null) {
- fm.ascent = above;
- fm.descent = below;
- fm.top = top;
- fm.bottom = bottom;
-
- for (int i = 0; i < chooseHt.length; i++) {
- if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
- ((LineHeightSpan.WithDensity) chooseHt[i])
- .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
-
- } else {
- chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
- }
- }
-
- above = fm.ascent;
- below = fm.descent;
- top = fm.top;
- bottom = fm.bottom;
- }
-
if (firstLine) {
if (trackPad) {
mTopPadding = top - above;
@@ -1038,6 +1022,8 @@
}
}
+ int extra;
+
if (lastLine) {
if (trackPad) {
mBottomPadding = bottom - below;
@@ -1048,9 +1034,8 @@
}
}
- final int extra;
if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
- final double ex = (below - above) * (spacingmult - 1) + spacingadd;
+ double ex = (below - above) * (spacingmult - 1) + spacingadd;
if (ex >= 0) {
extra = (int)(ex + EXTRA_ROUNDING);
} else {
@@ -1060,6 +1045,8 @@
extra = 0;
}
+ lines[off + START] = start;
+ lines[off + TOP] = v;
lines[off + DESCENT] = below + extra;
lines[off + EXTRA] = extra;
@@ -1067,7 +1054,7 @@
// store the height as if it was ellipsized
if (!mEllipsized && currentLineIsTheLastVisibleOne) {
// below calculation as if it was the last line
- final int maxLineBelow = includePad ? bottom : below;
+ int maxLineBelow = includePad ? bottom : below;
// similar to the calculation of v below, without the extra.
mMaxLineHeight = v + (maxLineBelow - above);
}
@@ -1076,13 +1063,23 @@
lines[off + mColumns + START] = end;
lines[off + mColumns + TOP] = v;
+ // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
+ // one bit for start field
+ lines[off + TAB] |= flags & TAB_MASK;
+ lines[off + HYPHEN] = flags;
+ lines[off + DIR] |= dir << DIR_SHIFT;
+ mLineDirections[j] = measured.getDirections(start - widthStart, end - widthStart);
+
mLineCount++;
return v;
}
- private void calculateEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
- int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
- TextPaint paint, boolean forceEllipsis, int dir) {
+ private void calculateEllipsis(int lineStart, int lineEnd,
+ float[] widths, int widthStart,
+ float avail, TextUtils.TruncateAt where,
+ int line, float textWidth, TextPaint paint,
+ boolean forceEllipsis) {
+ avail -= getTotalInsets(line);
if (textWidth <= avail && !forceEllipsis) {
// Everything fits!
mLines[mColumns * line + ELLIPSIS_START] = 0;
@@ -1090,53 +1087,11 @@
return;
}
- float tempAvail = avail;
- int numberOfTries = 0;
- boolean lineFits = false;
- mWorkPaint.set(paint);
- do {
- final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths,
- widthStart, tempAvail, where, line, mWorkPaint, forceEllipsis, dir);
- if (ellipsizedWidth <= avail) {
- lineFits = true;
- } else {
- numberOfTries++;
- if (numberOfTries > 10) {
- // If the text still doesn't fit after ten tries, assume it will never fit and
- // ellipsize it all.
- mLines[mColumns * line + ELLIPSIS_START] = 0;
- mLines[mColumns * line + ELLIPSIS_COUNT] = lineEnd - lineStart;
- lineFits = true;
- } else {
- // Some side effect of ellipsization has caused the text to go over the
- // available width. Let's make the available width shorter by exactly that
- // amount and retry.
- tempAvail -= ellipsizedWidth - avail;
- }
- }
- } while (!lineFits);
- mEllipsized = true;
- }
+ float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
+ int ellipsisStart = 0;
+ int ellipsisCount = 0;
+ int len = lineEnd - lineStart;
- // Returns the width of the ellipsized line which in some rare cases can actually be larger
- // than 'avail' (due to kerning or other context-based effect of replacement of text by
- // ellipsis). If all the line needs to ellipsized away, or it's an invalud hyphenation mode,
- // returns 0 so the caller can stop iterating.
- //
- // This method temporarily modifies the TextPaint passed to it, so the TextPaint passed to it
- // should not be accessed while the method is running.
- private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
- int widthStart, float avail, TextUtils.TruncateAt where, int line,
- TextPaint paint, boolean forceEllipsis, int dir) {
- final int savedHyphenEdit = paint.getHyphenEdit();
- paint.setHyphenEdit(0);
- final float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
- final int ellipsisStart;
- final int ellipsisCount;
- final int len = lineEnd - lineStart;
- final int offset = lineStart - widthStart;
-
- int hyphen = getHyphen(line);
// We only support start ellipsis on a single line
if (where == TextUtils.TruncateAt.START) {
if (mMaximumVisibleLineCount == 1) {
@@ -1144,9 +1099,9 @@
int i;
for (i = len; i > 0; i--) {
- final float w = widths[i - 1 + offset];
+ float w = widths[i - 1 + lineStart - widthStart];
if (w + sum + ellipsisWidth > avail) {
- while (i < len && widths[i + offset] == 0.0f) {
+ while (i < len && widths[i + lineStart - widthStart] == 0.0f) {
i++;
}
break;
@@ -1157,13 +1112,9 @@
ellipsisStart = 0;
ellipsisCount = i;
- // Strip the potential hyphenation at beginning of line.
- hyphen &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE;
} else {
- ellipsisStart = 0;
- ellipsisCount = 0;
if (Log.isLoggable(TAG, Log.WARN)) {
- Log.w(TAG, "Start ellipsis only supported with one line");
+ Log.w(TAG, "Start Ellipsis only supported with one line");
}
}
} else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
@@ -1172,7 +1123,7 @@
int i;
for (i = 0; i < len; i++) {
- final float w = widths[i + offset];
+ float w = widths[i + lineStart - widthStart];
if (w + sum + ellipsisWidth > avail) {
break;
@@ -1181,27 +1132,24 @@
sum += w;
}
- if (forceEllipsis && i == len && len > 0) {
+ ellipsisStart = i;
+ ellipsisCount = len - i;
+ if (forceEllipsis && ellipsisCount == 0 && len > 0) {
ellipsisStart = len - 1;
ellipsisCount = 1;
- } else {
- ellipsisStart = i;
- ellipsisCount = len - i;
}
- // Strip the potential hyphenation at end of line.
- hyphen &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE;
- } else { // where = TextUtils.TruncateAt.MIDDLE
- // We only support middle ellipsis on a single line.
+ } else {
+ // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
if (mMaximumVisibleLineCount == 1) {
float lsum = 0, rsum = 0;
int left = 0, right = len;
- final float ravail = (avail - ellipsisWidth) / 2;
+ float ravail = (avail - ellipsisWidth) / 2;
for (right = len; right > 0; right--) {
- final float w = widths[right - 1 + offset];
+ float w = widths[right - 1 + lineStart - widthStart];
if (w + rsum > ravail) {
- while (right < len && widths[right + offset] == 0.0f) {
+ while (right < len && widths[right + lineStart - widthStart] == 0.0f) {
right++;
}
break;
@@ -1209,9 +1157,9 @@
rsum += w;
}
- final float lavail = avail - ellipsisWidth - rsum;
+ float lavail = avail - ellipsisWidth - rsum;
for (left = 0; left < right; left++) {
- final float w = widths[left + offset];
+ float w = widths[left + lineStart - widthStart];
if (w + lsum > lavail) {
break;
@@ -1223,53 +1171,14 @@
ellipsisStart = left;
ellipsisCount = right - left;
} else {
- ellipsisStart = 0;
- ellipsisCount = 0;
if (Log.isLoggable(TAG, Log.WARN)) {
- Log.w(TAG, "Middle ellipsis only supported with one line");
+ Log.w(TAG, "Middle Ellipsis only supported with one line");
}
}
}
+ mEllipsized = true;
mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
-
- if (ellipsisStart == 0 && (ellipsisCount == 0 || ellipsisCount == len)) {
- // Unsupported ellipsization mode or all text is ellipsized away. Return 0.
- return 0.0f;
- }
-
- final boolean isSpanned = text instanceof Spanned;
- final Ellipsizer ellipsizedText = isSpanned
- ? new SpannedEllipsizer(text)
- : new Ellipsizer(text);
- ellipsizedText.mLayout = this;
- ellipsizedText.mMethod = where;
-
- final boolean hasTabs = getLineContainsTab(line);
- final TabStops tabStops;
- if (hasTabs && isSpanned) {
- final TabStopSpan[] tabs = getParagraphSpans((Spanned) ellipsizedText, lineStart,
- lineEnd, TabStopSpan.class);
- if (tabs.length == 0) {
- tabStops = null;
- } else {
- tabStops = new TabStops(TAB_INCREMENT, tabs);
- }
- } else {
- tabStops = null;
- }
- paint.setHyphenEdit(hyphen);
- final TextLine textline = TextLine.obtain();
- textline.set(paint, ellipsizedText, lineStart, lineEnd, dir, getLineDirections(line),
- hasTabs, tabStops);
- // Since TextLine.metric() returns negative values for RTL text, multiplication by dir
- // converts it to an actual width. Note that we don't want to use the absolute value,
- // since we may actually have glyphs with negative advances, which by definition always
- // fit.
- final float ellipsizedWidth = textline.metrics(null) * dir;
- TextLine.recycle(textline);
- paint.setHyphenEdit(savedHyphenEdit);
- return ellipsizedWidth;
}
private float getTotalInsets(int line) {
@@ -1509,8 +1418,6 @@
*/
private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
- private TextPaint mWorkPaint = new TextPaint();
-
private static final int COLUMNS_NORMAL = 5;
private static final int COLUMNS_ELLIPSIZE = 7;
private static final int START = 0;
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 409e514..af0eebf 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -88,8 +88,8 @@
/** {@hide} */
@NonNull
- public static String getEllipsisString(@NonNull TruncateAt method) {
- return (method == TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL;
+ public static String getEllipsisString(@NonNull TextUtils.TruncateAt method) {
+ return (method == TextUtils.TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL;
}
@@ -1194,11 +1194,9 @@
* or, if it does not fit, a truncated
* copy with ellipsis character added at the specified edge or center.
*/
- @NonNull
- public static CharSequence ellipsize(@NonNull CharSequence text,
- @NonNull TextPaint p,
- @FloatRange(from = 0.0) float avail,
- @NonNull TruncateAt where) {
+ public static CharSequence ellipsize(CharSequence text,
+ TextPaint p,
+ float avail, TruncateAt where) {
return ellipsize(text, p, avail, where, false, null);
}
@@ -1214,11 +1212,9 @@
* report the start and end of the ellipsized range. TextDirection
* is determined by the first strong directional character.
*/
- @NonNull
- public static CharSequence ellipsize(@NonNull CharSequence text,
- @NonNull TextPaint paint,
- @FloatRange(from = 0.0) float avail,
- @NonNull TruncateAt where,
+ public static CharSequence ellipsize(CharSequence text,
+ TextPaint paint,
+ float avail, TruncateAt where,
boolean preserveLength,
@Nullable EllipsizeCallback callback) {
return ellipsize(text, paint, avail, where, preserveLength, callback,
@@ -1239,19 +1235,16 @@
*
* @hide
*/
- @NonNull
- public static CharSequence ellipsize(@NonNull CharSequence text,
- @NonNull TextPaint paint,
- @FloatRange(from = 0.0) float avail,
- @NonNull TruncateAt where,
+ public static CharSequence ellipsize(CharSequence text,
+ TextPaint paint,
+ float avail, TruncateAt where,
boolean preserveLength,
@Nullable EllipsizeCallback callback,
- @NonNull TextDirectionHeuristic textDir,
- @NonNull String ellipsis) {
+ TextDirectionHeuristic textDir, String ellipsis) {
- final int len = text.length();
+ int len = text.length();
+
MeasuredParagraph mt = null;
- MeasuredParagraph resultMt = null;
try {
mt = MeasuredParagraph.buildForMeasurement(paint, text, 0, text.length(), textDir, mt);
float width = mt.getWholeWidth();
@@ -1260,110 +1253,76 @@
if (callback != null) {
callback.ellipsized(0, 0);
}
+
return text;
}
- // First estimate of effective width of ellipsis.
- float ellipsisWidth = paint.measureText(ellipsis);
- int numberOfTries = 0;
- boolean textFits = false;
- int start, end;
- CharSequence result;
- do {
- if (avail < ellipsisWidth) {
- // Even the ellipsis can't fit. So it all goes.
- start = 0;
- end = len;
- } else {
- final float remainingWidth = avail - ellipsisWidth;
- if (where == TruncateAt.START) {
- start = 0;
- end = len - mt.breakText(len, false /* backwards */, remainingWidth);
- } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) {
- start = mt.breakText(len, true /* forwards */, remainingWidth);
- end = len;
- } else {
- end = len - mt.breakText(len, false /* backwards */, remainingWidth / 2);
- start = mt.breakText(end, true /* forwards */,
- remainingWidth - mt.measure(end, len));
- }
- }
+ // XXX assumes ellipsis string does not require shaping and
+ // is unaffected by style
+ float ellipsiswid = paint.measureText(ellipsis);
+ avail -= ellipsiswid;
- final char[] buf = mt.getChars();
- final Spanned sp = text instanceof Spanned ? (Spanned) text : null;
-
- final int removed = end - start;
- final int remaining = len - removed;
- if (preserveLength) {
- int pos = start;
- if (remaining > 0 && removed >= ellipsis.length()) {
- ellipsis.getChars(0, ellipsis.length(), buf, start);
- pos += ellipsis.length();
- } // else eliminate the ellipsis
- while (pos < end) {
- buf[pos++] = ELLIPSIS_FILLER;
- }
- final String s = new String(buf, 0, len);
- if (sp == null) {
- result = s;
- } else {
- final SpannableString ss = new SpannableString(s);
- copySpansFrom(sp, 0, len, Object.class, ss, 0);
- result = ss;
- }
- } else {
- if (remaining == 0) {
- result = "";
- } else if (sp == null) {
- final StringBuilder sb = new StringBuilder(remaining + ellipsis.length());
- sb.append(buf, 0, start);
- sb.append(ellipsis);
- sb.append(buf, end, len - end);
- result = sb.toString();
- } else {
- final SpannableStringBuilder ssb = new SpannableStringBuilder();
- ssb.append(text, 0, start);
- ssb.append(ellipsis);
- ssb.append(text, end, len);
- result = ssb;
- }
- }
-
- if (remaining == 0) { // All text is gone.
- textFits = true;
- } else {
- resultMt = MeasuredParagraph.buildForMeasurement(
- paint, result, 0, result.length(), textDir, resultMt);
- width = resultMt.getWholeWidth();
- if (width <= avail) {
- textFits = true;
- } else {
- numberOfTries++;
- if (numberOfTries > 10) {
- // If the text still doesn't fit after ten tries, assume it will never
- // fit and ellipsize it all. We do this by setting the width of the
- // ellipsis to be positive infinity, so we get to empty text in the next
- // round.
- ellipsisWidth = Float.POSITIVE_INFINITY;
- } else {
- // Adjust the width of the ellipsis by adding the amount 'width' is
- // still over.
- ellipsisWidth += width - avail;
- }
- }
- }
- } while (!textFits);
- if (callback != null) {
- callback.ellipsized(start, end);
+ int left = 0;
+ int right = len;
+ if (avail < 0) {
+ // it all goes
+ } else if (where == TruncateAt.START) {
+ right = len - mt.breakText(len, false, avail);
+ } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) {
+ left = mt.breakText(len, true, avail);
+ } else {
+ right = len - mt.breakText(len, false, avail / 2);
+ avail -= mt.measure(right, len);
+ left = mt.breakText(right, true, avail);
}
- return result;
+
+ if (callback != null) {
+ callback.ellipsized(left, right);
+ }
+
+ final char[] buf = mt.getChars();
+ Spanned sp = text instanceof Spanned ? (Spanned) text : null;
+
+ final int removed = right - left;
+ final int remaining = len - removed;
+ if (preserveLength) {
+ if (remaining > 0 && removed >= ellipsis.length()) {
+ ellipsis.getChars(0, ellipsis.length(), buf, left);
+ left += ellipsis.length();
+ } // else skip the ellipsis
+ for (int i = left; i < right; i++) {
+ buf[i] = ELLIPSIS_FILLER;
+ }
+ String s = new String(buf, 0, len);
+ if (sp == null) {
+ return s;
+ }
+ SpannableString ss = new SpannableString(s);
+ copySpansFrom(sp, 0, len, Object.class, ss, 0);
+ return ss;
+ }
+
+ if (remaining == 0) {
+ return "";
+ }
+
+ if (sp == null) {
+ StringBuilder sb = new StringBuilder(remaining + ellipsis.length());
+ sb.append(buf, 0, left);
+ sb.append(ellipsis);
+ sb.append(buf, right, len - right);
+ return sb.toString();
+ }
+
+ SpannableStringBuilder ssb = new SpannableStringBuilder();
+ ssb.append(text, 0, left);
+ ssb.append(ellipsis);
+ ssb.append(text, right, len);
+ return ssb;
} finally {
if (mt != null) {
mt.recycle();
}
- if (resultMt != null) {
- resultMt.recycle();
- }
}
}
@@ -1394,6 +1353,7 @@
* @return the formatted CharSequence. If even the shortest sequence (e.g. {@code "A, 11 more"})
* doesn't fit, it will return an empty string.
*/
+
public static CharSequence listEllipsize(@Nullable Context context,
@Nullable List<CharSequence> elements, @NonNull String separator,
@NonNull TextPaint paint, @FloatRange(from=0.0,fromInclusive=false) float avail,
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 9f9033c..25a177e 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -40,12 +40,13 @@
DEFAULT_FLAGS.put("device_info_v2", "true");
DEFAULT_FLAGS.put("settings_app_info_v2", "true");
DEFAULT_FLAGS.put("settings_connected_device_v2", "true");
- DEFAULT_FLAGS.put("settings_battery_v2", "false");
+ DEFAULT_FLAGS.put("settings_battery_v2", "true");
DEFAULT_FLAGS.put("settings_battery_display_app_list", "false");
DEFAULT_FLAGS.put("settings_security_settings_v2", "true");
DEFAULT_FLAGS.put("settings_zone_picker_v2", "true");
DEFAULT_FLAGS.put("settings_suggestion_ui_v2", "false");
DEFAULT_FLAGS.put("settings_about_phone_v2", "false");
+ DEFAULT_FLAGS.put("settings_bluetooth_while_driving", "false");
}
/**
diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java
index e2e9d53..a5e3818 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -105,7 +105,7 @@
* @param data The data.
* @return The digest or null if an error occurs.
*/
- public static @Nullable String computeSha256Digest(@NonNull byte[] data) {
+ public static @Nullable byte[] computeSha256DigestBytes(@NonNull byte[] data) {
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance("SHA256");
@@ -116,6 +116,15 @@
messageDigest.update(data);
- return ByteStringUtils.toHexString(messageDigest.digest());
+ return messageDigest.digest();
+ }
+
+ /**
+ * Computes the SHA256 digest of some data.
+ * @param data The data.
+ * @return The digest or null if an error occurs.
+ */
+ public static @Nullable String computeSha256Digest(@NonNull byte[] data) {
+ return ByteStringUtils.toHexString(computeSha256DigestBytes(data));
}
}
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index a2a7616..8794372 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -80,10 +80,22 @@
ApkSignatureSchemeV3Verifier.verify(apkPath);
Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
Signature[] signerSigs = convertToSignatures(signerCerts);
- return new PackageParser.SigningDetails(signerSigs,
- SignatureSchemeVersion.SIGNING_BLOCK_V3);
+ Signature[] pastSignerSigs = null;
+ int[] pastSignerSigsFlags = null;
+ if (vSigner.por != null) {
+ // populate proof-of-rotation information
+ pastSignerSigs = new Signature[vSigner.por.certs.size()];
+ pastSignerSigsFlags = new int[vSigner.por.flagsList.size()];
+ for (int i = 0; i < pastSignerSigs.length; i++) {
+ pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
+ pastSignerSigsFlags[i] = vSigner.por.flagsList.get(i);
+ }
+ }
+ return new PackageParser.SigningDetails(
+ signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ pastSignerSigs, pastSignerSigsFlags);
} catch (SignatureNotFoundException e) {
- // not signed with v2, try older if allowed
+ // not signed with v3, try older if allowed
if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No APK Signature Scheme v3 signature in package " + apkPath, e);
@@ -92,7 +104,7 @@
// APK Signature Scheme v2 signature found but did not verify
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"Failed to collect certificates from " + apkPath
- + " using APK Signature Scheme v2", e);
+ + " using APK Signature Scheme v3", e);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -304,25 +316,37 @@
}
// first try v3
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3");
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV3");
try {
ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
ApkSignatureSchemeV3Verifier.plsCertsNoVerifyOnlyCerts(apkPath);
Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
Signature[] signerSigs = convertToSignatures(signerCerts);
- return new PackageParser.SigningDetails(signerSigs,
- SignatureSchemeVersion.SIGNING_BLOCK_V3);
+ Signature[] pastSignerSigs = null;
+ int[] pastSignerSigsFlags = null;
+ if (vSigner.por != null) {
+ // populate proof-of-rotation information
+ pastSignerSigs = new Signature[vSigner.por.certs.size()];
+ pastSignerSigsFlags = new int[vSigner.por.flagsList.size()];
+ for (int i = 0; i < pastSignerSigs.length; i++) {
+ pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
+ pastSignerSigsFlags[i] = vSigner.por.flagsList.get(i);
+ }
+ }
+ return new PackageParser.SigningDetails(
+ signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ pastSignerSigs, pastSignerSigsFlags);
} catch (SignatureNotFoundException e) {
- // not signed with v2, try older if allowed
+ // not signed with v3, try older if allowed
if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No APK Signature Scheme v3 signature in package " + apkPath, e);
}
} catch (Exception e) {
- // APK Signature Scheme v2 signature found but did not verify
+ // APK Signature Scheme v3 signature found but did not verify
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"Failed to collect certificates from " + apkPath
- + " using APK Signature Scheme v2", e);
+ + " using APK Signature Scheme v3", e);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
diff --git a/core/java/android/util/apk/ApkVerityBuilder.java b/core/java/android/util/apk/ApkVerityBuilder.java
index a0d5e4c..5880c6a 100644
--- a/core/java/android/util/apk/ApkVerityBuilder.java
+++ b/core/java/android/util/apk/ApkVerityBuilder.java
@@ -68,31 +68,78 @@
static ApkVerityResult generateApkVerity(RandomAccessFile apk,
SignatureInfo signatureInfo, ByteBufferFactory bufferFactory)
throws IOException, SecurityException, NoSuchAlgorithmException, DigestException {
+ long signingBlockSize =
+ signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset;
+ long dataSize = apk.length() - signingBlockSize - ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
+ int[] levelOffset = calculateVerityLevelOffset(dataSize);
+
+ ByteBuffer output = bufferFactory.create(
+ CHUNK_SIZE_BYTES + // fsverity header + extensions + padding
+ levelOffset[levelOffset.length - 1]); // Merkle tree size
+ output.order(ByteOrder.LITTLE_ENDIAN);
+
+ ByteBuffer header = slice(output, 0, FSVERITY_HEADER_SIZE_BYTES);
+ ByteBuffer extensions = slice(output, FSVERITY_HEADER_SIZE_BYTES, CHUNK_SIZE_BYTES);
+ ByteBuffer tree = slice(output, CHUNK_SIZE_BYTES, output.limit());
+ byte[] apkDigestBytes = new byte[DIGEST_SIZE_BYTES];
+ ByteBuffer apkDigest = ByteBuffer.wrap(apkDigestBytes);
+ apkDigest.order(ByteOrder.LITTLE_ENDIAN);
+
+ calculateFsveritySignatureInternal(apk, signatureInfo, tree, apkDigest, header, extensions);
+
+ output.rewind();
+ return new ApkVerityResult(output, apkDigestBytes);
+ }
+
+ /**
+ * Calculates the fsverity root hash for integrity measurement. This needs to be consistent to
+ * what kernel returns.
+ */
+ static byte[] generateFsverityRootHash(RandomAccessFile apk, ByteBuffer apkDigest,
+ SignatureInfo signatureInfo)
+ throws NoSuchAlgorithmException, DigestException, IOException {
+ ByteBuffer verityBlock = ByteBuffer.allocate(CHUNK_SIZE_BYTES)
+ .order(ByteOrder.LITTLE_ENDIAN);
+ ByteBuffer header = slice(verityBlock, 0, FSVERITY_HEADER_SIZE_BYTES);
+ ByteBuffer extensions = slice(verityBlock, FSVERITY_HEADER_SIZE_BYTES, CHUNK_SIZE_BYTES);
+
+ calculateFsveritySignatureInternal(apk, signatureInfo, null, null, header, extensions);
+
+ MessageDigest md = MessageDigest.getInstance(JCA_DIGEST_ALGORITHM);
+ md.update(DEFAULT_SALT);
+ md.update(verityBlock);
+ md.update(apkDigest);
+ return md.digest();
+ }
+
+ private static void calculateFsveritySignatureInternal(
+ RandomAccessFile apk, SignatureInfo signatureInfo, ByteBuffer treeOutput,
+ ByteBuffer rootHashOutput, ByteBuffer headerOutput, ByteBuffer extensionsOutput)
+ throws IOException, NoSuchAlgorithmException, DigestException {
assertSigningBlockAlignedAndHasFullPages(signatureInfo);
long signingBlockSize =
signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset;
long dataSize = apk.length() - signingBlockSize - ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
int[] levelOffset = calculateVerityLevelOffset(dataSize);
- ByteBuffer output = bufferFactory.create(
- CHUNK_SIZE_BYTES + // fsverity header + extensions + padding
- levelOffset[levelOffset.length - 1] + // Merkle tree size
- FSVERITY_HEADER_SIZE_BYTES); // second fsverity header (verbatim copy)
- // Start generating the tree from the block boundary as the kernel will expect.
- ByteBuffer treeOutput = slice(output, CHUNK_SIZE_BYTES,
- output.limit() - FSVERITY_HEADER_SIZE_BYTES);
- byte[] rootHash = generateApkVerityTree(apk, signatureInfo, DEFAULT_SALT, levelOffset,
- treeOutput);
+ if (treeOutput != null) {
+ byte[] apkRootHash = generateApkVerityTree(apk, signatureInfo, DEFAULT_SALT,
+ levelOffset, treeOutput);
+ if (rootHashOutput != null) {
+ rootHashOutput.put(apkRootHash);
+ }
+ }
- ByteBuffer integrityHeader = generateFsverityHeader(apk.length(), DEFAULT_SALT);
- output.put(integrityHeader);
- output.put(generateFsverityExtensions());
+ if (headerOutput != null) {
+ generateFsverityHeader(headerOutput, apk.length(), levelOffset.length - 1,
+ DEFAULT_SALT);
+ }
- integrityHeader.rewind();
- output.put(integrityHeader);
- output.rewind();
- return new ApkVerityResult(output, rootHash);
+ if (extensionsOutput != null) {
+ generateFsverityExtensions(extensionsOutput, signatureInfo.apkSigningBlockOffset,
+ signingBlockSize, signatureInfo.eocdOffset);
+ }
}
/**
@@ -211,7 +258,7 @@
eocdCdOffsetFieldPosition - signatureInfo.centralDirOffset),
MMAP_REGION_SIZE_BYTES);
- // 3. Fill up the rest of buffer with 0s.
+ // 3. Consume offset of Signing Block as an alternative EoCD.
ByteBuffer alternativeCentralDirOffset = ByteBuffer.allocate(
ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE).order(ByteOrder.LITTLE_ENDIAN);
alternativeCentralDirOffset.putInt(Math.toIntExact(signatureInfo.apkSigningBlockOffset));
@@ -259,36 +306,109 @@
return rootHash;
}
- private static ByteBuffer generateFsverityHeader(long fileSize, byte[] salt) {
+ private static ByteBuffer generateFsverityHeader(ByteBuffer buffer, long fileSize, int depth,
+ byte[] salt) {
if (salt.length != 8) {
throw new IllegalArgumentException("salt is not 8 bytes long");
}
- ByteBuffer buffer = ByteBuffer.allocate(FSVERITY_HEADER_SIZE_BYTES);
- buffer.order(ByteOrder.LITTLE_ENDIAN);
-
- // TODO(b/30972906): insert a reference when there is a public one.
+ // TODO(b/30972906): update the reference when there is a better one in public.
buffer.put("TrueBrew".getBytes()); // magic
+
buffer.put((byte) 1); // major version
buffer.put((byte) 0); // minor version
- buffer.put((byte) 12); // log2(block-size) == log2(4096)
- buffer.put((byte) 7); // log2(leaves-per-node) == log2(block-size / digest-size)
- // == log2(4096 / 32)
- buffer.putShort((short) 1); // meta algorithm, 1: SHA-256 FIXME finalize constant
- buffer.putShort((short) 1); // data algorithm, 1: SHA-256 FIXME finalize constant
- buffer.putInt(0x1); // flags, 0x1: has extension, FIXME also hide it
- buffer.putInt(0); // reserved
- buffer.putLong(fileSize); // original i_size
- buffer.put(salt); // salt (8 bytes)
+ buffer.put((byte) 12); // log2(block-size): log2(4096)
+ buffer.put((byte) 7); // log2(leaves-per-node): log2(4096 / 32)
- // TODO(b/30972906): Add extension.
+ buffer.putShort((short) 1); // meta algorithm, SHA256_MODE == 1
+ buffer.putShort((short) 1); // data algorithm, SHA256_MODE == 1
+
+ buffer.putInt(0x1); // flags, 0x1: has extension
+ buffer.putInt(0); // reserved
+
+ buffer.putLong(fileSize); // original file size
+
+ buffer.put((byte) 0); // auth block offset, disabled here
+ buffer.put(salt); // salt (8 bytes)
+ // skip(buffer, 22); // reserved
buffer.rewind();
return buffer;
}
- private static ByteBuffer generateFsverityExtensions() {
- return ByteBuffer.allocate(64); // TODO(b/30972906): implement this.
+ private static ByteBuffer generateFsverityExtensions(ByteBuffer buffer, long signingBlockOffset,
+ long signingBlockSize, long eocdOffset) {
+ // Snapshot of the FSVerity structs (subject to change once upstreamed).
+ //
+ // struct fsverity_header_extension {
+ // u8 extension_count;
+ // u8 reserved[7];
+ // };
+ //
+ // struct fsverity_extension {
+ // __le16 length;
+ // u8 type;
+ // u8 reserved[5];
+ // };
+ //
+ // struct fsverity_extension_elide {
+ // __le64 offset;
+ // __le64 length;
+ // }
+ //
+ // struct fsverity_extension_patch {
+ // __le64 offset;
+ // u8 length;
+ // u8 reserved[7];
+ // u8 databytes[];
+ // };
+
+ // struct fsverity_header_extension
+ buffer.put((byte) 2); // extension count
+ skip(buffer, 3); // reserved
+
+ final int kSizeOfFsverityExtensionHeader = 8;
+
+ {
+ // struct fsverity_extension #1
+ final int kSizeOfFsverityElidedExtension = 16;
+
+ buffer.putShort((short) // total size of extension, padded to 64-bit alignment
+ (kSizeOfFsverityExtensionHeader + kSizeOfFsverityElidedExtension));
+ buffer.put((byte) 0); // ID of elide extension
+ skip(buffer, 5); // reserved
+
+ // struct fsverity_extension_elide
+ buffer.putLong(signingBlockOffset);
+ buffer.putLong(signingBlockSize);
+ }
+
+ {
+ // struct fsverity_extension #2
+ final int kSizeOfFsverityPatchExtension =
+ 8 + // offset size
+ 1 + // size of length from offset (up to 255)
+ 7 + // reserved
+ ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
+ final int kPadding = (int) divideRoundup(kSizeOfFsverityPatchExtension % 8, 8);
+
+ buffer.putShort((short) // total size of extension, padded to 64-bit alignment
+ (kSizeOfFsverityExtensionHeader + kSizeOfFsverityPatchExtension + kPadding));
+ buffer.put((byte) 1); // ID of patch extension
+ skip(buffer, 5); // reserved
+
+ // struct fsverity_extension_patch
+ buffer.putLong(eocdOffset); // offset
+ buffer.put((byte) ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE); // length
+ skip(buffer, 7); // reserved
+ buffer.putInt(Math.toIntExact(signingBlockOffset)); // databytes
+
+ // There are extra kPadding bytes of 0s here, included in the total size field of the
+ // extension header. The output ByteBuffer is assumed to be initialized to 0.
+ }
+
+ buffer.rewind();
+ return buffer;
}
/**
@@ -344,6 +464,11 @@
return b.slice();
}
+ /** Skip the {@code ByteBuffer} position by {@code bytes}. */
+ private static void skip(ByteBuffer buffer, int bytes) {
+ buffer.position(buffer.position() + bytes);
+ }
+
/** Divides a number and round up to the closest integer. */
private static long divideRoundup(long dividend, long divisor) {
return (dividend + divisor - 1) / divisor;
diff --git a/core/java/android/util/proto/ProtoUtils.java b/core/java/android/util/proto/ProtoUtils.java
index 85b7ec8..c7bbb9f 100644
--- a/core/java/android/util/proto/ProtoUtils.java
+++ b/core/java/android/util/proto/ProtoUtils.java
@@ -48,4 +48,26 @@
proto.write(Duration.END_MS, endMs);
proto.end(token);
}
+
+ /**
+ * Helper function to write bit-wise flags to proto as repeated enums
+ * @hide
+ */
+ public static void writeBitWiseFlagsToProtoEnum(ProtoOutputStream proto, long fieldId,
+ int flags, int[] origEnums, int[] protoEnums) {
+ if (protoEnums.length != origEnums.length) {
+ throw new IllegalArgumentException("The length of origEnums must match protoEnums");
+ }
+ int len = origEnums.length;
+ for (int i = 0; i < len; i++) {
+ // handle zero flag case.
+ if (origEnums[i] == 0 && flags == 0) {
+ proto.write(fieldId, protoEnums[i]);
+ return;
+ }
+ if ((flags & origEnums[i]) != 0) {
+ proto.write(fieldId, protoEnums[i]);
+ }
+ }
+ }
}
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index ed2b8b6..a597405 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -806,8 +806,10 @@
public static final int KEYCODE_SYSTEM_NAVIGATION_RIGHT = 283;
/** Key code constant: Show all apps */
public static final int KEYCODE_ALL_APPS = 284;
+ /** Key code constant: Refresh key. */
+ public static final int KEYCODE_REFRESH = 285;
- private static final int LAST_KEYCODE = KEYCODE_ALL_APPS;
+ private static final int LAST_KEYCODE = KEYCODE_REFRESH;
// NOTE: If you add a new keycode here you must also add it to:
// isSystem()
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 05770c3..a2ecfc4 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -22209,7 +22209,7 @@
*
* @param id the ID to search for
* @return a view with given ID if found, or {@code null} otherwise
- * @see View#findViewById(int)
+ * @see View#requireViewById(int)
*/
@Nullable
public final <T extends View> T findViewById(@IdRes int id) {
@@ -22220,6 +22220,29 @@
}
/**
+ * Finds the first descendant view with the given ID, the view itself if the ID matches
+ * {@link #getId()}, or throws an IllegalArgumentException if the ID is invalid 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 a view with given ID
+ * @see View#findViewById(int)
+ */
+ @NonNull
+ public final <T extends View> T requireViewById(@IdRes int id) {
+ T view = findViewById(id);
+ if (view == null) {
+ throw new IllegalArgumentException("ID does not reference a View inside this View");
+ }
+ return view;
+ }
+
+ /**
* Finds a view by its unuque and stable accessibility id.
*
* @param accessibilityId The searched accessibility id.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index e0864bd..4631261 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -57,6 +57,7 @@
import android.view.animation.AnimationUtils;
import android.view.animation.LayoutAnimationController;
import android.view.animation.Transformation;
+import android.view.autofill.Helper;
import com.android.internal.R;
@@ -3474,8 +3475,10 @@
}
if (!isLaidOut()) {
- Log.v(VIEW_LOG_TAG, "dispatchProvideStructure(): not laid out, ignoring "
- + childrenCount + " children of " + getAccessibilityViewId());
+ if (Helper.sVerbose) {
+ Log.v(VIEW_LOG_TAG, "dispatchProvideStructure(): not laid out, ignoring "
+ + childrenCount + " children of " + getAccessibilityViewId());
+ }
return;
}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 176927f..5bd0782 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1339,9 +1339,9 @@
/**
* 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.
+ * that was processed in {@link android.app.Activity#onCreate}.
+ * <p>
+ * 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
@@ -1351,11 +1351,35 @@
* @param id the ID to search for
* @return a view with given ID if found, or {@code null} otherwise
* @see View#findViewById(int)
+ * @see Window#requireViewById(int)
*/
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
+ /**
+ * Finds a view that was identified by the {@code android:id} XML attribute
+ * that was processed in {@link android.app.Activity#onCreate}, or throws an
+ * IllegalArgumentException if the ID is invalid, 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 a view with given ID
+ * @see View#requireViewById(int)
+ * @see Window#findViewById(int)
+ */
+ @NonNull
+ public final <T extends View> T requireViewById(@IdRes int id) {
+ T view = findViewById(id);
+ if (view == null) {
+ throw new IllegalArgumentException("ID does not reference a View inside this Window");
+ }
+ return view;
+ }
/**
* Convenience for
@@ -2244,9 +2268,36 @@
* <p>
* The transitionName for the view background will be "android:navigation:background".
* </p>
+ * @attr ref android.R.styleable#Window_navigationBarColor
*/
public abstract void setNavigationBarColor(@ColorInt int color);
+ /**
+ * Shows a thin line of the specified color between the navigation bar and the app
+ * content.
+ * <p>
+ * For this to take effect,
+ * the window must be drawing the system bar backgrounds with
+ * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
+ * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_NAVIGATION} must not be set.
+ *
+ * @param dividerColor The color of the thin line.
+ * @attr ref android.R.styleable#Window_navigationBarDividerColor
+ */
+ public void setNavigationBarDividerColor(@ColorInt int dividerColor) {
+ }
+
+ /**
+ * Retrieves the color of the navigation bar divider.
+ *
+ * @return The color of the navigation bar divider color.
+ * @see #setNavigationBarColor(int)
+ * @attr ref android.R.styleable#Window_navigationBarDividerColor
+ */
+ public @ColorInt int getNavigationBarDividerColor() {
+ return 0;
+ }
+
/** @hide */
public void setTheme(int resId) {
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 00860a4..2c51ee9 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -663,6 +663,10 @@
if (context == null) {
throw new IllegalArgumentException("Invalid context argument");
}
+ if (mWebViewThread == null) {
+ throw new RuntimeException(
+ "WebView cannot be initialized on a thread that has no Looper.");
+ }
sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
Build.VERSION_CODES.JELLY_BEAN_MR2;
checkThread();
@@ -2422,6 +2426,14 @@
return getFactory().getWebViewClassLoader();
}
+ /**
+ * Returns the {@link Looper} corresponding to the thread on which WebView calls must be made.
+ */
+ @NonNull
+ public Looper getLooper() {
+ return mWebViewThread;
+ }
+
//-------------------------------------------------------------------------
// Interface for WebView providers
//-------------------------------------------------------------------------
diff --git a/core/java/android/widget/MediaControlView2.java b/core/java/android/widget/MediaControlView2.java
index 0aa2b64..f1d633a 100644
--- a/core/java/android/widget/MediaControlView2.java
+++ b/core/java/android/widget/MediaControlView2.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -26,12 +27,59 @@
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.View;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
+ * A View that contains the controls for MediaPlayer2.
+ * It provides a wide range of UI including buttons such as "Play/Pause", "Rewind", "Fast Forward",
+ * "Subtitle", "Full Screen", and it is also possible to add multiple custom buttons.
+ *
+ * <p>
+ * <em> MediaControlView2 can be initialized in two different ways: </em>
+ * 1) When VideoView2 is initialized, it automatically initializes a MediaControlView2 instance and
+ * adds it to the view.
+ * 2) Initialize MediaControlView2 programmatically and add it to a ViewGroup instance.
+ *
+ * In the first option, VideoView2 automatically connects MediaControlView2 to MediaController2,
+ * which is necessary to communicate with MediaSession2. In the second option, however, the
+ * developer needs to manually retrieve a MediaController2 instance and set it to MediaControlView2
+ * by calling setController(MediaController2 controller).
+ *
* TODO PUBLIC API
* @hide
*/
public class MediaControlView2 extends FrameLayout {
+ /** @hide */
+ @IntDef({
+ BUTTON_PLAY_PAUSE,
+ BUTTON_FFWD,
+ BUTTON_REW,
+ BUTTON_NEXT,
+ BUTTON_PREV,
+ BUTTON_SUBTITLE,
+ BUTTON_FULL_SCREEN,
+ BUTTON_OVERFLOW,
+ BUTTON_MUTE,
+ BUTTON_ASPECT_RATIO,
+ BUTTON_SETTINGS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Button {}
+
+ public static final int BUTTON_PLAY_PAUSE = 1;
+ public static final int BUTTON_FFWD = 2;
+ public static final int BUTTON_REW = 3;
+ public static final int BUTTON_NEXT = 4;
+ public static final int BUTTON_PREV = 5;
+ public static final int BUTTON_SUBTITLE = 6;
+ public static final int BUTTON_FULL_SCREEN = 7;
+ public static final int BUTTON_OVERFLOW = 8;
+ public static final int BUTTON_MUTE = 9;
+ public static final int BUTTON_ASPECT_RATIO = 10;
+ public static final int BUTTON_SETTINGS = 11;
+
private final MediaControlView2Provider mProvider;
public MediaControlView2(@NonNull Context context) {
@@ -55,112 +103,93 @@
.createMediaControlView2(this, new SuperProvider());
}
+ /**
+ * @hide
+ */
public MediaControlView2Provider getProvider() {
return mProvider;
}
/**
- * TODO: add docs
+ * Sets MediaController2 instance to control corresponding MediaSession2.
*/
public void setController(MediaController controller) {
mProvider.setController_impl(controller);
}
/**
- * TODO: add docs
+ * Shows the control view on screen. It will disappear automatically after 3 seconds of
+ * inactivity.
*/
public void show() {
mProvider.show_impl();
}
/**
- * TODO: add docs
+ * Shows the control view on screen. It will disappear automatically after {@code timeout}
+ * milliseconds of inactivity.
*/
public void show(int timeout) {
mProvider.show_impl(timeout);
}
/**
- * TODO: add docs
+ * Returns whether the control view is currently shown or hidden.
*/
public boolean isShowing() {
return mProvider.isShowing_impl();
}
/**
- * TODO: add docs
+ * Hide the control view from the screen.
*/
public void hide() {
mProvider.hide_impl();
}
/**
- * TODO: add docs
- */
- public void showCCButton() {
- mProvider.showCCButton_impl();
- }
-
- /**
- * TODO: add docs
- */
- public boolean isPlaying() {
- return mProvider.isPlaying_impl();
- }
-
- /**
- * TODO: add docs
- */
- public int getCurrentPosition() {
- return mProvider.getCurrentPosition_impl();
- }
-
- /**
- * TODO: add docs
- */
- public int getBufferPercentage() {
- return mProvider.getBufferPercentage_impl();
- }
-
- /**
- * TODO: add docs
- */
- public boolean canPause() {
- return mProvider.canPause_impl();
- }
-
- /**
- * TODO: add docs
- */
- public boolean canSeekBackward() {
- return mProvider.canSeekBackward_impl();
- }
-
- /**
- * TODO: add docs
- */
- public boolean canSeekForward() {
- return mProvider.canSeekForward_impl();
- }
-
- /**
- * TODO: add docs
+ * If the media selected has a subtitle track, calling this method will display the subtitle at
+ * the bottom of the view. If a media has multiple subtitle tracks, this method will select the
+ * first one of them.
*/
public void showSubtitle() {
mProvider.showSubtitle_impl();
}
/**
- * TODO: add docs
+ * Hides the currently displayed subtitle.
*/
public void hideSubtitle() {
mProvider.hideSubtitle_impl();
}
+ /**
+ * Set listeners for previous and next buttons to customize the behavior of clicking them.
+ * The UI for these buttons are provided as default and will be automatically displayed when
+ * this method is called.
+ *
+ * @param next Listener for clicking next button
+ * @param prev Listener for clicking previous button
+ */
+ public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) {
+ mProvider.setPrevNextListeners_impl(next, prev);
+ }
+
+ /**
+ * Hides the specified button from view.
+ *
+ * @param button the constant integer assigned to individual buttons
+ * @param visible whether the button should be visible or not
+ */
+ public void setButtonVisibility(int button, boolean visible) {
+ mProvider.setButtonVisibility_impl(button, visible);
+ }
+
@Override
protected void onAttachedToWindow() {
mProvider.onAttachedToWindow_impl();
}
+
@Override
protected void onDetachedFromWindow() {
mProvider.onDetachedFromWindow_impl();
diff --git a/core/java/android/widget/VideoView2.java b/core/java/android/widget/VideoView2.java
index 56f3dbd..8650c0a 100644
--- a/core/java/android/widget/VideoView2.java
+++ b/core/java/android/widget/VideoView2.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
+import android.media.MediaPlayerBase;
import android.media.update.ApiLoader;
import android.media.update.VideoView2Provider;
import android.media.update.ViewProvider;
@@ -32,6 +33,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
import java.util.Map;
// TODO: Use @link tag to refer MediaPlayer2 in docs once MediaPlayer2.java is submitted. Same to
@@ -235,6 +237,13 @@
mProvider.hideSubtitle_impl();
}
+ /**
+ * Sets full screen mode.
+ */
+ public void setFullScreen(boolean fullScreen) {
+ mProvider.setFullScreen_impl(fullScreen);
+ }
+
// TODO: This should be revised after integration with MediaPlayer2.
/**
* Sets playback speed.
@@ -271,7 +280,8 @@
* background.
*
* @param focusGain the type of audio focus gain that will be requested, or
- * {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during playback.
+ * {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during
+ * playback.
*/
public void setAudioFocusRequest(int focusGain) {
mProvider.setAudioFocusRequest_impl(focusGain);
@@ -287,6 +297,21 @@
}
/**
+ * Sets a remote player for handling playback of the selected route from MediaControlView2.
+ * If this is not called, MediaCotrolView2 will not show the route button.
+ *
+ * @param routeCategories the list of media control categories in
+ * {@link android.support.v7.media.MediaControlIntent}
+ * @param player the player to handle the selected route. If null, a default
+ * route player will be used.
+ * @throws IllegalStateException if MediaControlView2 is not set.
+ */
+ public void setRouteAttributes(@NonNull List<String> routeCategories,
+ @Nullable MediaPlayerBase player) {
+ mProvider.setRouteAttributes_impl(routeCategories, player);
+ }
+
+ /**
* Sets video path.
*
* @param path the path of the video.
@@ -399,6 +424,13 @@
}
/**
+ * Registers a callback to be invoked when the fullscreen mode should be changed.
+ */
+ public void setFullScreenChangedListener(OnFullScreenChangedListener l) {
+ mProvider.setFullScreenChangedListener_impl(l);
+ }
+
+ /**
* Interface definition of a callback to be invoked when the viw type has been changed.
*/
public interface OnViewTypeChangedListener {
@@ -466,6 +498,16 @@
void onInfo(int what, int extra);
}
+ /**
+ * Interface definition of a callback to be invoked to inform the fullscreen mode is changed.
+ */
+ public interface OnFullScreenChangedListener {
+ /**
+ * Called to indicate a fullscreen mode change.
+ */
+ void onFullScreenChanged(boolean fullScreen);
+ }
+
@Override
protected void onAttachedToWindow() {
mProvider.onAttachedToWindow_impl();
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 6e0ba341..997d47f 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -841,7 +841,7 @@
}
@Override
- public boolean startAsCaller(Activity activity, Bundle options, int userId) {
+ public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
final Intent intent = getBaseIntentToSend();
if (intent == null) {
return false;
@@ -860,8 +860,7 @@
final boolean ignoreTargetSecurity = mSourceInfo != null
&& mSourceInfo.getResolvedComponentName().getPackageName()
.equals(mChooserTarget.getComponentName().getPackageName());
- activity.startActivityAsCaller(intent, options, ignoreTargetSecurity, userId);
- return true;
+ return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId);
}
@Override
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 398d087..86731bc 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -107,7 +107,7 @@
|| ChooserActivity.class.getName().equals(ri.activityInfo.name));
try {
- startActivityAsCaller(newIntent, null, false, targetUserId);
+ startActivityAsCaller(newIntent, null, null, false, targetUserId);
} catch (RuntimeException e) {
int launchedFromUid = -1;
String launchedFromPackage = "?";
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index ceb06f5..d6d4490 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -43,6 +43,7 @@
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.PatternMatcher;
import android.os.RemoteException;
import android.os.StrictMode;
@@ -857,6 +858,36 @@
}
}
+ public boolean startAsCallerImpl(Intent intent, Bundle options, boolean ignoreTargetSecurity,
+ int userId) {
+ // Pass intent to delegate chooser activity with permission token.
+ // TODO: This should move to a trampoline Activity in the system when the ChooserActivity
+ // moves into systemui
+ try {
+ // TODO: Once this is a small springboard activity, it can move off the UI process
+ // and we can move the request method to ActivityManagerInternal.
+ IBinder permissionToken = ActivityManager.getService()
+ .requestStartActivityPermissionToken(getActivityToken());
+ final Intent chooserIntent = new Intent();
+ final ComponentName delegateActivity = ComponentName.unflattenFromString(
+ Resources.getSystem().getString(R.string.config_chooserActivity));
+ chooserIntent.setClassName(delegateActivity.getPackageName(),
+ delegateActivity.getClassName());
+ chooserIntent.putExtra(ActivityManager.EXTRA_PERMISSION_TOKEN, permissionToken);
+
+ // TODO: These extras will change as chooser activity moves into systemui
+ chooserIntent.putExtra(Intent.EXTRA_INTENT, intent);
+ chooserIntent.putExtra(ActivityManager.EXTRA_OPTIONS, options);
+ chooserIntent.putExtra(ActivityManager.EXTRA_IGNORE_TARGET_SECURITY,
+ ignoreTargetSecurity);
+ chooserIntent.putExtra(Intent.EXTRA_USER_ID, userId);
+ startActivity(chooserIntent);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ return true;
+ }
+
public void onActivityStarted(TargetInfo cti) {
// Do nothing
}
@@ -1181,9 +1212,8 @@
}
@Override
- public boolean startAsCaller(Activity activity, Bundle options, int userId) {
- activity.startActivityAsCaller(mResolvedIntent, options, false, userId);
- return true;
+ public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
+ return activity.startAsCallerImpl(mResolvedIntent, options, false, userId);
}
@Override
@@ -1242,7 +1272,7 @@
* @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller
* @return true if the start completed successfully
*/
- boolean startAsCaller(Activity activity, Bundle options, int userId);
+ boolean startAsCaller(ResolverActivity activity, Bundle options, int userId);
/**
* Start the activity referenced by this target as a given user.
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index 02cd09f..43abade 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -31,13 +31,17 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ProcFileReader;
+import com.google.android.collect.Lists;
import libcore.io.IoUtils;
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileReader;
import java.io.IOException;
import java.net.ProtocolException;
+import java.util.ArrayList;
import java.util.Objects;
/**
@@ -57,6 +61,8 @@
// Used for correct stats accounting on clatd interfaces.
private static final int IPV4V6_HEADER_DELTA = 20;
+ /** Path to {@code /proc/net/dev}. */
+ private final File mStatsIfaceDev;
/** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */
private final File mStatsXtIfaceAll;
/** Path to {@code /proc/net/xt_qtaguid/iface_stat_fmt}. */
@@ -64,6 +70,8 @@
/** Path to {@code /proc/net/xt_qtaguid/stats}. */
private final File mStatsXtUid;
+ private boolean mUseBpfStats;
+
// TODO: to improve testability and avoid global state, do not use a static variable.
@GuardedBy("sStackedIfaces")
private static final ArrayMap<String, String> sStackedIfaces = new ArrayMap<>();
@@ -79,14 +87,54 @@
}
public NetworkStatsFactory() {
- this(new File("/proc/"));
+ this(new File("/proc/"), new File("/sys/fs/bpf/traffic_uid_stats_map").exists());
}
@VisibleForTesting
- public NetworkStatsFactory(File procRoot) {
+ public NetworkStatsFactory(File procRoot, boolean useBpfStats) {
+ mStatsIfaceDev = new File(procRoot, "net/dev");
mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
+ mUseBpfStats = useBpfStats;
+ }
+
+ @VisibleForTesting
+ public NetworkStats readNetworkStatsIfaceDev() throws IOException {
+ final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
+
+ final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(mStatsIfaceDev));
+
+ // skip first two header lines
+ reader.readLine();
+ reader.readLine();
+
+ // parse remaining lines
+ String line;
+ while ((line = reader.readLine()) != null) {
+ String[] values = line.trim().split("\\:?\\s+");
+ entry.iface = values[0];
+ entry.uid = UID_ALL;
+ entry.set = SET_ALL;
+ entry.tag = TAG_NONE;
+ entry.rxBytes = Long.parseLong(values[1]);
+ entry.rxPackets = Long.parseLong(values[2]);
+ entry.txBytes = Long.parseLong(values[9]);
+ entry.txPackets = Long.parseLong(values[10]);
+ stats.addValues(entry);
+ }
+ } catch (NullPointerException|NumberFormatException e) {
+ throw new ProtocolException("problem parsing stats", e);
+ } finally {
+ IoUtils.closeQuietly(reader);
+ StrictMode.setThreadPolicy(savedPolicy);
+ }
+ return stats;
}
/**
@@ -98,6 +146,11 @@
* @throws IllegalStateException when problem parsing stats.
*/
public NetworkStats readNetworkStatsSummaryDev() throws IOException {
+
+ // Return the stats get from /proc/net/dev if switched to bpf module.
+ if (mUseBpfStats)
+ return readNetworkStatsIfaceDev();
+
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
@@ -149,6 +202,11 @@
* @throws IllegalStateException when problem parsing stats.
*/
public NetworkStats readNetworkStatsSummaryXt() throws IOException {
+
+ // Return the stats get from /proc/net/dev if qtaguid module is replaced.
+ if (mUseBpfStats)
+ return readNetworkStatsIfaceDev();
+
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
// return null when kernel doesn't support
@@ -254,7 +312,7 @@
stats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
}
if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid,
- limitIfaces, limitTag) != 0) {
+ limitIfaces, limitTag, mUseBpfStats) != 0) {
throw new IOException("Failed to parse network stats");
}
if (SANITY_CHECK_NATIVE) {
@@ -348,6 +406,6 @@
* are expected to monotonically increase since device boot.
*/
@VisibleForTesting
- public static native int nativeReadNetworkStatsDetail(
- NetworkStats stats, String path, int limitUid, String[] limitIfaces, int limitTag);
+ public static native int nativeReadNetworkStatsDetail(NetworkStats stats, String path,
+ int limitUid, String[] limitIfaces, int limitTag, boolean useBpfStats);
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 039b66d..51f51c2 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -6211,8 +6211,20 @@
}
@Override public long getGpsBatteryDrainMaMs() {
- //TODO: Add GPS power computation (b/67213967)
- return 0;
+ final double opVolt = mPowerProfile.getAveragePower(
+ PowerProfile.POWER_GPS_OPERATING_VOLTAGE) / 1000.0;
+ if (opVolt == 0) {
+ return 0;
+ }
+ double energyUsedMaMs = 0.0;
+ final int which = STATS_SINCE_CHARGED;
+ final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
+ for(int i=0; i < GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) {
+ energyUsedMaMs
+ += mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_SIGNAL_QUALITY_BASED, i)
+ * (getGpsSignalQualityTime(i, rawRealtime, which) / 1000);
+ }
+ return (long) energyUsedMaMs;
}
@Override public long getPhoneOnTime(long elapsedRealtimeUs, int which) {
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 240fc51..f4436d3 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -104,12 +104,18 @@
public static final String POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE =
"modem.controller.voltage";
- /**
+ /**
* Power consumption when GPS is on.
*/
public static final String POWER_GPS_ON = "gps.on";
/**
+ * GPS power parameters based on signal quality
+ */
+ public static final String POWER_GPS_SIGNAL_QUALITY_BASED = "gps.signalqualitybased";
+ public static final String POWER_GPS_OPERATING_VOLTAGE = "gps.voltage";
+
+ /**
* Power consumption when Bluetooth driver is on.
* @deprecated
*/
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 2671f29..5659470 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -98,6 +98,10 @@
private static final String SOCKET_NAME_ARG = "--socket-name=";
+ /* Dexopt flag to disable hidden API access checks when dexopting SystemServer.
+ * Must be kept in sync with com.android.server.pm.Installer. */
+ private static final int DEXOPT_DISABLE_HIDDEN_API_CHECKS = 1 << 10;
+
/**
* Used to pre-load resources.
*/
@@ -565,7 +569,10 @@
if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
final String packageName = "*";
final String outputPath = null;
- final int dexFlags = 0;
+ // Dexopt with a flag which lifts restrictions on hidden API usage.
+ // Offending methods would otherwise be re-verified at runtime and
+ // we want to avoid the performance overhead of that.
+ final int dexFlags = DEXOPT_DISABLE_HIDDEN_API_CHECKS;
final String compilerFilter = systemServerFilter;
final String uuid = StorageManager.UUID_PRIVATE_INTERNAL;
final String seInfo = null;
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index e8ee29d..34b5ec8 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -3810,6 +3810,19 @@
}
}
+ @Override
+ public void setNavigationBarDividerColor(int navigationBarDividerColor) {
+ mNavigationBarDividerColor = navigationBarDividerColor;
+ if (mDecor != null) {
+ mDecor.updateColorViews(null, false /* animate */);
+ }
+ }
+
+ @Override
+ public int getNavigationBarDividerColor() {
+ return mNavigationBarDividerColor;
+ }
+
public void setIsStartingWindow(boolean isStartingWindow) {
mIsStartingWindow = isStartingWindow;
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index e097362a..ebb5f9f 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.graphics.Rect;
+import android.hardware.fingerprint.IFingerprintDialogReceiver;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
@@ -34,6 +35,8 @@
void animateCollapsePanels();
void togglePanel();
+ void showChargingAnimation(int batteryLevel);
+
/**
* Notifies the status bar of a System UI visibility flag change.
*
@@ -130,4 +133,15 @@
void handleSystemKey(in int key);
void showShutdownUi(boolean isReboot, String reason);
+
+ // Used to show the dialog when FingerprintService starts authentication
+ void showFingerprintDialog(in Bundle bundle, IFingerprintDialogReceiver receiver);
+ // Used to hide the dialog when a finger is authenticated
+ void onFingerprintAuthenticated();
+ // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
+ void onFingerprintHelp(String message);
+ // Used to set a message - the dialog will dismiss after a certain amount of time
+ void onFingerprintError(String error);
+ // Used to hide the fingerprint dialog when the authenticationclient is stopped
+ void hideFingerprintDialog();
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 03603e4..cb0b53c 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -20,6 +20,7 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
+import android.hardware.fingerprint.IFingerprintDialogReceiver;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.StatusBarIcon;
@@ -79,4 +80,15 @@
void remTile(in ComponentName tile);
void clickTile(in ComponentName tile);
void handleSystemKey(in int key);
+
+ // Used to show the dialog when FingerprintService starts authentication
+ void showFingerprintDialog(in Bundle bundle, IFingerprintDialogReceiver receiver);
+ // Used to hide the dialog when a finger is authenticated
+ void onFingerprintAuthenticated();
+ // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
+ void onFingerprintHelp(String message);
+ // Used to set a message - the dialog will dismiss after a certain amount of time
+ void onFingerprintError(String error);
+ // Used to hide the fingerprint dialog when the authenticationclient is stopped
+ void hideFingerprintDialog();
}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index f9da1eb..927d757 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -20,8 +20,8 @@
import android.app.trust.IStrongAuthTracker;
import android.os.Bundle;
import android.security.keystore.recovery.WrappedApplicationKey;
-import android.security.keystore.recovery.KeychainSnapshot;
-import android.security.keystore.recovery.KeychainProtectionParams;
+import android.security.keystore.recovery.KeyChainSnapshot;
+import android.security.keystore.recovery.KeyChainProtectionParams;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -64,7 +64,7 @@
// {@code ServiceSpecificException} may be thrown to signal an error, which caller can
// convert to {@code RecoveryManagerException}.
void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList);
- KeychainSnapshot getRecoveryData(in byte[] account);
+ KeyChainSnapshot getRecoveryData(in byte[] account);
byte[] generateAndStoreKey(String alias);
void removeKey(String alias);
void setSnapshotCreatedPendingIntent(in PendingIntent intent);
@@ -75,10 +75,10 @@
void setRecoverySecretTypes(in int[] secretTypes);
int[] getRecoverySecretTypes();
int[] getPendingRecoverySecretTypes();
- void recoverySecretAvailable(in KeychainProtectionParams recoverySecret);
+ void recoverySecretAvailable(in KeyChainProtectionParams recoverySecret);
byte[] startRecoverySession(in String sessionId,
in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge,
- in List<KeychainProtectionParams> secrets);
+ in List<KeyChainProtectionParams> secrets);
Map/*<String, byte[]>*/ recoverKeys(in String sessionId, in byte[] recoveryKeyBlob,
in List<WrappedApplicationKey> applicationKeys);
void closeSession(in String sessionId);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 543acc7..47765d9 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -228,6 +228,8 @@
],
shared_libs: [
+ "libbpf",
+ "libnetdutils",
"libmemtrack",
"libandroidfw",
"libappfuse",
diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp
index ec15cce..8b3ce66 100644
--- a/core/jni/android/graphics/AnimatedImageDrawable.cpp
+++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp
@@ -18,6 +18,7 @@
#include "ImageDecoder.h"
#include "core_jni_helpers.h"
+#include <hwui/AnimatedImageDrawable.h>
#include <hwui/Canvas.h>
#include <SkAndroidCodec.h>
#include <SkAnimatedImage.h>
@@ -27,10 +28,6 @@
using namespace android;
-struct AnimatedImageDrawable {
- sk_sp<SkAnimatedImage> mDrawable;
- SkPaint mPaint;
-};
// Note: jpostProcess holds a handle to the ImageDecoder.
static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/,
@@ -65,20 +62,22 @@
picture = recorder.finishRecordingAsPicture();
}
- std::unique_ptr<AnimatedImageDrawable> drawable(new AnimatedImageDrawable);
- drawable->mDrawable = SkAnimatedImage::Make(std::move(imageDecoder->mCodec),
- scaledSize, subset, std::move(picture));
- if (!drawable->mDrawable) {
+
+ sk_sp<SkAnimatedImage> animatedImg = SkAnimatedImage::Make(std::move(imageDecoder->mCodec),
+ scaledSize, subset,
+ std::move(picture));
+ if (!animatedImg) {
doThrowIOE(env, "Failed to create drawable");
return 0;
}
- drawable->mDrawable->start();
+ sk_sp<AnimatedImageDrawable> drawable(new AnimatedImageDrawable(animatedImg));
+ drawable->start();
return reinterpret_cast<jlong>(drawable.release());
}
static void AnimatedImageDrawable_destruct(AnimatedImageDrawable* drawable) {
- delete drawable;
+ SkSafeUnref(drawable);
}
static jlong AnimatedImageDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject /*clazz*/) {
@@ -86,45 +85,43 @@
}
static jlong AnimatedImageDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
- jlong canvasPtr, jlong msecs) {
+ jlong canvasPtr) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- double timeToNextUpdate = drawable->mDrawable->update(msecs);
auto* canvas = reinterpret_cast<Canvas*>(canvasPtr);
- canvas->drawAnimatedImage(drawable->mDrawable.get(), 0, 0, &drawable->mPaint);
- return (jlong) timeToNextUpdate;
+ return (jlong) canvas->drawAnimatedImage(drawable);
}
static void AnimatedImageDrawable_nSetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
jint alpha) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- drawable->mPaint.setAlpha(alpha);
+ drawable->setStagingAlpha(alpha);
}
static jlong AnimatedImageDrawable_nGetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- return drawable->mPaint.getAlpha();
+ return drawable->getStagingAlpha();
}
static void AnimatedImageDrawable_nSetColorFilter(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
jlong nativeFilter) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
auto* filter = reinterpret_cast<SkColorFilter*>(nativeFilter);
- drawable->mPaint.setColorFilter(sk_ref_sp(filter));
+ drawable->setStagingColorFilter(sk_ref_sp(filter));
}
static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- return drawable->mDrawable->isRunning();
+ return drawable->isRunning();
}
static void AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- drawable->mDrawable->start();
+ drawable->start();
}
static void AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- drawable->mDrawable->stop();
+ drawable->stop();
}
static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
@@ -136,7 +133,7 @@
static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
{ "nCreate", "(JLandroid/graphics/ImageDecoder;IILandroid/graphics/Rect;)J", (void*) AnimatedImageDrawable_nCreate },
{ "nGetNativeFinalizer", "()J", (void*) AnimatedImageDrawable_nGetNativeFinalizer },
- { "nDraw", "(JJJ)J", (void*) AnimatedImageDrawable_nDraw },
+ { "nDraw", "(JJ)J", (void*) AnimatedImageDrawable_nDraw },
{ "nSetAlpha", "(JI)V", (void*) AnimatedImageDrawable_nSetAlpha },
{ "nGetAlpha", "(J)I", (void*) AnimatedImageDrawable_nGetAlpha },
{ "nSetColorFilter", "(JJ)V", (void*) AnimatedImageDrawable_nSetColorFilter },
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 2be9471..376a797 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -608,9 +608,10 @@
}
static jint
-android_media_AudioSystem_setLowRamDevice(JNIEnv *env, jobject clazz, jboolean isLowRamDevice)
+android_media_AudioSystem_setLowRamDevice(
+ JNIEnv *env, jobject clazz, jboolean isLowRamDevice, jlong totalMemory)
{
- return (jint) AudioSystem::setLowRamDevice((bool) isLowRamDevice);
+ return (jint) AudioSystem::setLowRamDevice((bool) isLowRamDevice, (int64_t) totalMemory);
}
static jint
@@ -1819,7 +1820,7 @@
{"getPrimaryOutputSamplingRate", "()I", (void *)android_media_AudioSystem_getPrimaryOutputSamplingRate},
{"getPrimaryOutputFrameCount", "()I", (void *)android_media_AudioSystem_getPrimaryOutputFrameCount},
{"getOutputLatency", "(I)I", (void *)android_media_AudioSystem_getOutputLatency},
- {"setLowRamDevice", "(Z)I", (void *)android_media_AudioSystem_setLowRamDevice},
+ {"setLowRamDevice", "(ZJ)I", (void *)android_media_AudioSystem_setLowRamDevice},
{"checkAudioFlinger", "()I", (void *)android_media_AudioSystem_checkAudioFlinger},
{"listAudioPorts", "(Ljava/util/ArrayList;[I)I",
(void *)android_media_AudioSystem_listAudioPorts},
diff --git a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
index d254de6..99d9839 100644
--- a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
+++ b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
@@ -30,7 +30,14 @@
#include <utils/Log.h>
#include <utils/misc.h>
-#include <utils/Vector.h>
+
+#include "android-base/unique_fd.h"
+#include "bpf/BpfNetworkStats.h"
+#include "bpf/BpfUtils.h"
+
+using android::bpf::hasBpfSupport;
+using android::bpf::parseBpfNetworkStatsDetail;
+using android::bpf::stats_line;
namespace android {
@@ -53,17 +60,6 @@
jfieldID operations;
} gNetworkStatsClassInfo;
-struct stats_line {
- char iface[32];
- int32_t uid;
- int32_t set;
- int32_t tag;
- int64_t rxBytes;
- int64_t rxPackets;
- int64_t txBytes;
- int64_t txPackets;
-};
-
static jobjectArray get_string_array(JNIEnv* env, jobject obj, jfieldID field, int size, bool grow)
{
if (!grow) {
@@ -97,33 +93,14 @@
return env->NewLongArray(size);
}
-static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats,
- jstring path, jint limitUid, jobjectArray limitIfacesObj, jint limitTag) {
- ScopedUtfChars path8(env, path);
- if (path8.c_str() == NULL) {
- return -1;
- }
-
- FILE *fp = fopen(path8.c_str(), "r");
+static int legacyReadNetworkStatsDetail(std::vector<stats_line>* lines,
+ const std::vector<std::string>& limitIfaces,
+ int limitTag, int limitUid, const char* path) {
+ FILE* fp = fopen(path, "r");
if (fp == NULL) {
return -1;
}
- Vector<String8> limitIfaces;
- if (limitIfacesObj != NULL && env->GetArrayLength(limitIfacesObj) > 0) {
- int num = env->GetArrayLength(limitIfacesObj);
- limitIfaces.setCapacity(num);
- for (int i=0; i<num; i++) {
- jstring string = (jstring)env->GetObjectArrayElement(limitIfacesObj, i);
- ScopedUtfChars string8(env, string);
- if (string8.c_str() != NULL) {
- limitIfaces.add(String8(string8.c_str()));
- }
- }
- }
-
- Vector<stats_line> lines;
-
int lastIdx = 1;
int idx;
char buffer[384];
@@ -215,7 +192,7 @@
//ALOGI("skipping due to uid: %s", buffer);
continue;
}
- lines.push_back(s);
+ lines->push_back(s);
} else {
//ALOGI("skipping due to bad remaining fields: %s", pos);
}
@@ -225,8 +202,42 @@
ALOGE("Failed to close netstats file");
return -1;
}
+ return 0;
+}
+
+static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, jstring path,
+ jint limitUid, jobjectArray limitIfacesObj, jint limitTag,
+ jboolean useBpfStats) {
+ ScopedUtfChars path8(env, path);
+ if (path8.c_str() == NULL) {
+ return -1;
+ }
+
+ std::vector<std::string> limitIfaces;
+ if (limitIfacesObj != NULL && env->GetArrayLength(limitIfacesObj) > 0) {
+ int num = env->GetArrayLength(limitIfacesObj);
+ for (int i = 0; i < num; i++) {
+ jstring string = (jstring)env->GetObjectArrayElement(limitIfacesObj, i);
+ ScopedUtfChars string8(env, string);
+ if (string8.c_str() != NULL) {
+ limitIfaces.push_back(std::string(string8.c_str()));
+ }
+ }
+ }
+ std::vector<stats_line> lines;
+
+
+ if (useBpfStats) {
+ if (parseBpfNetworkStatsDetail(&lines, limitIfaces, limitTag, limitUid) < 0)
+ return -1;
+ } else {
+ if (legacyReadNetworkStatsDetail(&lines, limitIfaces, limitTag,
+ limitUid, path8.c_str()) < 0)
+ return -1;
+ }
int size = lines.size();
+
bool grow = size > env->GetIntField(stats, gNetworkStatsClassInfo.capacity);
ScopedLocalRef<jobjectArray> iface(env, get_string_array(env, stats,
@@ -303,7 +314,7 @@
static const JNINativeMethod gMethods[] = {
{ "nativeReadNetworkStatsDetail",
- "(Landroid/net/NetworkStats;Ljava/lang/String;I[Ljava/lang/String;I)I",
+ "(Landroid/net/NetworkStats;Ljava/lang/String;I[Ljava/lang/String;IZ)I",
(void*) readNetworkStatsDetail }
};
diff --git a/core/proto/android/app/activitymanager.proto b/core/proto/android/app/activitymanager.proto
index 3412a32..03f8204 100644
--- a/core/proto/android/app/activitymanager.proto
+++ b/core/proto/android/app/activitymanager.proto
@@ -19,6 +19,7 @@
package android.app;
option java_multiple_files = true;
+option java_outer_classname = "ActivityManagerProto";
// ActivityManager.java PROCESS_STATEs
enum ProcessState {
@@ -79,3 +80,16 @@
PROCESS_STATE_NONEXISTENT = 1900;
}
+// ActivityManager.java UID_OBSERVERs flags
+enum UidObserverFlag {
+ // report changes in process state, original value is 1 << 0
+ UID_OBSERVER_FLAG_PROCSTATE = 1;
+ // report uid gone, original value is 1 << 1
+ UID_OBSERVER_FLAG_GONE = 2;
+ // report uid has become idle, original value is 1 << 2
+ UID_OBSERVER_FLAG_IDLE = 3;
+ // report uid has become active, original value is 1 << 3
+ UID_OBSERVER_FLAG_ACTIVE = 4;
+ // report uid cached state has changed, original value is 1 << 4
+ UID_OBSERVER_FLAG_CACHED = 5;
+}
diff --git a/core/proto/android/app/profilerinfo.proto b/core/proto/android/app/profilerinfo.proto
new file mode 100644
index 0000000..ca1b935
--- /dev/null
+++ b/core/proto/android/app/profilerinfo.proto
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+option java_package = "android.app";
+option java_multiple_files = true;
+
+package android.app;
+
+/**
+ * An android.app.ProfilerInfo object.
+ */
+message ProfilerInfoProto {
+ optional string profile_file = 1;
+ optional int32 profile_fd = 2;
+ optional int32 sampling_interval = 3;
+ optional bool auto_stop_profiler = 4;
+ optional bool streaming_output = 5;
+ optional string agent = 6;
+}
diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto
new file mode 100644
index 0000000..8470159
--- /dev/null
+++ b/core/proto/android/content/package_item_info.proto
@@ -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.
+ */
+
+syntax = "proto2";
+option java_package = "android.content.pm";
+option java_multiple_files = true;
+
+package android.content.pm;
+
+message PackageItemInfoProto {
+ optional string name = 1;
+ optional string package_name = 2;
+ optional int32 label_res = 3;
+ optional string non_localized_label = 4;
+ optional int32 icon = 5;
+ optional int32 banner = 6;
+}
+
+// Proto of android.content.pm.ApplicationInfo which extends PackageItemInfo
+message ApplicationInfoProto {
+ optional PackageItemInfoProto package = 1;
+ optional string permission = 2;
+ optional string process_name = 3;
+ optional int32 uid = 4;
+ optional int32 flags = 5;
+ optional int32 private_flags = 6;
+ optional int32 theme = 7;
+ optional string source_dir = 8;
+ optional string public_source_dir = 9;
+ repeated string split_source_dirs = 10;
+ repeated string split_public_source_dirs = 11;
+ repeated string resource_dirs = 12;
+ optional string data_dir = 13;
+ optional string class_loader_name = 14;
+ repeated string split_class_loader_names = 15;
+
+ message Version {
+ optional bool enabled = 1;
+ optional int32 min_sdk_version = 2;
+ optional int32 target_sdk_version = 3;
+ optional int32 version_code = 4;
+ optional int32 target_sandbox_version = 5;
+ }
+ optional Version version = 16;
+
+ message Detail {
+ optional string class_name = 1;
+ optional string task_affinity = 2;
+ optional int32 requires_smallest_width_dp = 3;
+ optional int32 compatible_width_limit_dp = 4;
+ optional int32 largest_width_limit_dp = 5;
+ optional string seinfo = 6;
+ optional string seinfo_user = 7;
+ optional string device_protected_data_dir = 8;
+ optional string credential_protected_data_dir = 9;
+ repeated string shared_library_files = 10;
+ optional string manage_space_activity_name = 11;
+ optional int32 description_res = 12;
+ optional int32 ui_options = 13;
+ optional bool supports_rtl = 14;
+ oneof full_backup_content {
+ string content = 15;
+ bool is_full_backup = 16;
+ }
+ optional int32 networkSecurity_config_res = 17;
+ optional int32 category = 18;
+ }
+ optional Detail detail = 17;
+}
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
index 331f80f..ce1d5c9 100644
--- a/core/proto/android/os/batterystats.proto
+++ b/core/proto/android/os/batterystats.proto
@@ -22,8 +22,11 @@
import "frameworks/base/core/proto/android/app/jobparameters.proto";
import "frameworks/base/core/proto/android/os/powermanager.proto";
import "frameworks/base/core/proto/android/telephony/signalstrength.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
message BatteryStatsProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 report_version = 1;
optional int64 parcel_version = 2;
optional string start_platform_version = 3;
@@ -33,6 +36,8 @@
}
message ControllerActivityProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Time (milliseconds) spent in the idle state.
optional int64 idle_duration_ms = 1;
// Time (milliseconds) spent in the receive state.
@@ -45,6 +50,8 @@
// of power. The levels themselves are controller-specific (and may possibly
// be device specific...yet to be confirmed).
message TxLevel {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Transmit level. Higher levels draw more power.
optional int32 level = 1;
// Time spent in this specific transmit level state.
@@ -54,7 +61,11 @@
}
message SystemProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
message Battery {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Wall clock time when the data collection started.
// In case of device time manually reset by users:
// start_clock_time_ms keeps the same value in the current collection
@@ -92,6 +103,8 @@
optional Battery battery = 1;
message BatteryDischarge {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Discharged battery percentage points since the stats were last reset
// after charging (lower bound approximation).
optional int32 lower_bound_since_charge = 1;
@@ -142,6 +155,8 @@
// the entire duration. Field for which the conditions were not consistent
// for the entire duration should be marked MIXED.
message BatteryLevelStep {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// How long the battery was at the current level.
optional int64 duration_ms = 1;
// Battery level
@@ -192,6 +207,8 @@
repeated int64 cpu_frequency = 7;
message DataConnection {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
enum Name {
NONE = 0;
GPRS = 1;
@@ -221,6 +238,8 @@
optional ControllerActivityProto global_wifi_controller = 11;
message GlobalNetwork {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Total Bytes received on mobile connections.
optional int64 mobile_bytes_rx = 1;
// Total Bytes transmitted on mobile connections.
@@ -245,6 +264,8 @@
optional GlobalNetwork global_network = 12;
message GlobalWifi {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// The amount of time that wifi has been on while the device was running on
// battery.
optional int64 on_duration_ms = 1;
@@ -257,6 +278,8 @@
// Kernel wakelock metrics are only recorded when the device is unplugged
// *and* the screen is off.
message KernelWakelock {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string name = 1;
// Kernel wakelock stats aren't apportioned across all kernel wakelocks (as
// app wakelocks stats are).
@@ -267,6 +290,8 @@
repeated KernelWakelock kernel_wakelock = 14;
message Misc {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int64 screen_on_duration_ms = 1;
optional int64 phone_on_duration_ms = 2;
optional int64 full_wakelock_total_duration_ms = 3;
@@ -312,12 +337,16 @@
optional Misc misc = 15;
message PhoneSignalStrength {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional android.telephony.SignalStrengthProto.StrengthName name = 1;
optional TimerProto total = 2;
};
repeated PhoneSignalStrength phone_signal_strength = 16;
message PowerUseItem {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
enum Sipper {
UNKNOWN_SIPPER = 0;
IDLE = 1;
@@ -352,6 +381,8 @@
repeated PowerUseItem power_use_item = 17;
message PowerUseSummary {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional double battery_capacity_mah = 1;
optional double computed_power_mah = 2;
// Lower bound of actual power drained.
@@ -362,6 +393,8 @@
optional PowerUseSummary power_use_summary = 18;
message ResourcePowerManager {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Either StateName or StateName.VoterName.
optional string name = 1;
optional TimerProto total = 2;
@@ -370,6 +403,8 @@
repeated ResourcePowerManager resource_power_manager = 19;
message ScreenBrightness {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
enum Name {
DARK = 0; // Not screen-off.
DIM = 1;
@@ -386,18 +421,24 @@
optional TimerProto signal_scanning = 21;
message WakeupReason {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string name = 1;
optional TimerProto total = 2;
};
repeated WakeupReason wakeup_reason = 22;
message WifiMulticastWakelockTotal {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int64 duration_ms = 1;
optional int32 count = 2;
}
optional WifiMulticastWakelockTotal wifi_multicast_wakelock_total = 23;
message WifiSignalStrength {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
enum Name {
NONE = 0;
POOR = 1;
@@ -411,6 +452,8 @@
repeated WifiSignalStrength wifi_signal_strength = 24;
message WifiState {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
enum Name {
OFF = 0;
OFF_SCANNING = 1;
@@ -427,6 +470,8 @@
repeated WifiState wifi_state = 25;
message WifiSupplicantState {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
enum Name {
INVALID = 0;
DISCONNECTED = 1;
@@ -449,6 +494,8 @@
}
message TimerProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// This may be an apportioned time.
optional int64 duration_ms = 1;
optional int64 count = 2;
@@ -468,14 +515,20 @@
}
message UidProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Combination of app ID and user ID.
optional int32 uid = 1;
// The statistics associated with a particular package.
message Package {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string name = 1;
message Service {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string name = 1;
// Time spent started.
optional int64 start_duration_ms = 2;
@@ -492,6 +545,8 @@
// Bluetooth misc data.
message BluetoothMisc {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Duration spent BLE scanning blamed on this App (i.e. apportioned to this
// app amongst all apps doing BLE scanning; see explanation of 'apportioned'
// in App's comment).
@@ -515,6 +570,8 @@
optional BluetoothMisc bluetooth_misc = 6;
message Cpu {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Total CPU time with processes executing in userspace. Summed up across
// multiple cores.
optional int64 user_duration_ms = 1;
@@ -529,6 +586,8 @@
// system_duration_millis, which are just approximations. Data is not
// tracked when device is charging.
message ByFrequency {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Index of the frequency in system.cpu_frequency. It starts from 1, to
// make it easier to analyze.
optional int32 frequency_index = 1;
@@ -551,6 +610,8 @@
}
// CPU times at different process states.
message ByProcessState {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional ProcessState process_state = 1;
repeated ByFrequency by_frequency = 2;
}
@@ -574,7 +635,11 @@
optional TimerProto video = 14;
message Job {
- optional string name = 1;
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional string name = 1 [
+ (android.privacy).dest = DEST_EXPLICIT
+ ];
// Job times aren't apportioned.
optional TimerProto total = 2;
optional TimerProto background = 3;
@@ -582,10 +647,16 @@
repeated Job jobs = 15;
message JobCompletion {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Job name.
- optional string name = 1;
+ optional string name = 1 [
+ (android.privacy).dest = DEST_EXPLICIT
+ ];
message ReasonCount {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional android.app.JobParametersProto.CancelReason name = 1;
optional int32 count = 2;
}
@@ -594,6 +665,8 @@
repeated JobCompletion job_completion = 16;
message Network {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Mobile data traffic (total, background + foreground).
optional int64 mobile_bytes_rx = 1;
optional int64 mobile_bytes_tx = 2;
@@ -631,6 +704,8 @@
// TODO: combine System and App messages?
message PowerUseItem {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Estimated power use in mAh.
optional double computed_power_mah = 1;
// Starting in Oreo, Battery Settings has two modes to display the battery
@@ -648,6 +723,8 @@
// Durations are not pooled/apportioned.
message Process {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string name = 1;
// Time spent executing in user code.
optional int64 user_duration_ms = 2;
@@ -665,6 +742,8 @@
repeated Process process = 19;
message StateTime {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// All of these (non-deprecated) states are mutually exclusive and can be
// added together to find the total time a uid has had any processes running
// at all.
@@ -706,6 +785,8 @@
repeated StateTime states = 20;
message Sensor {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 id = 1;
optional TimerProto apportioned = 2;
// Background times aren't apportioned.
@@ -714,7 +795,11 @@
repeated Sensor sensors = 21;
message Sync {
- optional string name = 1;
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional string name = 1 [
+ (android.privacy).dest = DEST_EXPLICIT
+ ];
// Sync times aren't apportioned.
optional TimerProto total = 2;
optional TimerProto background = 3;
@@ -722,6 +807,8 @@
repeated Sync syncs = 22;
message UserActivity {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional android.os.PowerManagerProto.UserActivityEvent name = 1;
optional int32 count = 2;
};
@@ -736,6 +823,8 @@
// wakelocks. AggregatedWakelock, on the other hand, holds overall per-app
// wakelock data.
message AggregatedWakelock {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// The total duration that the app spent holding partial wakelocks.
// It includes both foreground + background use.
optional int64 partial_duration_ms = 1;
@@ -747,7 +836,11 @@
optional AggregatedWakelock aggregated_wakelock = 24;
message Wakelock {
- optional string name = 1;
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional string name = 1 [
+ (android.privacy).dest = DEST_EXPLICIT
+ ];
// Full wakelocks keep the screen on. Based on
// PowerManager.SCREEN_BRIGHT_WAKE_LOCK (deprecated in API 13) and
@@ -776,14 +869,20 @@
repeated Wakelock wakelocks = 25;
message WakeupAlarm {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Wakeup alarm name.
- optional string name = 1;
+ optional string name = 1 [
+ (android.privacy).dest = DEST_EXPLICIT
+ ];
// Only includes counts when screen-off (& on battery).
optional int32 count = 2;
}
repeated WakeupAlarm wakeup_alarm = 26;
message Wifi {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Duration holding Wifi-lock. This time is apportioned.
optional int64 full_wifi_lock_duration_ms = 1;
// Duration running Wifi. This time is apportioned.
diff --git a/core/proto/android/os/batterytype.proto b/core/proto/android/os/batterytype.proto
index 75d0dd3..2388c1e 100644
--- a/core/proto/android/os/batterytype.proto
+++ b/core/proto/android/os/batterytype.proto
@@ -20,6 +20,10 @@
option java_multiple_files = true;
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
message BatteryTypeProto {
- optional string type = 1;
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional string type = 1;
}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index e5efb90..828a55f 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -226,7 +226,10 @@
(section).args = "activity --proto service"
];
- optional com.android.server.am.proto.ProcessProto amprocesses = 3015;
+ optional com.android.server.am.proto.ProcessesProto amprocesses = 3015 [
+ (section).type = SECTION_DUMPSYS,
+ (section).args = "activity --proto processes"
+ ];
optional com.android.server.AlarmManagerServiceProto alarm = 3016 [
(section).type = SECTION_DUMPSYS,
diff --git a/core/proto/android/os/powermanager.proto b/core/proto/android/os/powermanager.proto
index e9f409d..8e0a607 100644
--- a/core/proto/android/os/powermanager.proto
+++ b/core/proto/android/os/powermanager.proto
@@ -19,6 +19,8 @@
option java_multiple_files = true;
+import "frameworks/base/core/proto/android/os/worksource.proto";
+
message PowerManagerProto {
/* User activity events in PowerManager.java. */
enum UserActivityEvent {
@@ -85,6 +87,14 @@
// the dozing state.
DRAW_WAKE_LOCK = 128;
}
+
+ // WakeLock class in android.os.PowerManager, it is the one used by sdk
+ message WakeLockProto {
+ optional string hex_string = 1;
+ optional bool held = 2;
+ optional int32 internal_count = 3;
+ optional WorkSourceProto work_source = 4;
+ }
}
message PowerManagerInternalProto {
diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto
index 95eb889..fd28322 100644
--- a/core/proto/android/providers/settings.proto
+++ b/core/proto/android/providers/settings.proto
@@ -392,8 +392,10 @@
optional SettingProto enable_smart_replies_in_notifications = 348;
optional SettingProto show_first_crash_dialog = 349;
optional SettingProto wifi_connected_mac_randomization_enabled = 350;
+ optional SettingProto show_restart_in_crash_dialog = 351;
+ optional SettingProto show_mute_in_crash_dialog = 352;
- // Next tag = 351;
+ // Next tag = 353;
}
message SecureSettingsProto {
@@ -596,8 +598,9 @@
optional SettingProto lockdown_in_power_menu = 194;
optional SettingProto backup_manager_constants = 169;
optional SettingProto show_first_crash_dialog_dev_option = 195;
+ optional SettingProto bluetooth_on_while_driving = 196;
- // Next tag = 196
+ // Next tag = 197
}
message SystemSettingsProto {
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index d3ca496..1434d82 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -18,11 +18,17 @@
package com.android.server.am.proto;
+import "frameworks/base/core/proto/android/app/activitymanager.proto";
import "frameworks/base/core/proto/android/app/notification.proto";
+import "frameworks/base/core/proto/android/app/profilerinfo.proto";
+import "frameworks/base/core/proto/android/content/component_name.proto";
+import "frameworks/base/core/proto/android/content/configuration.proto";
import "frameworks/base/core/proto/android/content/intent.proto";
+import "frameworks/base/core/proto/android/content/package_item_info.proto";
import "frameworks/base/core/proto/android/graphics/rect.proto";
import "frameworks/base/core/proto/android/internal/processstats.proto";
import "frameworks/base/core/proto/android/os/looper.proto";
+import "frameworks/base/core/proto/android/os/powermanager.proto";
import "frameworks/base/core/proto/android/server/intentresolver.proto";
import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
import "frameworks/base/core/proto/android/util/common.proto";
@@ -36,7 +42,7 @@
optional ActiveServicesProto services = 3;
- optional ProcessProto processes = 4;
+ optional ProcessesProto processes = 4;
}
// "dumpsys activity --proto activities"
@@ -130,6 +136,7 @@
optional int32 user_id = 4;
optional int32 app_id = 5;
optional int32 isolated_app_id = 6;
+ optional bool persistent = 7;
}
message BroadcastRecordProto {
@@ -459,7 +466,7 @@
WAIVE_PRIORITY = 6;
IMPORTANT = 7;
ADJUST_WITH_ACTIVITY = 8;
- FG_SERVICE_WHILE_WAKE = 9;
+ FG_SERVICE_WHILE_AWAKE = 9;
FG_SERVICE = 10;
TREAT_LIKE_ACTIVITY = 11;
VISIBLE = 12;
@@ -492,5 +499,362 @@
}
// TODO: "dumpsys activity --proto processes"
-message ProcessProto {
+message ProcessesProto {
+ repeated ProcessRecordProto procs = 1;
+ repeated ProcessRecordProto isolated_procs = 2;
+ repeated ActiveInstrumentationProto active_instrumentations = 3;
+ repeated UidRecordProto active_uids = 4;
+ repeated UidRecordProto validate_uids = 5;
+
+ // Process LRU list (sorted by oom_adj)
+ message LruProcesses {
+ optional int32 size = 1;
+ optional int32 non_act_at = 2;
+ optional int32 non_svc_at = 3;
+ repeated ProcessOomProto list = 4;
+ }
+ optional LruProcesses lru_procs = 6;
+ repeated ProcessRecordProto pids_self_locked = 7;
+ // Foreground Processes
+ repeated ImportanceTokenProto important_procs = 8;
+ // Persisent processes that are starting
+ repeated ProcessRecordProto persistent_starting_procs = 9;
+ // Processes that are being removed
+ repeated ProcessRecordProto removed_procs = 10;
+ // Processes that are on old until the system is ready
+ repeated ProcessRecordProto on_hold_procs = 11;
+ // Processes that are waiting to GC
+ repeated ProcessToGcProto gc_procs = 12;
+ optional AppErrorsProto app_errors = 13;
+ optional UserControllerProto user_controller = 14;
+ optional ProcessRecordProto home_proc = 15;
+ optional ProcessRecordProto previous_proc = 16;
+ optional int64 previous_proc_visible_time_ms = 17;
+ optional ProcessRecordProto heavy_weight_proc = 18;
+ optional .android.content.ConfigurationProto global_configuration = 19;
+ // ActivityStackSupervisorProto dumps these values as well, still here?
+ // repeated ActivityDisplayProto displays = 20;
+
+ optional bool config_will_change = 21;
+
+ message ScreenCompatPackage {
+ optional string package = 1;
+ optional int32 mode = 2;
+ }
+ repeated ScreenCompatPackage screen_compat_packages = 22;
+
+ message UidObserverRegistrationProto {
+ optional int32 uid = 1;
+ optional string package = 2;
+ repeated .android.app.UidObserverFlag flags = 3;
+ optional int32 cut_point = 4; // only available when UID_OBSERVER_PROCSTATE is on
+
+ message ProcState {
+ optional int32 uid = 1;
+ optional int32 state = 2;
+ }
+ repeated ProcState last_proc_states = 5;
+ }
+ repeated UidObserverRegistrationProto uid_observers = 23;
+ repeated int32 device_idle_whitelist = 24;
+ repeated int32 device_idle_temp_whitelist = 25;
+
+ message PendingTempWhitelist {
+ optional int32 target_uid = 1;
+ optional int64 duration_ms = 2;
+ optional string tag = 3;
+ }
+ repeated PendingTempWhitelist pending_temp_whitelist = 26;
+
+ message SleepStatus {
+ optional .android.os.PowerManagerInternalProto.Wakefulness wakefulness = 1;
+ repeated string sleep_tokens = 2;
+ optional bool sleeping = 3;
+ optional bool shutting_down = 4;
+ optional bool test_pss_mode = 5;
+ }
+ optional SleepStatus sleep_status = 27;
+
+ message VoiceProto {
+ optional string session = 1;
+ optional .android.os.PowerManagerProto.WakeLockProto wakelock = 2;
+ }
+ optional VoiceProto running_voice = 28;
+
+ message VrControllerProto {
+ enum VrMode {
+ FLAG_NON_VR_MODE = 0;
+ FLAG_VR_MODE = 1;
+ FLAG_PERSISTENT_VR_MODE = 2;
+ }
+ repeated VrMode vr_mode = 1;
+ optional int32 render_thread_id = 2;
+ }
+ optional VrControllerProto vr_controller = 29;
+
+ message DebugApp {
+ optional string debug_app = 1;
+ optional string orig_debug_app = 2;
+ optional bool debug_transient = 3;
+ optional bool orig_wait_for_debugger = 4;
+ }
+ optional DebugApp debug = 30;
+ optional AppTimeTrackerProto current_tracker = 31;
+
+ message MemWatchProcess {
+ message Process {
+ optional string name = 1;
+
+ message MemStats {
+ optional int32 uid = 1;
+ optional string size = 2;
+ optional string report_to = 3;
+ }
+ repeated MemStats mem_stats = 2;
+ }
+ repeated Process procs = 1;
+
+ message Dump {
+ optional string proc_name = 1;
+ optional string file = 2;
+ optional int32 pid = 3;
+ optional int32 uid = 4;
+ }
+ optional Dump dump = 2;
+ }
+ optional MemWatchProcess mem_watch_processes = 32;
+ optional string track_allocation_app = 33;
+
+ message Profile {
+ optional string app_name = 1;
+ optional ProcessRecordProto proc = 2;
+ optional .android.app.ProfilerInfoProto info = 3;
+ optional int32 type = 4;
+ }
+ optional Profile profile = 34;
+ optional string native_debugging_app = 35;
+ optional bool always_finish_activities = 36;
+
+ message Controller {
+ optional string controller = 1;
+ optional bool is_a_monkey = 2;
+ }
+ optional Controller controller = 37;
+
+ optional int32 total_persistent_procs = 38;
+ optional bool processes_ready = 39;
+ optional bool system_ready = 40;
+ optional bool booted = 41;
+ optional int32 factory_test = 42;
+ optional bool booting = 43;
+ optional bool call_finish_booting = 44;
+ optional bool boot_animation_complete = 45;
+ optional int64 last_power_check_uptime_ms = 46;
+ optional .android.os.PowerManagerProto.WakeLockProto going_to_sleep = 47;
+ optional .android.os.PowerManagerProto.WakeLockProto launching_activity = 48;
+ optional int32 adj_seq = 49;
+ optional int32 lru_seq = 50;
+ optional int32 num_non_cached_procs = 51;
+ optional int32 num_cached_hidden_procs = 52;
+ optional int32 num_service_procs = 53;
+ optional int32 new_num_service_procs = 54;
+ optional bool allow_lower_mem_level = 55;
+ optional int32 last_memory_level = 56;
+ optional int32 last_num_processes = 57;
+ optional .android.util.Duration last_idle_time = 58;
+ optional int64 low_ram_since_last_idle_ms = 59;
+}
+
+message ActiveInstrumentationProto {
+ optional .android.content.ComponentNameProto class = 1;
+ optional bool finished = 2;
+ repeated ProcessRecordProto running_processes = 3;
+ repeated string target_processes = 4;
+ optional .android.content.pm.ApplicationInfoProto target_info = 5;
+ optional string profile_file = 6;
+ optional string watcher = 7;
+ optional string ui_automation_connection = 8;
+ optional string arguments = 9;
+}
+
+// Proto definition of com.android.server.am.UidRecord.java
+message UidRecordProto {
+ optional string hex_hash = 1;
+ optional int32 uid = 2;
+ optional .android.app.ProcessState current = 3;
+ optional bool ephemeral = 4;
+ optional bool fg_services = 5;
+ optional bool whilelist = 6;
+ optional .android.util.Duration last_background_time = 7;
+ optional bool idle = 8;
+
+ enum Change {
+ CHANGE_GONE = 0;
+ CHANGE_IDLE = 1;
+ CHANGE_ACTIVE = 2;
+ CHANGE_CACHED = 3;
+ CHANGE_UNCACHED = 4;
+ }
+ repeated Change last_reported_changes = 9;
+ optional int32 num_procs = 10;
+
+ message ProcStateSequence {
+ optional int64 cururent = 1;
+ optional int64 last_network_updated = 2;
+ optional int64 last_dispatched = 3;
+ }
+ optional ProcStateSequence network_state_update = 11;
+}
+
+// proto of class ImportanceToken in ActivityManagerService
+message ImportanceTokenProto {
+ optional int32 pid = 1;
+ optional string token = 2;
+ optional string reason = 3;
+}
+
+message ProcessOomProto {
+ optional bool persistent = 1;
+ optional int32 num = 2;
+ optional string oom_adj = 3;
+
+ // Activity manager's version of Process enum, see ProcessList.java
+ enum SchedGroup {
+ SCHED_GROUP_UNKNOWN = -1;
+ SCHED_GROUP_BACKGROUND = 0;
+ SCHED_GROUP_DEFAULT = 1;
+ SCHED_GROUP_TOP_APP = 2;
+ SCHED_GROUP_TOP_APP_BOUND = 3;
+ }
+ optional SchedGroup sched_group = 4 [ default = SCHED_GROUP_UNKNOWN];
+
+ oneof Foreground {
+ bool activities = 5;
+ bool services = 6;
+ }
+
+ optional .android.app.ProcessState state = 7;
+ optional int32 trim_memory_level = 8;
+ optional ProcessRecordProto proc = 9;
+ optional string adj_type = 10;
+
+ oneof AdjTarget {
+ .android.content.ComponentNameProto adj_target_component_name = 11;
+ string adj_target_object = 12;
+ }
+
+ oneof AdjSource {
+ ProcessRecordProto adj_source_proc = 13;
+ string adj_source_object = 14;
+ }
+
+ message Detail {
+ optional int32 max_adj = 1;
+ optional int32 cur_raw_adj = 2;
+ optional int32 set_raw_adj = 3;
+ optional int32 cur_adj = 4;
+ optional int32 set_adj = 5;
+ optional .android.app.ProcessState current_state = 7;
+ optional .android.app.ProcessState set_state = 8;
+ optional string last_pss = 9;
+ optional string last_swap_pss = 10;
+ optional string last_cached_pss = 11;
+ optional bool cached = 12;
+ optional bool empty = 13;
+ optional bool has_above_client = 14;
+
+ // only make sense if process is a service
+ message CpuRunTime {
+ optional int64 over_ms = 1;
+ optional int64 used_ms = 2;
+ optional float ultilization = 3; // ratio of cpu time usage
+ }
+ optional CpuRunTime service_run_time = 15;
+ }
+ optional Detail detail = 15;
+}
+
+message ProcessToGcProto {
+ optional ProcessRecordProto proc = 1;
+ optional bool report_low_memory = 2;
+ optional int64 now_uptime_ms = 3;
+ optional int64 last_gced_ms = 4;
+ optional int64 last_low_memory_ms = 5;
+}
+
+// sync with com.android.server.am.AppErrors.java
+message AppErrorsProto {
+
+ optional int64 now_uptime_ms = 1;
+
+ message ProcessCrashTime {
+ optional string process_name = 1;
+
+ message Entry {
+ optional int32 uid = 1;
+ optional int64 last_crashed_at_ms = 2;
+ }
+ repeated Entry entries = 2;
+ }
+ repeated ProcessCrashTime process_crash_times = 2;
+
+ message BadProcess {
+ optional string process_name = 1;
+
+ message Entry {
+ optional int32 uid = 1;
+ optional int64 crashed_at_ms = 2;
+ optional string short_msg = 3;
+ optional string long_msg = 4;
+ optional string stack = 5;
+ }
+ repeated Entry entries = 2;
+ }
+ repeated BadProcess bad_processes = 3;
+}
+
+// sync with com.android.server.am.UserState.java
+message UserStateProto {
+ enum State {
+ STATE_BOOTING = 0;
+ STATE_RUNNING_LOCKED = 1;
+ STATE_RUNNING_UNLOCKING = 2;
+ STATE_RUNNING_UNLOCKED = 3;
+ STATE_STOPPING = 4;
+ STATE_SHUTDOWN = 5;
+ }
+ optional State state = 1;
+ optional bool switching = 2;
+}
+
+// sync with com.android.server.am.UserController.java
+message UserControllerProto {
+ message User {
+ optional int32 id = 1;
+ optional UserStateProto state = 2;
+ }
+ repeated User started_users = 1;
+ repeated int32 started_user_array = 2;
+ repeated int32 user_lru = 3;
+
+ message UserProfile {
+ optional int32 user = 1;
+ optional int32 profile = 2;
+ }
+ repeated UserProfile user_profile_group_ids = 4;
+}
+
+// sync with com.android.server.am.AppTimeTracker.java
+message AppTimeTrackerProto {
+ optional string receiver = 1;
+ optional int64 total_duration_ms = 2;
+
+ message PackageTime {
+ optional string package = 1;
+ optional int64 duration_ms = 2;
+ }
+ repeated PackageTime package_times = 3;
+
+ optional .android.util.Duration started_time = 4;
+ optional string started_package = 5;
}
diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto
index 87d302e..53b4be4 100644
--- a/core/proto/android/server/alarmmanagerservice.proto
+++ b/core/proto/android/server/alarmmanagerservice.proto
@@ -47,6 +47,8 @@
// Only valid if is_interactive is false.
optional int64 time_until_next_non_wakeup_delivery_ms = 11;
+ // Can be negative if the non-wakeup alarm time is in the past (non-wakeup
+ // alarms aren't delivered unil the next time the device wakes up).
optional int64 time_until_next_non_wakeup_alarm_ms = 12;
optional int64 time_until_next_wakeup_ms = 13;
optional int64 time_since_last_wakeup_ms = 14;
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index f72ca62..739fca3 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -558,4 +558,6 @@
optional int64 last_successful_run_time = 22;
optional int64 last_failed_run_time = 23;
+
+ optional int64 internal_flags = 24;
}
diff --git a/core/proto/android/service/battery.proto b/core/proto/android/service/battery.proto
index 4cb7fd3..42fa72c 100644
--- a/core/proto/android/service/battery.proto
+++ b/core/proto/android/service/battery.proto
@@ -21,8 +21,11 @@
option java_outer_classname = "BatteryServiceProto";
import "frameworks/base/core/proto/android/os/batterymanager.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
message BatteryServiceDumpProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
enum BatteryStatus {
BATTERY_STATUS_INVALID = 0;
BATTERY_STATUS_UNKNOWN = 1;
diff --git a/core/proto/android/service/batterystats.proto b/core/proto/android/service/batterystats.proto
index 54d3f40..e31e7f3 100644
--- a/core/proto/android/service/batterystats.proto
+++ b/core/proto/android/service/batterystats.proto
@@ -21,7 +21,10 @@
option java_outer_classname = "BatteryStatsServiceProto";
import "frameworks/base/core/proto/android/os/batterystats.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
message BatteryStatsServiceDumpProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional android.os.BatteryStatsProto batterystats = 1;
}
diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto
index 7a0e152..65df89a 100644
--- a/core/proto/android/service/notification.proto
+++ b/core/proto/android/service/notification.proto
@@ -24,6 +24,7 @@
import "frameworks/base/core/proto/android/app/notification_channel_group.proto";
import "frameworks/base/core/proto/android/app/notificationmanager.proto";
import "frameworks/base/core/proto/android/content/component_name.proto";
+import "frameworks/base/core/proto/android/media/audioattributes.proto";
message NotificationServiceDumpProto {
repeated NotificationRecordProto records = 1;
@@ -55,7 +56,7 @@
optional int32 flags = 3;
optional string channelId = 4;
optional string sound = 5;
- optional int32 sound_usage = 6;
+ optional .android.media.AudioAttributesProto audio_attributes = 6;
optional bool can_vibrate = 7;
optional bool can_show_light = 8;
optional string group_key = 9;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 93d852c..f1a9bd4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -327,6 +327,10 @@
<protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK" />
<protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK" />
<protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_NETWORK_AFTER_FAILURE" />
+ <protected-broadcast android:name="com.android.server.wifi.wakeup.DISMISS_NOTIFICATION" />
+ <protected-broadcast android:name="com.android.server.wifi.wakeup.OPEN_WIFI_PREFERENCES" />
+ <protected-broadcast android:name="com.android.server.wifi.wakeup.OPEN_WIFI_SETTINGS" />
+ <protected-broadcast android:name="com.android.server.wifi.wakeup.TURN_OFF_WIFI_WAKE" />
<protected-broadcast android:name="android.net.wifi.WIFI_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_AP_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_CREDENTIAL_CHANGED" />
@@ -510,6 +514,7 @@
<protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
<protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" />
<protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" />
+ <protected-broadcast android:name="android.app.action.APP_BLOCK_STATE_CHANGED" />
<protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
<protected-broadcast android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS" />
@@ -573,6 +578,7 @@
<!-- Added in P -->
<protected-broadcast android:name="android.app.action.PROFILE_OWNER_CHANGED" />
<protected-broadcast android:name="android.app.action.TRANSFER_OWNERSHIP_COMPLETE" />
+ <protected-broadcast android:name="android.app.action.AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE" />
<protected-broadcast android:name="android.app.action.DATA_SHARING_RESTRICTION_CHANGED" />
<!-- ====================================================================== -->
@@ -1929,6 +1935,12 @@
<permission android:name="android.permission.START_ANY_ACTIVITY"
android:protectionLevel="signature" />
+ <!-- Allows an application to start an activity as another app, provided that app has been
+ granted a permissionToken from the ActivityManagerService.
+ @hide -->
+ <permission android:name="android.permission.START_ACTIVITY_AS_CALLER"
+ android:protectionLevel="signature" />
+
<!-- @deprecated The {@link android.app.ActivityManager#restartPackage}
API is no longer supported. -->
<permission android:name="android.permission.RESTART_PACKAGES"
@@ -3706,6 +3718,15 @@
<permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"
android:protectionLevel="signature|development|instant|appop" />
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground}.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE"
+ android:description="@string/permdesc_foregroundService"
+ android:label="@string/permlab_foregroundService"
+ android:protectionLevel="normal|instant" />
+
<!-- @hide Allows system components to access all app shortcuts. -->
<permission android:name="android.permission.ACCESS_SHORTCUTS"
android:protectionLevel="signature" />
diff --git a/core/res/res/drawable/ic_info_outline_24.xml b/core/res/res/drawable/ic_info_outline_24.xml
new file mode 100644
index 0000000..abba8cf
--- /dev/null
+++ b/core/res/res/drawable/ic_info_outline_24.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
+</vector>
diff --git a/core/res/res/layout/app_error_dialog.xml b/core/res/res/layout/app_error_dialog.xml
index d78ce59..c3b149a 100644
--- a/core/res/res/layout/app_error_dialog.xml
+++ b/core/res/res/layout/app_error_dialog.xml
@@ -18,48 +18,50 @@
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingTop="@dimen/aerr_padding_list_top"
+ android:paddingBottom="@dimen/aerr_padding_list_bottom">
+
+ <Button
+ android:id="@+id/aerr_restart"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
- android:paddingTop="@dimen/aerr_padding_list_top"
- android:paddingBottom="@dimen/aerr_padding_list_bottom">
-
+ android:text="@string/aerr_restart"
+ android:drawableStart="@drawable/ic_refresh"
+ style="@style/aerr_list_item" />
<Button
- android:id="@+id/aerr_restart"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/aerr_restart"
- android:drawableStart="@drawable/ic_refresh"
- style="@style/aerr_list_item"
- />
+ android:id="@+id/aerr_app_info"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/app_info"
+ android:drawableStart="@drawable/ic_info_outline_24"
+ style="@style/aerr_list_item" />
<Button
- android:id="@+id/aerr_close"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/aerr_close_app"
- android:drawableStart="@drawable/ic_close"
- style="@style/aerr_list_item"
- />
+ android:id="@+id/aerr_close"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_close_app"
+ android:drawableStart="@drawable/ic_close"
+ style="@style/aerr_list_item" />
<Button
- android:id="@+id/aerr_report"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/aerr_report"
- android:drawableStart="@drawable/ic_feedback"
- style="@style/aerr_list_item"
- />
+ android:id="@+id/aerr_report"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_report"
+ android:drawableStart="@drawable/ic_feedback"
+ style="@style/aerr_list_item" />
<Button
- android:id="@+id/aerr_mute"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/aerr_mute"
- android:drawableStart="@drawable/ic_eject_24dp"
- style="@style/aerr_list_item"
- />
-
+ android:id="@+id/aerr_mute"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_mute"
+ android:drawableStart="@drawable/ic_eject_24dp"
+ style="@style/aerr_list_item" />
</LinearLayout>
diff --git a/core/res/res/layout/notification_template_material_ambient.xml b/core/res/res/layout/notification_template_material_ambient.xml
index 435289d..19c4d23 100644
--- a/core/res/res/layout/notification_template_material_ambient.xml
+++ b/core/res/res/layout/notification_template_material_ambient.xml
@@ -57,7 +57,7 @@
android:singleLine="true"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
- android:textSize="20sp"
+ android:textSize="24sp"
android:textColor="#ffffffff"
/>
<TextView android:id="@+id/text"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index ffe68dc..354d658 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1897,6 +1897,7 @@
<enum name="KEYCODE_SYSTEM_NAVIGATION_LEFT" value="282" />
<enum name="KEYCODE_SYSTEM_NAVIGATION_RIGHT" value="283" />
<enum name="KEYCODE_ALL_APPS" value="284" />
+ <enum name="KEYCODE_REFRESH" value="285" />
</attr>
<!-- ***************************************************************** -->
@@ -2066,7 +2067,8 @@
<p>For this to take effect, the window must be drawing the system bar backgrounds with
{@link android.R.attr#windowDrawsSystemBarBackgrounds} and the navigation bar must not
have been requested to be translucent with
- {@link android.R.attr#windowTranslucentNavigation}. -->
+ {@link android.R.attr#windowTranslucentNavigation}.
+ Corresponds to {@link android.view.Window#setNavigationBarDividerColor(int)}. -->
<attr name="navigationBarDividerColor" format="color" />
<!-- The duration, in milliseconds, of the window background fade duration
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2d4af46..66e56bf 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2283,7 +2283,10 @@
Can be customized for other product types -->
<string name="config_chooseTypeAndAccountActivity" translatable="false"
>android/android.accounts.ChooseTypeAndAccountActivity</string>
-
+ <!-- Name of the activity that will handle requests to the system to choose an activity for
+ the purposes of resolving an intent. -->
+ <string name="config_chooserActivity" translatable="false"
+ >com.android.systemui/com.android.systemui.chooser.ChooserActivity</string>
<!-- Component name of a custom ResolverActivity (Intent resolver) to be used instead of
the default framework version. If left empty, then the framework version will be used.
Example: com.google.android.myapp/.resolver.MyResolverActivity -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9cab9fa..71e963a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -650,11 +650,11 @@
<!-- Label for the Android system components when they are shown to the user. -->
<string name="android_system_label">Android System</string>
- <!-- Label for the user owner in the intent forwarding app. -->
- <string name="user_owner_label">Switch to Personal</string>
+ <!-- "Switch" is a verb; it means to change user profile by tapping another user profile name. -->
+ <string name="user_owner_label">Switch to personal profile</string>
- <!-- Label for a corporate profile in the intent forwarding app. -->
- <string name="managed_profile_label">Switch to Work</string>
+ <!-- "Switch" is a verb; it means to change user profile by tapping another user profile name. -->
+ <string name="managed_profile_label">Switch to work profile</string>
<!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgrouplab_contacts">Contacts</string>
@@ -916,6 +916,11 @@
<string name="permdesc_persistentActivity" product="default">Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down the phone.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundService">run foreground service</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundService">Allows the app to make use of foreground services.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_getPackageSize">measure app storage space</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_getPackageSize">Allows the app to retrieve its code, data, and cache sizes</string>
@@ -1321,6 +1326,9 @@
<string-array name="fingerprint_acquired_vendor">
</string-array>
+ <!-- Message shown by the fingerprint dialog when fingerprint is not recognized -->
+ <string name="fingerprint_not_recognized">Not recognized</string>
+
<!-- Error message shown when the fingerprint hardware can't be accessed -->
<string name="fingerprint_error_hw_not_available">Fingerprint hardware not available.</string>
<!-- Error message shown when the fingerprint hardware has run out of room for storing fingerprints -->
@@ -1329,6 +1337,8 @@
<string name="fingerprint_error_timeout">Fingerprint time out reached. Try again.</string>
<!-- Generic error message shown when the fingerprint operation (e.g. enrollment or authentication) is canceled. Generally not shown to the user-->
<string name="fingerprint_error_canceled">Fingerprint operation canceled.</string>
+ <!-- Generic error message shown when the fingerprint authentication operation is canceled due to user input. Generally not shown to the user -->
+ <string name="fingerprint_error_user_canceled">Fingerprint operation canceled by user.</string>
<!-- Generic error message shown when the fingerprint operation fails because too many attempts have been made. -->
<string name="fingerprint_error_lockout">Too many attempts. Try again later.</string>
<!-- Generic error message shown when the fingerprint operation fails because strong authentication is required -->
@@ -4604,11 +4614,10 @@
<!-- Menu item in the locale menu [CHAR LIMIT=30] -->
<string name="locale_search_menu">Search</string>
- <!-- Title for dialog displayed when work profile is turned off. [CHAR LIMIT=30] -->
- <string name="work_mode_off_title">Turn on work mode?</string>
- <!-- Message displayed in dialog when work profile is turned off. [CHAR LIMIT=NONE] -->
- <string name="work_mode_off_message">This will turn on your work profile, including apps,
- background sync, and related features</string>
+ <!-- Title of a dialog. The string is asking if the user wants to turn on their work profile, which contains work apps that are managed by their employer. "Work" is an adjective. [CHAR LIMIT=30] -->
+ <string name="work_mode_off_title">Turn on work profile?</string>
+ <!-- Text in a dialog. This string describes what will happen if a user decides to turn on their work profile. "Work profile" is used as an adjective. [CHAR LIMIT=NONE] -->
+ <string name="work_mode_off_message">Your work apps, notifications, data, and other work profile features will be turned on</string>
<!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] -->
<string name="work_mode_turn_on">Turn on</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4ef0a6c..ee20873 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1067,6 +1067,7 @@
<java-symbol type="string" name="owner_name" />
<java-symbol type="string" name="config_chooseAccountActivity" />
<java-symbol type="string" name="config_chooseTypeAndAccountActivity" />
+ <java-symbol type="string" name="config_chooserActivity" />
<java-symbol type="string" name="config_customResolverActivity" />
<java-symbol type="string" name="config_appsAuthorizedForSharedAccounts" />
<java-symbol type="string" name="error_message_title" />
@@ -2341,9 +2342,11 @@
<java-symbol type="string" name="fingerprint_acquired_too_fast" />
<java-symbol type="array" name="fingerprint_acquired_vendor" />
<java-symbol type="string" name="fingerprint_error_canceled" />
+ <java-symbol type="string" name="fingerprint_error_user_canceled" />
<java-symbol type="string" name="fingerprint_error_lockout" />
<java-symbol type="string" name="fingerprint_error_lockout_permanent" />
<java-symbol type="string" name="fingerprint_name_template" />
+ <java-symbol type="string" name="fingerprint_not_recognized" />
<!-- Fingerprint config -->
<java-symbol type="integer" name="config_fingerprintMaxTemplatesPerUser"/>
@@ -2650,6 +2653,7 @@
<java-symbol type="id" name="aerr_report" />
<java-symbol type="id" name="aerr_restart" />
<java-symbol type="id" name="aerr_close" />
+ <java-symbol type="id" name="aerr_app_info" />
<java-symbol type="id" name="aerr_mute" />
<java-symbol type="string" name="status_bar_rotate" />
diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml
index bc4b10f..d80c697 100644
--- a/core/res/res/xml/power_profile.xml
+++ b/core/res/res/xml/power_profile.xml
@@ -127,4 +127,11 @@
</array>
<item name="modem.controller.voltage">0</item>
+ <!-- GPS related values. Default is 0.-->
+ <array name="gps.signalqualitybased"> <!-- Strength 0 to 1 -->
+ <value>0</value>
+ <value>0</value>
+ </array>
+ <item name="gps.voltage">0</item>
+
</device>
diff --git a/core/tests/benchmarks/src/com/android/internal/net/NetworkStatsFactoryBenchmark.java b/core/tests/benchmarks/src/com/android/internal/net/NetworkStatsFactoryBenchmark.java
index e62fbd6..c213464 100644
--- a/core/tests/benchmarks/src/com/android/internal/net/NetworkStatsFactoryBenchmark.java
+++ b/core/tests/benchmarks/src/com/android/internal/net/NetworkStatsFactoryBenchmark.java
@@ -53,7 +53,7 @@
stats, mStats.getAbsolutePath(), NetworkStats.UID_ALL,
// Looks like this was broken by change d0c5b9abed60b7bc056d026bf0f2b2235410fb70
// Fixed compilation problem but needs addressing properly.
- new String[0], 999);
+ new String[0], 999, false);
}
}
}
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 47990a1..60b46b4 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -53,6 +53,10 @@
android.test.base \
android.test.mock \
+ifeq ($(REMOVE_OAHL_FROM_BCP),true)
+LOCAL_JAVA_LIBRARIES += framework-oahl-backward-compatibility
+endif
+
LOCAL_PACKAGE_NAME := FrameworksCoreTests
LOCAL_COMPATIBILITY_SUITE := device-tests
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index e094772..3e38010 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -51,6 +51,7 @@
<uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
<uses-permission android:name="android.permission.DELETE_CACHE_FILES" />
<uses-permission android:name="android.permission.DOWNLOAD_CACHE_NON_PURGEABLE" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.INJECT_EVENTS" />
diff --git a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
index 63a5e4c..6996e50 100644
--- a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
@@ -73,9 +73,13 @@
public void targeted_at_O() {
mPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
PackageBackwardCompatibility.modifySharedLibraries(mPackage);
- assertEquals("usesLibraries not updated correctly",
- arrayList(ORG_APACHE_HTTP_LEGACY),
- mPackage.usesLibraries);
+ if (PackageBackwardCompatibility.removeOAHLFromBCP()) {
+ assertEquals("usesLibraries not updated correctly",
+ arrayList(ORG_APACHE_HTTP_LEGACY),
+ mPackage.usesLibraries);
+ } else {
+ assertNull("usesOptionalLibraries not updated correctly", mPackage.usesLibraries);
+ }
assertNull("usesOptionalLibraries not updated correctly", mPackage.usesOptionalLibraries);
}
@@ -84,10 +88,16 @@
mPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
mPackage.usesLibraries = arrayList(OTHER_LIBRARY);
PackageBackwardCompatibility.modifySharedLibraries(mPackage);
- // The org.apache.http.legacy jar should be added at the start of the list.
- assertEquals("usesLibraries not updated correctly",
- arrayList(ORG_APACHE_HTTP_LEGACY, OTHER_LIBRARY),
- mPackage.usesLibraries);
+ if (PackageBackwardCompatibility.removeOAHLFromBCP()) {
+ // The org.apache.http.legacy jar should be added at the start of the list.
+ assertEquals("usesLibraries not updated correctly",
+ arrayList(ORG_APACHE_HTTP_LEGACY, OTHER_LIBRARY),
+ mPackage.usesLibraries);
+ } else {
+ assertEquals("usesLibraries not updated correctly",
+ arrayList(OTHER_LIBRARY),
+ mPackage.usesLibraries);
+ }
assertNull("usesOptionalLibraries not updated correctly", mPackage.usesOptionalLibraries);
}
@@ -96,9 +106,13 @@
mPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
mPackage.usesLibraries = arrayList(ORG_APACHE_HTTP_LEGACY);
PackageBackwardCompatibility.modifySharedLibraries(mPackage);
- assertEquals("usesLibraries not updated correctly",
- arrayList(ORG_APACHE_HTTP_LEGACY),
- mPackage.usesLibraries);
+ if (PackageBackwardCompatibility.removeOAHLFromBCP()) {
+ assertEquals("usesLibraries not updated correctly",
+ arrayList(ORG_APACHE_HTTP_LEGACY),
+ mPackage.usesLibraries);
+ } else {
+ assertNull("usesLibraries not updated correctly", mPackage.usesLibraries);
+ }
assertNull("usesOptionalLibraries not updated correctly", mPackage.usesOptionalLibraries);
}
@@ -108,18 +122,27 @@
mPackage.usesOptionalLibraries = arrayList(ORG_APACHE_HTTP_LEGACY);
PackageBackwardCompatibility.modifySharedLibraries(mPackage);
assertNull("usesLibraries not updated correctly", mPackage.usesLibraries);
- assertEquals("usesOptionalLibraries not updated correctly",
- arrayList(ORG_APACHE_HTTP_LEGACY),
- mPackage.usesOptionalLibraries);
+ if (PackageBackwardCompatibility.removeOAHLFromBCP()) {
+ assertEquals("usesOptionalLibraries not updated correctly",
+ arrayList(ORG_APACHE_HTTP_LEGACY),
+ mPackage.usesOptionalLibraries);
+ } else {
+ assertNull("usesOptionalLibraries not updated correctly",
+ mPackage.usesOptionalLibraries);
+ }
}
@Test
public void org_apache_http_legacy_in_usesLibraries() {
mPackage.usesLibraries = arrayList(ORG_APACHE_HTTP_LEGACY);
PackageBackwardCompatibility.modifySharedLibraries(mPackage);
- assertEquals("usesLibraries not updated correctly",
- arrayList(ORG_APACHE_HTTP_LEGACY),
- mPackage.usesLibraries);
+ if (PackageBackwardCompatibility.removeOAHLFromBCP()) {
+ assertEquals("usesLibraries not updated correctly",
+ arrayList(ORG_APACHE_HTTP_LEGACY),
+ mPackage.usesLibraries);
+ } else {
+ assertNull("usesLibraries not updated correctly", mPackage.usesLibraries);
+ }
assertNull("usesOptionalLibraries not updated correctly", mPackage.usesOptionalLibraries);
}
@@ -128,9 +151,14 @@
mPackage.usesOptionalLibraries = arrayList(ORG_APACHE_HTTP_LEGACY);
PackageBackwardCompatibility.modifySharedLibraries(mPackage);
assertNull("usesLibraries not updated correctly", mPackage.usesLibraries);
- assertEquals("usesOptionalLibraries not updated correctly",
- arrayList(ORG_APACHE_HTTP_LEGACY),
- mPackage.usesOptionalLibraries);
+ if (PackageBackwardCompatibility.removeOAHLFromBCP()) {
+ assertEquals("usesOptionalLibraries not updated correctly",
+ arrayList(ORG_APACHE_HTTP_LEGACY),
+ mPackage.usesOptionalLibraries);
+ } else {
+ assertNull("usesOptionalLibraries not updated correctly",
+ mPackage.usesOptionalLibraries);
+ }
}
@Test
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 7403c26..08d023d 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -332,7 +332,9 @@
Settings.Global.SETUP_PREPAID_DETECTION_TARGET_URL,
Settings.Global.SHORTCUT_MANAGER_CONSTANTS,
Settings.Global.SHOW_FIRST_CRASH_DIALOG,
+ Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG,
Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS,
+ Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG,
Settings.Global.SHOW_TEMPERATURE_WARNING,
Settings.Global.SMART_SELECTION_UPDATE_CONTENT_URL,
Settings.Global.SMART_SELECTION_UPDATE_METADATA_URL,
@@ -428,7 +430,8 @@
Settings.Global.ZEN_MODE,
Settings.Global.ZEN_MODE_CONFIG_ETAG,
Settings.Global.ZEN_MODE_RINGER_LEVEL,
- Settings.Global.ZRAM_ENABLED);
+ Settings.Global.ZRAM_ENABLED,
+ Settings.Global.OVERRIDE_SETTINGS_PROVIDER_RESTORE_ANY_VERSION);
private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
newHashSet(
@@ -481,7 +484,6 @@
Settings.Secure.INSTALL_NON_MARKET_APPS,
Settings.Secure.LAST_SETUP_SHOWN,
Settings.Secure.LOCATION_MODE,
- Settings.Secure.LOCATION_PREVIOUS_MODE,
Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, // Candidate?
Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, // Candidate?
Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
@@ -538,7 +540,8 @@
Settings.Secure.BACKUP_MANAGER_CONSTANTS,
Settings.Secure.KEYGUARD_SLICE_URI,
Settings.Secure.PARENTAL_CONTROL_ENABLED,
- Settings.Secure.PARENTAL_CONTROL_REDIRECT_URL);
+ Settings.Secure.PARENTAL_CONTROL_REDIRECT_URL,
+ Settings.Secure.BLUETOOTH_ON_WHILE_DRIVING);
@Test
public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java b/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java
index 4c4aeaf..e750766 100644
--- a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java
@@ -17,6 +17,8 @@
package android.provider;
import static org.junit.Assert.fail;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import android.platform.test.annotations.Presubmit;
import android.provider.SettingsValidators.Validator;
@@ -28,13 +30,101 @@
import java.util.Map;
-/** Tests that ensure all backed up settings have non-null validators. */
+/**
+ * Tests that ensure all backed up settings have non-null validators. Also, common validators
+ * are tested.
+ */
@Presubmit
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SettingsValidatorsTest {
@Test
+ public void testNonNegativeIntegerValidator() {
+ assertTrue(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("1"));
+ assertTrue(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("0"));
+ assertFalse(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("-1"));
+ assertFalse(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("rectangle"));
+ }
+
+ @Test
+ public void testAnyIntegerValidator() {
+ assertTrue(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("1"));
+ assertTrue(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("0"));
+ assertTrue(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("-1"));
+ assertFalse(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("rectangle"));
+ }
+
+ @Test
+ public void testComponentNameValidator() {
+ assertTrue(SettingsValidators.COMPONENT_NAME_VALIDATOR.validate(
+ "android/com.android.internal.backup.LocalTransport"));
+ assertFalse(SettingsValidators.COMPONENT_NAME_VALIDATOR.validate("rectangle"));
+ }
+
+ @Test
+ public void testLocaleValidator() {
+ assertTrue(SettingsValidators.LOCALE_VALIDATOR.validate("en_US"));
+ assertTrue(SettingsValidators.LOCALE_VALIDATOR.validate("es"));
+ assertFalse(SettingsValidators.LOCALE_VALIDATOR.validate("rectangle"));
+ }
+
+ @Test
+ public void testPackageNameValidator() {
+ assertTrue(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(
+ "com.google.android"));
+ assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate("com.google.@android"));
+ assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(".com.google.android"));
+ assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(".com.google.5android"));
+ }
+
+ @Test
+ public void testDiscreteValueValidator() {
+ String[] beerTypes = new String[]{"Ale", "American IPA", "Stout"};
+ Validator v = new SettingsValidators.DiscreteValueValidator(beerTypes);
+ assertTrue(v.validate("Ale"));
+ assertTrue(v.validate("American IPA"));
+ assertTrue(v.validate("Stout"));
+ assertFalse(v.validate("Cider")); // just juice pretending to be beer
+ }
+
+ @Test
+ public void testInclusiveIntegerRangeValidator() {
+ Validator v = new SettingsValidators.InclusiveIntegerRangeValidator(0, 5);
+ assertTrue(v.validate("0"));
+ assertTrue(v.validate("2"));
+ assertTrue(v.validate("5"));
+ assertFalse(v.validate("-1"));
+ assertFalse(v.validate("6"));
+ }
+
+ @Test
+ public void testInclusiveFloatRangeValidator() {
+ Validator v = new SettingsValidators.InclusiveFloatRangeValidator(0.0f, 5.0f);
+ assertTrue(v.validate("0.0"));
+ assertTrue(v.validate("2.0"));
+ assertTrue(v.validate("5.0"));
+ assertFalse(v.validate("-1.0"));
+ assertFalse(v.validate("6.0"));
+ }
+
+ @Test
+ public void testComponentNameListValidator() {
+ Validator v = new SettingsValidators.ComponentNameListValidator(",");
+ assertTrue(v.validate("android/com.android.internal.backup.LocalTransport,"
+ + "com.google.android.gms/.backup.migrate.service.D2dTransport"));
+ assertFalse(v.validate("com.google.5android,android"));
+ }
+
+ @Test
+ public void testPackageNameListValidator() {
+ Validator v = new SettingsValidators.PackageNameListValidator(",");
+ assertTrue(v.validate("com.android.internal.backup.LocalTransport,com.google.android.gms"));
+ assertFalse(v.validate("5com.android.internal.backup.LocalTransport,android"));
+ }
+
+
+ @Test
public void ensureAllBackedUpSystemSettingsHaveValidators() {
String offenders = getOffenders(concat(Settings.System.SETTINGS_TO_BACKUP,
Settings.System.LEGACY_RESTORE_SETTINGS), Settings.System.VALIDATORS);
diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
index b18fa74..c0bc3a8 100644
--- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
@@ -24,6 +24,7 @@
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.UserHandle;
import android.os.UserManager;
import android.support.test.InstrumentationRegistry;
@@ -269,8 +270,8 @@
}
@Override
- public void startActivityAsCaller(Intent intent, @Nullable Bundle options, boolean
- ignoreTargetSecurity, int userId) {
+ public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
+ IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
mStartActivityIntent = intent;
mUserIdActivityLaunchedIn = userId;
}
@@ -293,4 +294,4 @@
return mPm;
}
}
-}
\ No newline at end of file
+}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 4be6408..09192f4 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -172,6 +172,8 @@
<assign-permission name="android.permission.ACCESS_LOWPAN_STATE" uid="lowpan" />
<assign-permission name="android.permission.MANAGE_LOWPAN_INTERFACES" uid="lowpan" />
+ <assign-permission name="android.permission.DUMP" uid="statsd" />
+ <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="statsd" />
<assign-permission name="android.permission.STATSCOMPANION" uid="statsd" />
<!-- This is a list of all the libraries available for application
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index c0958cd..0949a90 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -368,6 +368,7 @@
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/>
+ <permission name="android.permission.START_ACTIVITY_AS_CALLER"/>
<permission name="android.permission.START_TASKS_FROM_RECENTS"/>
<permission name="android.permission.STATUS_BAR"/>
<permission name="android.permission.STOP_APP_SWITCHES"/>
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 74f8c71..8699cb4 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -192,7 +192,7 @@
# key 170 "KEY_ISO"
key 171 MUSIC
key 172 HOME
-# key 173 "KEY_REFRESH"
+key 173 REFRESH
# key 174 "KEY_EXIT"
# key 175 "KEY_MOVE"
# key 176 "KEY_EDIT"
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 317144a..5a80ee2 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2742,7 +2742,7 @@
* @param offset index of caret position
* @return width measurement between start and offset
*/
- public float getRunAdvance(@NonNull CharSequence text, int start, int end, int contextStart,
+ public float getRunAdvance(CharSequence text, int start, int end, int contextStart,
int contextEnd, boolean isRtl, int offset) {
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index da170c0..6d3ddd5 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -118,9 +118,12 @@
@Override
public void draw(@NonNull Canvas canvas) {
- long nextUpdate = nDraw(mNativePtr, canvas.getNativeCanvasWrapper(),
- SystemClock.uptimeMillis());
- scheduleSelf(mRunnable, nextUpdate);
+ long nextUpdate = nDraw(mNativePtr, canvas.getNativeCanvasWrapper());
+ // a value <= 0 indicates that the drawable is stopped or that renderThread
+ // will manage the animation
+ if (nextUpdate > 0) {
+ scheduleSelf(mRunnable, nextUpdate);
+ }
}
@Override
@@ -130,6 +133,7 @@
+ " 255! provided " + alpha);
}
nSetAlpha(mNativePtr, alpha);
+ invalidateSelf();
}
@Override
@@ -141,6 +145,7 @@
public void setColorFilter(@Nullable ColorFilter colorFilter) {
long nativeFilter = colorFilter == null ? 0 : colorFilter.getNativeInstance();
nSetColorFilter(mNativePtr, nativeFilter);
+ invalidateSelf();
}
@Override
@@ -161,7 +166,10 @@
@Override
public void start() {
- nStart(mNativePtr);
+ if (isRunning() == false) {
+ nStart(mNativePtr);
+ invalidateSelf();
+ }
}
@Override
@@ -173,7 +181,7 @@
@Nullable ImageDecoder decoder, int width, int height, Rect cropRect)
throws IOException;
private static native long nGetNativeFinalizer();
- private static native long nDraw(long nativePtr, long canvasNativePtr, long msecs);
+ private static native long nDraw(long nativePtr, long canvasNativePtr);
private static native void nSetAlpha(long nativePtr, int alpha);
private static native int nGetAlpha(long nativePtr);
private static native void nSetColorFilter(long nativePtr, long nativeFilter);
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index 3a5f7b7..e3740e3 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -27,7 +27,6 @@
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
-import android.graphics.ImageDecoder;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Outline;
@@ -50,7 +49,6 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -113,7 +111,7 @@
*/
@Deprecated
public BitmapDrawable() {
- init(new BitmapState((Bitmap) null), null);
+ mBitmapState = new BitmapState((Bitmap) null);
}
/**
@@ -126,7 +124,8 @@
@SuppressWarnings("unused")
@Deprecated
public BitmapDrawable(Resources res) {
- init(new BitmapState((Bitmap) null), res);
+ mBitmapState = new BitmapState((Bitmap) null);
+ mBitmapState.mTargetDensity = mTargetDensity;
}
/**
@@ -136,7 +135,7 @@
*/
@Deprecated
public BitmapDrawable(Bitmap bitmap) {
- init(new BitmapState(bitmap), null);
+ this(new BitmapState(bitmap), null);
}
/**
@@ -144,7 +143,8 @@
* the display metrics of the resources.
*/
public BitmapDrawable(Resources res, Bitmap bitmap) {
- init(new BitmapState(bitmap), res);
+ this(new BitmapState(bitmap), res);
+ mBitmapState.mTargetDensity = mTargetDensity;
}
/**
@@ -154,7 +154,10 @@
*/
@Deprecated
public BitmapDrawable(String filepath) {
- this(null, filepath);
+ this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
+ if (mBitmapState.mBitmap == null) {
+ android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
+ }
}
/**
@@ -162,21 +165,10 @@
*/
@SuppressWarnings("unused")
public BitmapDrawable(Resources res, String filepath) {
- Bitmap bitmap = null;
- try (FileInputStream stream = new FileInputStream(filepath)) {
- bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, stream),
- (decoder, info, src) -> {
- decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
- });
- } catch (Exception e) {
- /* do nothing. This matches the behavior of BitmapFactory.decodeFile()
- If the exception happened on decode, mBitmapState.mBitmap will be null.
- */
- } finally {
- init(new BitmapState(bitmap), res);
- if (mBitmapState.mBitmap == null) {
- android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
- }
+ this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
+ mBitmapState.mTargetDensity = mTargetDensity;
+ if (mBitmapState.mBitmap == null) {
+ android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
}
}
@@ -187,7 +179,10 @@
*/
@Deprecated
public BitmapDrawable(java.io.InputStream is) {
- this(null, is);
+ this(new BitmapState(BitmapFactory.decodeStream(is)), null);
+ if (mBitmapState.mBitmap == null) {
+ android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
+ }
}
/**
@@ -195,21 +190,10 @@
*/
@SuppressWarnings("unused")
public BitmapDrawable(Resources res, java.io.InputStream is) {
- Bitmap bitmap = null;
- try {
- bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, is),
- (decoder, info, src) -> {
- decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
- });
- } catch (Exception e) {
- /* do nothing. This matches the behavior of BitmapFactory.decodeStream()
- If the exception happened on decode, mBitmapState.mBitmap will be null.
- */
- } finally {
- init(new BitmapState(bitmap), res);
- if (mBitmapState.mBitmap == null) {
- android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
- }
+ this(new BitmapState(BitmapFactory.decodeStream(is)), null);
+ mBitmapState.mTargetDensity = mTargetDensity;
+ if (mBitmapState.mBitmap == null) {
+ android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
}
}
@@ -828,19 +812,9 @@
}
}
- int density = Bitmap.DENSITY_NONE;
- if (value.density == TypedValue.DENSITY_DEFAULT) {
- density = DisplayMetrics.DENSITY_DEFAULT;
- } else if (value.density != TypedValue.DENSITY_NONE) {
- density = value.density;
- }
-
Bitmap bitmap = null;
try (InputStream is = r.openRawResource(srcResId, value)) {
- ImageDecoder.Source source = ImageDecoder.createSource(r, is, density);
- bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, src) -> {
- decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
- });
+ bitmap = BitmapFactory.decodeResourceStream(r, value, is, null, null);
} catch (Exception e) {
// Do nothing and pick up the error below.
}
@@ -1039,21 +1013,14 @@
}
}
- private BitmapDrawable(BitmapState state, Resources res) {
- init(state, res);
- }
-
/**
- * The one helper to rule them all. This is called by all public & private
+ * The one constructor to rule them all. This is called by all public
* constructors to set the state and initialize local properties.
*/
- private void init(BitmapState state, Resources res) {
+ private BitmapDrawable(BitmapState state, Resources res) {
mBitmapState = state;
- updateLocalState(res);
- if (mBitmapState != null && res != null) {
- mBitmapState.mTargetDensity = mTargetDensity;
- }
+ updateLocalState(res);
}
/**
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 36a4d26..f17cd76 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -37,7 +37,6 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
-import android.graphics.ImageDecoder;
import android.graphics.Insets;
import android.graphics.NinePatch;
import android.graphics.Outline;
@@ -51,13 +50,11 @@
import android.os.Trace;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.util.StateSet;
import android.util.TypedValue;
import android.util.Xml;
import android.view.View;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
@@ -1178,10 +1175,6 @@
return null;
}
- if (opts == null) {
- return getBitmapDrawable(res, value, is);
- }
-
/* ugh. The decodeStream contract is that we have already allocated
the pad rect, but if the bitmap does not had a ninepatch chunk,
then the pad will be ignored. If we could change this to lazily
@@ -1214,33 +1207,6 @@
return null;
}
- private static Drawable getBitmapDrawable(Resources res, TypedValue value, InputStream is) {
- try {
- ImageDecoder.Source source = null;
- if (value != null) {
- int density = Bitmap.DENSITY_NONE;
- if (value.density == TypedValue.DENSITY_DEFAULT) {
- density = DisplayMetrics.DENSITY_DEFAULT;
- } else if (value.density != TypedValue.DENSITY_NONE) {
- density = value.density;
- }
- source = ImageDecoder.createSource(res, is, density);
- } else {
- source = ImageDecoder.createSource(res, is);
- }
-
- return ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
- decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
- });
- } catch (IOException e) {
- /* do nothing.
- If the exception happened on decode, the drawable will be null.
- */
- Log.e("Drawable", "Unable to decode stream: " + e);
- }
- return null;
- }
-
/**
* Create a drawable from an XML document. For more information on how to
* create resources in XML, see
@@ -1340,10 +1306,11 @@
}
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, pathName);
- try (FileInputStream stream = new FileInputStream(pathName)) {
- return getBitmapDrawable(null, null, stream);
- } catch(IOException e) {
- // Do nothing; we will just return null if the FileInputStream had an error
+ try {
+ Bitmap bm = BitmapFactory.decodeFile(pathName);
+ if (bm != null) {
+ return drawableFromBitmap(null, bm, null, null, null, pathName);
+ }
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 7cacaf6..17f9b7c 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -137,6 +137,7 @@
whole_static_libs: ["libskia"],
srcs: [
+ "hwui/AnimatedImageDrawable.cpp",
"hwui/Bitmap.cpp",
"font/CacheTexture.cpp",
"font/Font.cpp",
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index fb7b246..e1df1e7 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -495,9 +495,9 @@
refPaint(paint), refBitmap(bitmap), refPatch(&patch)));
}
-void RecordingCanvas::drawAnimatedImage(SkAnimatedImage*, float left, float top,
- const SkPaint*) {
+double RecordingCanvas::drawAnimatedImage(AnimatedImageDrawable*) {
// Unimplemented
+ return 0;
}
// Text
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index dd06ada..e663402 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -183,8 +183,7 @@
virtual void drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& chunk, float dstLeft,
float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) override;
- virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
- const SkPaint* paint) override;
+ virtual double drawAnimatedImage(AnimatedImageDrawable*) override;
// Text
virtual bool drawTextAbsolutePos() const override { return false; }
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index dc274cf..b2edd33 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -725,18 +725,8 @@
mCanvas->drawImageLattice(image.get(), lattice, dst, addFilter(paint, &tmpPaint, colorFilter));
}
-void SkiaCanvas::drawAnimatedImage(SkAnimatedImage* image, float left, float top,
- const SkPaint* paint) {
- sk_sp<SkPicture> pic(image->newPictureSnapshot());
- SkMatrix matrixStorage;
- SkMatrix* matrix;
- if (left == 0.0f && top == 0.0f) {
- matrix = nullptr;
- } else {
- matrixStorage = SkMatrix::MakeTrans(left, top);
- matrix = &matrixStorage;
- }
- mCanvas->drawPicture(pic.get(), matrix, paint);
+double SkiaCanvas::drawAnimatedImage(AnimatedImageDrawable* imgDrawable) {
+ return imgDrawable->drawStaging(mCanvas);
}
void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) {
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 7137210..3efc22a 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -124,8 +124,7 @@
virtual void drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& chunk, float dstLeft,
float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) override;
- virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
- const SkPaint* paint) override;
+ virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) override;
virtual bool drawTextAbsolutePos() const override { return true; }
virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
new file mode 100644
index 0000000..36dd06f
--- /dev/null
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AnimatedImageDrawable.h"
+
+#include "thread/Task.h"
+#include "thread/TaskManager.h"
+#include "thread/TaskProcessor.h"
+#include "utils/TraceUtils.h"
+
+#include <SkPicture.h>
+#include <SkRefCnt.h>
+#include <SkTime.h>
+#include <SkTLazy.h>
+
+namespace android {
+
+AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage)
+ : mSkAnimatedImage(std::move(animatedImage)) { }
+
+void AnimatedImageDrawable::syncProperties() {
+ mAlpha = mStagingAlpha;
+ mColorFilter = mStagingColorFilter;
+}
+
+void AnimatedImageDrawable::start() {
+ SkAutoExclusive lock(mLock);
+
+ mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot());
+
+ mSkAnimatedImage->start();
+}
+
+void AnimatedImageDrawable::stop() {
+ SkAutoExclusive lock(mLock);
+ mSkAnimatedImage->stop();
+ mSnapshot.reset(nullptr);
+}
+
+bool AnimatedImageDrawable::isRunning() {
+ return mSkAnimatedImage->isRunning();
+}
+
+// This is really a Task<void> but that doesn't really work when Future<>
+// expects to be able to get/set a value
+class AnimatedImageDrawable::AnimatedImageTask : public uirenderer::Task<bool> {
+public:
+ AnimatedImageTask(AnimatedImageDrawable* animatedImageDrawable)
+ : mAnimatedImageDrawable(sk_ref_sp(animatedImageDrawable)) {}
+
+ sk_sp<AnimatedImageDrawable> mAnimatedImageDrawable;
+ bool mIsCompleted = false;
+};
+
+class AnimatedImageDrawable::AnimatedImageTaskProcessor : public uirenderer::TaskProcessor<bool> {
+public:
+ explicit AnimatedImageTaskProcessor(uirenderer::TaskManager* taskManager)
+ : uirenderer::TaskProcessor<bool>(taskManager) {}
+ ~AnimatedImageTaskProcessor() {}
+
+ virtual void onProcess(const sp<uirenderer::Task<bool>>& task) override {
+ ATRACE_NAME("Updating AnimatedImageDrawables");
+ AnimatedImageTask* t = static_cast<AnimatedImageTask*>(task.get());
+ t->mAnimatedImageDrawable->update();
+ t->mIsCompleted = true;
+ task->setResult(true);
+ };
+};
+
+void AnimatedImageDrawable::scheduleUpdate(uirenderer::TaskManager* taskManager) {
+ if (!mSkAnimatedImage->isRunning()
+ || (mDecodeTask.get() != nullptr && !mDecodeTask->mIsCompleted)) {
+ return;
+ }
+
+ if (!mDecodeTaskProcessor.get()) {
+ mDecodeTaskProcessor = new AnimatedImageTaskProcessor(taskManager);
+ }
+
+ // TODO get one frame ahead and only schedule updates when you need to replenish
+ mDecodeTask = new AnimatedImageTask(this);
+ mDecodeTaskProcessor->add(mDecodeTask);
+}
+
+void AnimatedImageDrawable::update() {
+ SkAutoExclusive lock(mLock);
+
+ if (!mSkAnimatedImage->isRunning()) {
+ return;
+ }
+
+ const double currentTime = SkTime::GetMSecs();
+ if (currentTime >= mNextFrameTime) {
+ mNextFrameTime = mSkAnimatedImage->update(currentTime);
+ mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot());
+ mIsDirty = true;
+ }
+}
+
+void AnimatedImageDrawable::onDraw(SkCanvas* canvas) {
+ SkTLazy<SkPaint> lazyPaint;
+ if (mAlpha != SK_AlphaOPAQUE || mColorFilter.get()) {
+ lazyPaint.init();
+ lazyPaint.get()->setAlpha(mAlpha);
+ lazyPaint.get()->setColorFilter(mColorFilter);
+ lazyPaint.get()->setFilterQuality(kLow_SkFilterQuality);
+ }
+
+ SkAutoExclusive lock(mLock);
+ if (mSkAnimatedImage->isRunning()) {
+ canvas->drawPicture(mSnapshot, nullptr, lazyPaint.getMaybeNull());
+ } else {
+ // TODO: we could potentially keep the cached surface around if there is a paint and we know
+ // the drawable is attached to the view system
+ SkAutoCanvasRestore acr(canvas, false);
+ if (lazyPaint.isValid()) {
+ canvas->saveLayer(mSkAnimatedImage->getBounds(), lazyPaint.get());
+ }
+ mSkAnimatedImage->draw(canvas);
+ }
+
+ mIsDirty = false;
+}
+
+double AnimatedImageDrawable::drawStaging(SkCanvas* canvas) {
+ // update the drawable with the current time
+ double nextUpdate = mSkAnimatedImage->update(SkTime::GetMSecs());
+ SkAutoCanvasRestore acr(canvas, false);
+ if (mStagingAlpha != SK_AlphaOPAQUE || mStagingColorFilter.get()) {
+ SkPaint paint;
+ paint.setAlpha(mStagingAlpha);
+ paint.setColorFilter(mStagingColorFilter);
+ canvas->saveLayer(mSkAnimatedImage->getBounds(), &paint);
+ }
+ canvas->drawDrawable(mSkAnimatedImage.get());
+ return nextUpdate;
+}
+
+}; // namespace android
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h
new file mode 100644
index 0000000..18764af
--- /dev/null
+++ b/libs/hwui/hwui/AnimatedImageDrawable.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cutils/compiler.h>
+#include <utils/RefBase.h>
+
+#include <SkAnimatedImage.h>
+#include <SkCanvas.h>
+#include <SkColorFilter.h>
+#include <SkDrawable.h>
+#include <SkMutex.h>
+
+class SkPicture;
+
+namespace android {
+
+namespace uirenderer {
+class TaskManager;
+}
+
+/**
+ * Native component of android.graphics.drawable.AnimatedImageDrawables.java. This class can be
+ * drawn into Canvas.h and maintains the state needed to drive the animation from the RenderThread.
+ */
+class ANDROID_API AnimatedImageDrawable : public SkDrawable {
+public:
+ AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage);
+
+ /**
+ * This returns true if the animation has updated and signals that the next draw will contain
+ * new content.
+ */
+ bool isDirty() const { return mIsDirty; }
+
+ int getStagingAlpha() const { return mStagingAlpha; }
+ void setStagingAlpha(int alpha) { mStagingAlpha = alpha; }
+ void setStagingColorFilter(sk_sp<SkColorFilter> filter) { mStagingColorFilter = filter; }
+ void syncProperties();
+
+ virtual SkRect onGetBounds() override {
+ return mSkAnimatedImage->getBounds();
+ }
+
+ double drawStaging(SkCanvas* canvas);
+
+ void start();
+ void stop();
+ bool isRunning();
+
+ void scheduleUpdate(uirenderer::TaskManager* taskManager);
+
+protected:
+ virtual void onDraw(SkCanvas* canvas) override;
+
+private:
+ void update();
+
+ sk_sp<SkAnimatedImage> mSkAnimatedImage;
+ sk_sp<SkPicture> mSnapshot;
+ SkMutex mLock;
+
+ int mStagingAlpha = SK_AlphaOPAQUE;
+ sk_sp<SkColorFilter> mStagingColorFilter;
+
+ int mAlpha = SK_AlphaOPAQUE;
+ sk_sp<SkColorFilter> mColorFilter;
+ double mNextFrameTime = 0.0;
+ bool mIsDirty = false;
+
+ class AnimatedImageTask;
+ class AnimatedImageTaskProcessor;
+ sp<AnimatedImageTask> mDecodeTask;
+ sp<AnimatedImageTaskProcessor> mDecodeTaskProcessor;
+};
+
+}; // namespace android
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 5efd357..cae4542 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -73,6 +73,7 @@
typedef std::function<void(uint16_t* text, float* positions)> ReadGlyphFunc;
+class AnimatedImageDrawable;
class Bitmap;
class Paint;
struct Typeface;
@@ -238,8 +239,7 @@
float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) = 0;
- virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
- const SkPaint* paint) = 0;
+ virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) = 0;
/**
* Specifies if the positions passed to ::drawText are absolute or relative
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index bad766c..b31302b 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -64,7 +64,7 @@
const Typeface* resolvedTypeface = Typeface::resolveDefault(typeface);
return minikin::Layout::measureText(buf, start, count, bufSize, bidiFlags, minikinPaint,
resolvedTypeface->fFontCollection, advances,
- nullptr /* extent */, nullptr /* overhangs */);
+ nullptr /* extent */);
}
bool MinikinUtils::hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs) {
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index cb10901..cf0b6a4 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -31,6 +31,9 @@
for (auto& functor : mChildFunctors) {
functor.syncFunctor();
}
+ for (auto& animatedImage : mAnimatedImages) {
+ animatedImage->syncProperties();
+ }
for (auto& vectorDrawable : mVectorDrawables) {
vectorDrawable->syncProperties();
}
@@ -89,6 +92,18 @@
}
bool isDirty = false;
+ for (auto& animatedImage : mAnimatedImages) {
+ // If any animated image in the display list needs updated, then damage the node.
+ if (animatedImage->isDirty()) {
+ isDirty = true;
+ }
+ if (animatedImage->isRunning()) {
+ static_cast<SkiaPipeline*>(info.canvasContext.getRenderPipeline())
+ ->scheduleDeferredUpdate(animatedImage);
+ info.out.hasAnimations = true;
+ }
+ }
+
for (auto& vectorDrawable : mVectorDrawables) {
// If any vector drawable in the display list needs update, damage the node.
if (vectorDrawable->isDirty()) {
@@ -109,6 +124,7 @@
mMutableImages.clear();
mVectorDrawables.clear();
+ mAnimatedImages.clear();
mChildFunctors.clear();
mChildNodes.clear();
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 6883d33..818ec11 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -17,6 +17,7 @@
#pragma once
#include "DisplayList.h"
+#include "hwui/AnimatedImageDrawable.h"
#include "GLFunctorDrawable.h"
#include "RenderNodeDrawable.h"
@@ -144,6 +145,7 @@
std::deque<GLFunctorDrawable> mChildFunctors;
std::vector<SkImage*> mMutableImages;
std::vector<VectorDrawableRoot*> mVectorDrawables;
+ std::vector<AnimatedImageDrawable*> mAnimatedImages;
SkLiteDL mDisplayList;
// mProjectionReceiver points to a child node (stored in mChildNodes) that is as a projection
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 9db39d9..534782a 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -40,6 +40,7 @@
Vector3 SkiaPipeline::mLightCenter = {FLT_MIN, FLT_MIN, FLT_MIN};
SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) {
+ mAnimatedImageDrawables.reserve(30);
mVectorDrawables.reserve(30);
}
@@ -326,6 +327,15 @@
ATRACE_NAME("flush commands");
surface->getCanvas()->flush();
+
+ // TODO move to another method
+ if (!mAnimatedImageDrawables.empty()) {
+ ATRACE_NAME("Update AnimatedImageDrawables");
+ for (auto animatedImage : mAnimatedImageDrawables) {
+ animatedImage->scheduleUpdate(getTaskManager());
+ }
+ mAnimatedImageDrawables.clear();
+ }
}
namespace {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 2709227..cc75e9c 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -18,6 +18,7 @@
#include <SkSurface.h>
#include "FrameBuilder.h"
+#include "hwui/AnimatedImageDrawable.h"
#include "renderthread/CanvasContext.h"
#include "renderthread/IRenderPipeline.h"
@@ -54,6 +55,12 @@
std::vector<VectorDrawableRoot*>* getVectorDrawables() { return &mVectorDrawables; }
+ void scheduleDeferredUpdate(AnimatedImageDrawable* imageDrawable) {
+ mAnimatedImageDrawables.push_back(imageDrawable);
+ }
+
+ std::vector<AnimatedImageDrawable*>* getAnimatingImages() { return &mAnimatedImageDrawables; }
+
static void destroyLayer(RenderNode* node);
static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
@@ -137,6 +144,11 @@
*/
std::vector<VectorDrawableRoot*> mVectorDrawables;
+ /**
+ * populated by prepareTree with images with active animations
+ */
+ std::vector<AnimatedImageDrawable*> mAnimatedImageDrawables;
+
// Block of properties used only for debugging to record a SkPicture and save it in a file.
/**
* mCapturedFile is used to enforce we don't capture more than once for a given name (cause
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 035cea3..eabe2e8 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -246,6 +246,12 @@
}
}
+double SkiaRecordingCanvas::drawAnimatedImage(AnimatedImageDrawable* animatedImage) {
+ drawDrawable(animatedImage);
+ mDisplayList->mAnimatedImages.push_back(animatedImage);
+ return 0;
+}
+
}; // namespace skiapipeline
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index d35bbab..0e5dbdb 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -53,6 +53,7 @@
virtual void drawNinePatch(Bitmap& hwuiBitmap, const android::Res_png_9patch& chunk,
float dstLeft, float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) override;
+ virtual double drawAnimatedImage(AnimatedImageDrawable* animatedImage) override;
virtual void drawRoundRect(uirenderer::CanvasPropertyPrimitive* left,
uirenderer::CanvasPropertyPrimitive* top,
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 0990dcc..018db9a 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -89,6 +89,10 @@
ProviderProperties getProviderProperties(String provider);
String getNetworkProviderPackage();
boolean isProviderEnabled(String provider);
+ boolean isProviderEnabledForUser(String provider, int userId);
+ boolean setProviderEnabledForUser(String provider, boolean enabled, int userId);
+ boolean isLocationEnabledForUser(int userId);
+ void setLocationEnabledForUser(boolean enabled, int userId);
void addTestProvider(String name, in ProviderProperties properties, String opPackageName);
void removeTestProvider(String provider, String opPackageName);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index f0b2774..9db9d33 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -16,7 +16,10 @@
package android.location;
-import com.android.internal.location.ProviderProperties;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.LOCATION_HARDWARE;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import android.Manifest;
import android.annotation.NonNull;
@@ -24,7 +27,6 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.TestApi;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -33,17 +35,15 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Log;
-
+import com.android.internal.location.ProviderProperties;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
-import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
-import static android.Manifest.permission.ACCESS_FINE_LOCATION;
-import static android.Manifest.permission.LOCATION_HARDWARE;
-
/**
* This class provides access to the system location services. These
* services allow applications to obtain periodic updates of the
@@ -1171,13 +1171,57 @@
}
/**
+ * Returns the current enabled/disabled status of location
+ *
+ * @return true if location is enabled. false if location is disabled.
+ */
+ public boolean isLocationEnabled() {
+ return isLocationEnabledForUser(Process.myUserHandle());
+ }
+
+ /**
+ * Method for enabling or disabling location.
+ *
+ * @param enabled true to enable location. false to disable location
+ * @param userHandle the user to set
+ * @return true if the value was set, false on database errors
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(WRITE_SECURE_SETTINGS)
+ public void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) {
+ try {
+ mService.setLocationEnabledForUser(enabled, userHandle.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the current enabled/disabled status of location
+ *
+ * @param userHandle the user to query
+ * @return true location is enabled. false if location is disabled.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isLocationEnabledForUser(UserHandle userHandle) {
+ try {
+ return mService.isLocationEnabledForUser(userHandle.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the current enabled/disabled status of the given provider.
*
* <p>If the user has enabled this provider in the Settings menu, true
* is returned otherwise false is returned
*
- * <p>Callers should instead use
- * {@link android.provider.Settings.Secure#LOCATION_MODE}
+ * <p>Callers should instead use {@link #isLocationEnabled()}
* unless they depend on provider-specific APIs such as
* {@link #requestLocationUpdates(String, long, float, LocationListener)}.
*
@@ -1202,6 +1246,64 @@
}
/**
+ * Returns the current enabled/disabled status of the given provider and user.
+ *
+ * <p>If the user has enabled this provider in the Settings menu, true
+ * is returned otherwise false is returned
+ *
+ * <p>Callers should instead use {@link #isLocationEnabled()}
+ * unless they depend on provider-specific APIs such as
+ * {@link #requestLocationUpdates(String, long, float, LocationListener)}.
+ *
+ * <p>
+ * Before API version {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this
+ * method would throw {@link SecurityException} if the location permissions
+ * were not sufficient to use the specified provider.
+ *
+ * @param provider the name of the provider
+ * @param userHandle the user to query
+ * @return true if the provider exists and is enabled
+ *
+ * @throws IllegalArgumentException if provider is null
+ * @hide
+ */
+ @SystemApi
+ public boolean isProviderEnabledForUser(String provider, UserHandle userHandle) {
+ checkProvider(provider);
+
+ try {
+ return mService.isProviderEnabledForUser(provider, userHandle.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Method for enabling or disabling a single location provider.
+ *
+ * @param provider the name of the provider
+ * @param enabled true to enable the provider. false to disable the provider
+ * @param userHandle the user to set
+ * @return true if the value was set, false on database errors
+ *
+ * @throws IllegalArgumentException if provider is null
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(WRITE_SECURE_SETTINGS)
+ public boolean setProviderEnabledForUser(
+ String provider, boolean enabled, UserHandle userHandle) {
+ checkProvider(provider);
+
+ try {
+ return mService.setProviderEnabledForUser(
+ provider, enabled, userHandle.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Get the last known location.
*
* <p>This location could be very old so use
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index d7861e3..44a2ff9 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -880,7 +880,9 @@
}
/** @hide */
- public void toProto(ProtoOutputStream proto) {
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
proto.write(AudioAttributesProto.USAGE, mUsage);
proto.write(AudioAttributesProto.CONTENT_TYPE, mContentType);
proto.write(AudioAttributesProto.FLAGS, mFlags);
@@ -892,6 +894,8 @@
}
}
// TODO: is the data in mBundle useful for debugging?
+
+ proto.end(token);
}
/** @hide */
diff --git a/media/java/android/media/AudioPort.java b/media/java/android/media/AudioPort.java
index 19bf51d..047db19 100644
--- a/media/java/android/media/AudioPort.java
+++ b/media/java/android/media/AudioPort.java
@@ -20,7 +20,7 @@
* An audio port is a node of the audio framework or hardware that can be connected to or
* disconnect from another audio node to create a specific audio routing configuration.
* Examples of audio ports are an output device (speaker) or an output mix (see AudioMixPort).
- * All attributes that are relevant for applications to make routing selection are decribed
+ * All attributes that are relevant for applications to make routing selection are described
* in an AudioPort, in particular:
* - possible channel mask configurations.
* - audio format (PCM 16bit, PCM 24bit...)
@@ -173,6 +173,7 @@
/**
* Build a specific configuration of this audio port for use by methods
* like AudioManager.connectAudioPatch().
+ * @param samplingRate
* @param channelMask The desired channel mask. AudioFormat.CHANNEL_OUT_DEFAULT if no change
* from active configuration requested.
* @param format The desired audio format. AudioFormat.ENCODING_DEFAULT if no change
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index b4316ba..dcd37da 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -793,7 +793,7 @@
public static native int getPrimaryOutputFrameCount();
public static native int getOutputLatency(int stream);
- public static native int setLowRamDevice(boolean isLowRamDevice);
+ public static native int setLowRamDevice(boolean isLowRamDevice, long totalMemory);
public static native int checkAudioFlinger();
public static native int listAudioPorts(ArrayList<AudioPort> ports, int[] generation);
diff --git a/media/java/android/media/IMediaSession2.aidl b/media/java/android/media/IMediaSession2.aidl
index bc8121e..078b611 100644
--- a/media/java/android/media/IMediaSession2.aidl
+++ b/media/java/android/media/IMediaSession2.aidl
@@ -47,6 +47,11 @@
PlaybackState getPlaybackState();
//////////////////////////////////////////////////////////////////////////////////////////////
+ // Get library service specific
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ oneway void getBrowserRoot(IMediaSession2Callback callback, in Bundle rootHints);
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
// Callbacks -- remove them
//////////////////////////////////////////////////////////////////////////////////////////////
/**
diff --git a/media/java/android/media/IMediaSession2Callback.aidl b/media/java/android/media/IMediaSession2Callback.aidl
index 0058e79..1664e01 100644
--- a/media/java/android/media/IMediaSession2Callback.aidl
+++ b/media/java/android/media/IMediaSession2Callback.aidl
@@ -44,4 +44,11 @@
// Follow-up TODO: Add similar functions to the session.
// TODO(jaewan): Is term 'accepted/rejected' correct? For permission, 'grant' is used.
void onConnectionChanged(IMediaSession2 sessionBinder, in Bundle commandGroup);
+
+ void onCustomLayoutChanged(in List<Bundle> commandButtonlist);
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ // Browser sepcific
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ void onGetRootResult(in Bundle rootHints, String rootMediaId, in Bundle rootExtra);
}
diff --git a/media/java/android/media/MediaBrowser2.java b/media/java/android/media/MediaBrowser2.java
new file mode 100644
index 0000000..33377bc
--- /dev/null
+++ b/media/java/android/media/MediaBrowser2.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.update.ApiLoader;
+import android.media.update.MediaBrowser2Provider;
+import android.os.Bundle;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Browses media content offered by a {@link MediaLibraryService2}.
+ * @hide
+ */
+public class MediaBrowser2 extends MediaController2 {
+ // Equals to the ((MediaBrowser2Provider) getProvider())
+ private final MediaBrowser2Provider mProvider;
+
+ /**
+ * Callback to listen events from {@link MediaLibraryService2}.
+ */
+ public static class BrowserCallback extends MediaController2.ControllerCallback {
+ /**
+ * Called with the result of {@link #getBrowserRoot(Bundle)}.
+ * <p>
+ * {@code rootMediaId} and {@code rootExtra} can be {@code null} if the browser root isn't
+ * available.
+ *
+ * @param rootHints rootHints that you previously requested.
+ * @param rootMediaId media id of the browser root. Can be {@code null}
+ * @param rootExtra extra of the browser root. Can be {@code null}
+ */
+ public void onGetRootResult(Bundle rootHints, @Nullable String rootMediaId,
+ @Nullable Bundle rootExtra) { }
+
+ /**
+ * Called when the item has been returned by the library service for the previous
+ * {@link MediaBrowser2#getItem} call.
+ * <p>
+ * Result can be null if there had been error.
+ *
+ * @param mediaId media id
+ * @param result result. Can be {@code null}
+ */
+ public void onItemLoaded(@NonNull String mediaId, @Nullable MediaItem2 result) { }
+
+ /**
+ * Called when the list of items has been returned by the library service for the previous
+ * {@link MediaBrowser2#getChildren(String, int, int, Bundle)}.
+ *
+ * @param parentId parent id
+ * @param page page number that you've specified
+ * @param pageSize page size that you've specified
+ * @param options optional bundle that you've specified
+ * @param result result. Can be {@code null}
+ */
+ public void onChildrenLoaded(@NonNull String parentId, int page, int pageSize,
+ @Nullable Bundle options, @Nullable List<MediaItem2> result) { }
+
+ /**
+ * Called when there's change in the parent's children.
+ *
+ * @param parentId parent id that you've specified with subscribe
+ * @param options optional bundle that you've specified with subscribe
+ */
+ public void onChildrenChanged(@NonNull String parentId, @Nullable Bundle options) { }
+
+ /**
+ * Called when the search result has been returned by the library service for the previous
+ * {@link MediaBrowser2#search(String, int, int, Bundle)}.
+ * <p>
+ * Result can be null if there had been error.
+ *
+ * @param query query string that you've specified
+ * @param page page number that you've specified
+ * @param pageSize page size that you've specified
+ * @param options optional bundle that you've specified
+ * @param result result. Can be {@code null}
+ */
+ public void onSearchResult(@NonNull String query, int page, int pageSize,
+ @Nullable Bundle options, @Nullable List<MediaItem2> result) { }
+ }
+
+ public MediaBrowser2(Context context, SessionToken token, BrowserCallback callback,
+ Executor executor) {
+ super(context, token, callback, executor);
+ mProvider = (MediaBrowser2Provider) getProvider();
+ }
+
+ @Override
+ MediaBrowser2Provider createProvider(Context context, SessionToken token,
+ ControllerCallback callback, Executor executor) {
+ return ApiLoader.getProvider(context)
+ .createMediaBrowser2(this, context, token, (BrowserCallback) callback, executor);
+ }
+
+ public void getBrowserRoot(Bundle rootHints) {
+ mProvider.getBrowserRoot_impl(rootHints);
+ }
+
+ /**
+ * Subscribe to a parent id for the change in its children. When there's a change,
+ * {@link BrowserCallback#onChildrenChanged(String, Bundle)} will be called with the bundle
+ * that you've specified. You should call {@link #getChildren(String, int, int, Bundle)} to get
+ * the actual contents for the parent.
+ *
+ * @param parentId parent id
+ * @param options optional bundle
+ */
+ public void subscribe(String parentId, @Nullable Bundle options) {
+ mProvider.subscribe_impl(parentId, options);
+ }
+
+ /**
+ * Unsubscribe for changes to the children of the parent, which was previously subscribed with
+ * {@link #subscribe(String, Bundle)}.
+ *
+ * @param parentId parent id
+ * @param options optional bundle
+ */
+ public void unsubscribe(String parentId, @Nullable Bundle options) {
+ mProvider.unsubscribe_impl(parentId, options);
+ }
+
+ /**
+ * Get the media item with the given media id. Result would be sent back asynchronously with the
+ * {@link BrowserCallback#onItemLoaded(String, MediaItem2)}.
+ *
+ * @param mediaId media id
+ */
+ public void getItem(String mediaId) {
+ mProvider.getItem_impl(mediaId);
+ }
+
+ /**
+ * Get list of children under the parent. Result would be sent back asynchronously with the
+ * {@link BrowserCallback#onChildrenLoaded(String, int, int, Bundle, List)}.
+ *
+ * @param parentId
+ * @param page
+ * @param pageSize
+ * @param options
+ */
+ public void getChildren(String parentId, int page, int pageSize, @Nullable Bundle options) {
+ mProvider.getChildren_impl(parentId, page, pageSize, options);
+ }
+
+ /**
+ *
+ * @param query search query deliminated by string
+ * @param page page number to get search result. Starts from {@code 1}
+ * @param pageSize page size. Should be greater or equal to {@code 1}
+ * @param extras extra bundle
+ */
+ public void search(String query, int page, int pageSize, Bundle extras) {
+ mProvider.search_impl(query, page, pageSize, extras);
+ }
+}
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
index 550eee2..dca1027 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -18,14 +18,21 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.Context;
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.PlaylistParam;
import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
import android.media.update.ApiLoader;
import android.media.update.MediaController2Provider;
-import android.os.Handler;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -33,7 +40,7 @@
* {@link MediaSessionService2} in any status. Media buttons and other commands can be sent to
* the session.
* <p>
- * When you're done, use {@link #release()} to clean up resources. This also helps session service
+ * When you're done, use {@link #close()} to clean up resources. This also helps session service
* to be destroyed when there's no controller associated with it.
* <p>
* When controlling {@link MediaSession2}, the controller will be available immediately after
@@ -58,7 +65,7 @@
*/
// TODO(jaewan): Unhide
// TODO(jaewan): Revisit comments. Currently MediaBrowser case is missing.
-public class MediaController2 extends MediaPlayerBase {
+public class MediaController2 implements AutoCloseable {
/**
* Interface for listening to change in activeness of the {@link MediaSession2}. It's
* active if and only if it has set a player.
@@ -77,10 +84,151 @@
* the session. The controller becomes unavailable afterwards and the callback wouldn't
* be called.
* <p>
- * It will be also called after the {@link #release()}, so you can put clean up code here.
- * You don't need to call {@link #release()} after this.
+ * It will be also called after the {@link #close()}, so you can put clean up code here.
+ * You don't need to call {@link #close()} after this.
*/
public void onDisconnected() { }
+
+ /**
+ * Called when the session set the custom layout through the
+ * {@link MediaSession2#setCustomLayout(ControllerInfo, List)}.
+ * <p>
+ * Can be called before {@link #onConnected(CommandGroup)} is called.
+ *
+ * @param layout
+ */
+ public void onCustomLayoutChanged(List<CommandButton> layout) { }
+
+ /**
+ * Called when the session has changed anything related with the {@link PlaybackInfo}.
+ *
+ * @param info new playback info
+ */
+ public void onAudioInfoChanged(PlaybackInfo info) { }
+
+ /**
+ * Called when the allowed commands are changed by session.
+ *
+ * @param commands newly allowed commands
+ */
+ public void onAllowedCommandsChanged(CommandGroup commands) { }
+
+ /**
+ * Called when the session sent a custom command.
+ *
+ * @param command
+ * @param args
+ * @param receiver
+ */
+ public void onCustomCommand(Command command, @Nullable Bundle args,
+ @Nullable ResultReceiver receiver) { }
+
+ /**
+ * Called when the playlist is changed.
+ *
+ * @param list
+ * @param param
+ */
+ public void onPlaylistChanged(
+ @NonNull List<MediaItem2> list, @NonNull PlaylistParam param) { }
+
+ /**
+ * Called when the playback state is changed.
+ *
+ * @param state
+ */
+ public void onPlaybackStateChanged(@NonNull PlaybackState2 state) { }
+ }
+
+ /**
+ * Holds information about the current playback and how audio is handled for
+ * this session.
+ */
+ // The same as MediaController.PlaybackInfo
+ public static final class PlaybackInfo {
+ /**
+ * The session uses remote playback.
+ */
+ public static final int PLAYBACK_TYPE_REMOTE = 2;
+ /**
+ * The session uses local playback.
+ */
+ public static final int PLAYBACK_TYPE_LOCAL = 1;
+
+ private final int mVolumeType;
+ private final int mVolumeControl;
+ private final int mMaxVolume;
+ private final int mCurrentVolume;
+ private final AudioAttributes mAudioAttrs;
+
+ /**
+ * @hide
+ */
+ public PlaybackInfo(int type, AudioAttributes attrs, int control, int max, int current) {
+ mVolumeType = type;
+ mAudioAttrs = attrs;
+ mVolumeControl = control;
+ mMaxVolume = max;
+ mCurrentVolume = current;
+ }
+
+ /**
+ * Get the type of playback which affects volume handling. One of:
+ * <ul>
+ * <li>{@link #PLAYBACK_TYPE_LOCAL}</li>
+ * <li>{@link #PLAYBACK_TYPE_REMOTE}</li>
+ * </ul>
+ *
+ * @return The type of playback this session is using.
+ */
+ public int getPlaybackType() {
+ return mVolumeType;
+ }
+
+ /**
+ * Get the audio attributes for this session. The attributes will affect
+ * volume handling for the session. When the volume type is
+ * {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} these may be ignored by the
+ * remote volume handler.
+ *
+ * @return The attributes for this session.
+ */
+ public AudioAttributes getAudioAttributes() {
+ return mAudioAttrs;
+ }
+
+ /**
+ * Get the type of volume control that can be used. One of:
+ * <ul>
+ * <li>{@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}</li>
+ * <li>{@link VolumeProvider#VOLUME_CONTROL_RELATIVE}</li>
+ * <li>{@link VolumeProvider#VOLUME_CONTROL_FIXED}</li>
+ * </ul>
+ *
+ * @return The type of volume control that may be used with this
+ * session.
+ */
+ public int getVolumeControl() {
+ return mVolumeControl;
+ }
+
+ /**
+ * Get the maximum volume that may be set for this session.
+ *
+ * @return The maximum allowed volume where this session is playing.
+ */
+ public int getMaxVolume() {
+ return mMaxVolume;
+ }
+
+ /**
+ * Get the current volume for this session.
+ *
+ * @return The current volume where this session is playing.
+ */
+ public int getCurrentVolume() {
+ return mCurrentVolume;
+ }
}
private final MediaController2Provider mProvider;
@@ -104,7 +252,13 @@
// session whose session binder is only valid while it's active.
// prevent a controller from reusable after the
// session is released and recreated.
- mProvider = ApiLoader.getProvider(context)
+ mProvider = createProvider(context, token, callback, executor);
+ }
+
+ MediaController2Provider createProvider(@NonNull Context context,
+ @NonNull SessionToken token, @NonNull ControllerCallback callback,
+ @NonNull Executor executor) {
+ return ApiLoader.getProvider(context)
.createMediaController2(this, context, token, callback, executor);
}
@@ -112,8 +266,9 @@
* Release this object, and disconnect from the session. After this, callbacks wouldn't be
* received.
*/
- public void release() {
- mProvider.release_impl();
+ @Override
+ public void close() {
+ mProvider.close_impl();
}
/**
@@ -126,8 +281,7 @@
/**
* @return token
*/
- public @NonNull
- SessionToken getSessionToken() {
+ public @NonNull SessionToken getSessionToken() {
return mProvider.getSessionToken_impl();
}
@@ -138,60 +292,324 @@
return mProvider.isConnected_impl();
}
- @Override
public void play() {
mProvider.play_impl();
}
- @Override
public void pause() {
mProvider.pause_impl();
}
- @Override
public void stop() {
mProvider.stop_impl();
}
- @Override
public void skipToPrevious() {
mProvider.skipToPrevious_impl();
}
- @Override
public void skipToNext() {
mProvider.skipToNext_impl();
}
- @Override
- public @Nullable PlaybackState getPlaybackState() {
+ /**
+ * Request that the player prepare its playback. In other words, other sessions can continue
+ * to play during the preparation of this session. This method can be used to speed up the
+ * start of the playback. Once the preparation is done, the session will change its playback
+ * state to {@link PlaybackState2#STATE_PAUSED}. Afterwards, {@link #play} can be called to
+ * start playback.
+ */
+ public void prepare() {
+ mProvider.prepare_impl();
+ }
+
+ /**
+ * Start fast forwarding. If playback is already fast forwarding this
+ * may increase the rate.
+ */
+ public void fastForward() {
+ mProvider.fastForward_impl();
+ }
+
+ /**
+ * Start rewinding. If playback is already rewinding this may increase
+ * the rate.
+ */
+ public void rewind() {
+ mProvider.rewind_impl();
+ }
+
+ /**
+ * Move to a new location in the media stream.
+ *
+ * @param pos Position to move to, in milliseconds.
+ */
+ public void seekTo(long pos) {
+ mProvider.seekTo_impl(pos);
+ }
+
+ /**
+ * Sets the index of current DataSourceDesc in the play list to be played.
+ *
+ * @param index the index of DataSourceDesc in the play list you want to play
+ * @throws IllegalArgumentException if the play list is null
+ * @throws NullPointerException if index is outside play list range
+ */
+ public void setCurrentPlaylistItem(int index) {
+ mProvider.setCurrentPlaylistItem_impl(index);
+ }
+
+ /**
+ * @hide
+ */
+ public void skipForward() {
+ // To match with KEYCODE_MEDIA_SKIP_FORWARD
+ }
+
+ /**
+ * @hide
+ */
+ public void skipBackward() {
+ // To match with KEYCODE_MEDIA_SKIP_BACKWARD
+ }
+
+ /**
+ * Request that the player start playback for a specific media id.
+ *
+ * @param mediaId The id of the requested media.
+ * @param extras Optional extras that can include extra information about the media item
+ * to be played.
+ */
+ public void playFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) {
+ mProvider.playFromMediaId_impl(mediaId, extras);
+ }
+
+ /**
+ * Request that the player start playback for a specific search query.
+ * An empty or null query should be treated as a request to play any
+ * music.
+ *
+ * @param query The search query.
+ * @param extras Optional extras that can include extra information
+ * about the query.
+ */
+ public void playFromSearch(@NonNull String query, @Nullable Bundle extras) {
+ mProvider.playFromSearch_impl(query, extras);
+ }
+
+ /**
+ * Request that the player start playback for a specific {@link Uri}.
+ *
+ * @param uri The URI of the requested media.
+ * @param extras Optional extras that can include extra information about the media item
+ * to be played.
+ */
+ public void playFromUri(@NonNull String uri, @Nullable Bundle extras) {
+ mProvider.playFromUri_impl(uri, extras);
+ }
+
+
+ /**
+ * Request that the player prepare playback for a specific media id. In other words, other
+ * sessions can continue to play during the preparation of this session. This method can be
+ * used to speed up the start of the playback. Once the preparation is done, the session
+ * will change its playback state to {@link PlaybackState2#STATE_PAUSED}. Afterwards,
+ * {@link #play} can be called to start playback. If the preparation is not needed,
+ * {@link #playFromMediaId} can be directly called without this method.
+ *
+ * @param mediaId The id of the requested media.
+ * @param extras Optional extras that can include extra information about the media item
+ * to be prepared.
+ */
+ public void prepareFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) {
+ mProvider.prepareMediaId_impl(mediaId, extras);
+ }
+
+ /**
+ * Request that the player prepare playback for a specific search query. An empty or null
+ * query should be treated as a request to prepare any music. In other words, other sessions
+ * can continue to play during the preparation of this session. This method can be used to
+ * speed up the start of the playback. Once the preparation is done, the session will
+ * change its playback state to {@link PlaybackState2#STATE_PAUSED}. Afterwards,
+ * {@link #play} can be called to start playback. If the preparation is not needed,
+ * {@link #playFromSearch} can be directly called without this method.
+ *
+ * @param query The search query.
+ * @param extras Optional extras that can include extra information
+ * about the query.
+ */
+ public void prepareFromSearch(@NonNull String query, @Nullable Bundle extras) {
+ mProvider.prepareFromSearch_impl(query, extras);
+ }
+
+ /**
+ * Request that the player prepare playback for a specific {@link Uri}. In other words,
+ * other sessions can continue to play during the preparation of this session. This method
+ * can be used to speed up the start of the playback. Once the preparation is done, the
+ * session will change its playback state to {@link PlaybackState2#STATE_PAUSED}. Afterwards,
+ * {@link #play} can be called to start playback. If the preparation is not needed,
+ * {@link #playFromUri} can be directly called without this method.
+ *
+ * @param uri The URI of the requested media.
+ * @param extras Optional extras that can include extra information about the media item
+ * to be prepared.
+ */
+ public void prepareFromUri(@NonNull Uri uri, @Nullable Bundle extras) {
+ mProvider.prepareFromUri_impl(uri, extras);
+ }
+
+ /**
+ * Set the volume of the output this session is playing on. The command will be ignored if it
+ * does not support {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}.
+ * <p>
+ * If the session is local playback, this changes the device's volume with the stream that
+ * session's player is using. Flags will be specified for the {@link AudioManager}.
+ * <p>
+ * If the session is remote player (i.e. session has set volume provider), its volume provider
+ * will receive this request instead.
+ *
+ * @see #getPlaybackInfo()
+ * @param value The value to set it to, between 0 and the reported max.
+ * @param flags flags from {@link AudioManager} to include with the volume request for local
+ * playback
+ */
+ public void setVolumeTo(int value, int flags) {
+ mProvider.setVolumeTo_impl(value, flags);
+ }
+
+ /**
+ * Adjust the volume of the output this session is playing on. The direction
+ * must be one of {@link AudioManager#ADJUST_LOWER},
+ * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
+ * The command will be ignored if the session does not support
+ * {@link VolumeProvider#VOLUME_CONTROL_RELATIVE} or
+ * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}.
+ * <p>
+ * If the session is local playback, this changes the device's volume with the stream that
+ * session's player is using. Flags will be specified for the {@link AudioManager}.
+ * <p>
+ * If the session is remote player (i.e. session has set volume provider), its volume provider
+ * will receive this request instead.
+ *
+ * @see #getPlaybackInfo()
+ * @param direction The direction to adjust the volume in.
+ * @param flags flags from {@link AudioManager} to include with the volume request for local
+ * playback
+ */
+ public void adjustVolume(int direction, int flags) {
+ mProvider.adjustVolume_impl(direction, flags);
+ }
+
+ /**
+ * Get the rating type supported by the session. One of:
+ * <ul>
+ * <li>{@link Rating2#RATING_NONE}</li>
+ * <li>{@link Rating2#RATING_HEART}</li>
+ * <li>{@link Rating2#RATING_THUMB_UP_DOWN}</li>
+ * <li>{@link Rating2#RATING_3_STARS}</li>
+ * <li>{@link Rating2#RATING_4_STARS}</li>
+ * <li>{@link Rating2#RATING_5_STARS}</li>
+ * <li>{@link Rating2#RATING_PERCENTAGE}</li>
+ * </ul>
+ *
+ * @return The supported rating type
+ */
+ public int getRatingType() {
+ return mProvider.getRatingType_impl();
+ }
+
+ /**
+ * Get an intent for launching UI associated with this session if one exists.
+ *
+ * @return A {@link PendingIntent} to launch UI or null.
+ */
+ public @Nullable PendingIntent getSessionActivity() {
+ return mProvider.getSessionActivity_impl();
+ }
+
+ /**
+ * Get the latest {@link PlaybackState2} from the session.
+ *
+ * @return a playback state
+ */
+ public PlaybackState2 getPlaybackState() {
return mProvider.getPlaybackState_impl();
}
/**
- * Add a {@link PlaybackListener} to listen changes in the
- * {@link MediaSession2}.
+ * Get the current playback info for this session.
*
- * @param listener the listener that will be run
- * @param handler the Handler that will receive the listener
- * @throws IllegalArgumentException Called when either the listener or handler is {@code null}.
+ * @return The current playback info or null.
*/
- // TODO(jaewan): Match with the addSessionAvailabilityListener() that tells the current state
- // through the listener.
- // TODO(jaewan): Can handler be null? Follow the API guideline after it's finalized.
- @Override
- public void addPlaybackListener(@NonNull PlaybackListener listener, @NonNull Handler handler) {
- mProvider.addPlaybackListener_impl(listener, handler);
+ public @Nullable PlaybackInfo getPlaybackInfo() {
+ return mProvider.getPlaybackInfo_impl();
}
/**
- * Remove previously added {@link PlaybackListener}.
+ * Rate the current content. This will cause the rating to be set for
+ * the current user. The Rating type must match the type returned by
+ * {@link #getRatingType()}.
*
- * @param listener the listener to be removed
- * @throws IllegalArgumentException if the listener is {@code null}.
+ * @param rating The rating to set for the current content
*/
- @Override
- public void removePlaybackListener(@NonNull PlaybackListener listener) {
- mProvider.removePlaybackListener_impl(listener);
+ public void setRating(Rating2 rating) {
+ mProvider.setRating_impl(rating);
+ }
+
+ /**
+ * Send custom command to the session
+ *
+ * @param command custom command
+ * @param args optional argument
+ * @param cb optional result receiver
+ */
+ public void sendCustomCommand(@NonNull Command command, @Nullable Bundle args,
+ @Nullable ResultReceiver cb) {
+ mProvider.sendCustomCommand_impl(command, args, cb);
+ }
+
+ /**
+ * Return playlist from the session.
+ *
+ * @return playlist. Can be {@code null} if the controller doesn't have enough permission.
+ */
+ public @Nullable List<MediaItem2> getPlaylist() {
+ return mProvider.getPlaylist_impl();
+ }
+
+ public @Nullable PlaylistParam getPlaylistParam() {
+ return mProvider.getPlaylistParam_impl();
+ }
+
+ /**
+ * Removes the media item at index in the play list.
+ *<p>
+ * If index is same as the current index of the playlist, current playback
+ * will be stopped and playback moves to next source in the list.
+ *
+ * @return the removed DataSourceDesc at index in the play list
+ * @throws IllegalArgumentException if the play list is null
+ * @throws IndexOutOfBoundsException if index is outside play list range
+ */
+ // TODO(jaewan): Remove with index was previously rejected by council (b/36524925)
+ // TODO(jaewan): Should we also add movePlaylistItem from index to index?
+ public void removePlaylistItem(MediaItem2 item) {
+ mProvider.removePlaylistItem_impl(item);
+ }
+
+ /**
+ * Inserts the media item to the play list at position index.
+ * <p>
+ * This will not change the currently playing media item.
+ * If index is less than or equal to the current index of the play list,
+ * the current index of the play list will be incremented correspondingly.
+ *
+ * @param index the index you want to add dsd to the play list
+ * @param item the media item you want to add to the play list
+ * @throws IndexOutOfBoundsException if index is outside play list range
+ * @throws NullPointerException if dsd is null
+ */
+ public void addPlaylistItem(int index, MediaItem2 item) {
+ mProvider.addPlaylistItem_impl(index, item);
}
}
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 690d740..063186d 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -26,6 +26,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
+import android.os.PersistableBundle;
import android.util.Log;
import dalvik.system.CloseGuard;
import java.lang.annotation.Retention;
@@ -678,12 +679,14 @@
private int mRequestType;
/**
- * Key request type is initial license request
+ * Key request type is initial license request. A license request
+ * is necessary to load keys.
*/
public static final int REQUEST_TYPE_INITIAL = 0;
/**
- * Key request type is license renewal
+ * Key request type is license renewal. A license request is
+ * necessary to prevent the keys from expiring.
*/
public static final int REQUEST_TYPE_RENEWAL = 1;
@@ -692,11 +695,25 @@
*/
public static final int REQUEST_TYPE_RELEASE = 2;
+ /**
+ * Keys are already loaded. No license request is necessary, and no
+ * key request data is returned.
+ */
+ public static final int REQUEST_TYPE_NONE = 3;
+
+ /**
+ * Keys have been loaded but an additional license request is needed
+ * to update their values.
+ */
+ public static final int REQUEST_TYPE_UPDATE = 4;
+
/** @hide */
@IntDef({
REQUEST_TYPE_INITIAL,
REQUEST_TYPE_RENEWAL,
REQUEST_TYPE_RELEASE,
+ REQUEST_TYPE_NONE,
+ REQUEST_TYPE_UPDATE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface RequestType {}
@@ -737,7 +754,8 @@
/**
* Get the type of the request
* @return one of {@link #REQUEST_TYPE_INITIAL},
- * {@link #REQUEST_TYPE_RENEWAL} or {@link #REQUEST_TYPE_RELEASE}
+ * {@link #REQUEST_TYPE_RENEWAL}, {@link #REQUEST_TYPE_RELEASE},
+ * {@link #REQUEST_TYPE_NONE} or {@link #REQUEST_TYPE_UPDATE}
*/
@RequestType
public int getRequestType() { return mRequestType; }
@@ -1201,7 +1219,6 @@
public native void setPropertyByteArray(@NonNull @ArrayProperty
String propertyName, @NonNull byte[] value);
-
private static final native void setCipherAlgorithmNative(
@NonNull MediaDrm drm, @NonNull byte[] sessionId, @NonNull String algorithm);
@@ -1228,6 +1245,25 @@
@NonNull byte[] keyId, @NonNull byte[] message, @NonNull byte[] signature);
/**
+ * Return Metrics data about the current MediaDrm instance.
+ *
+ * @return a {@link PersistableBundle} containing the set of attributes and values
+ * available for this instance of MediaDrm.
+ * The attributes are described in {@link MetricsConstants}.
+ *
+ * Additional vendor-specific fields may also be present in
+ * the return value.
+ *
+ * @hide - not part of the public API at this time
+ */
+ public PersistableBundle getMetrics() {
+ PersistableBundle bundle = getMetricsNative();
+ return bundle;
+ }
+
+ private native PersistableBundle getMetricsNative();
+
+ /**
* In addition to supporting decryption of DASH Common Encrypted Media, the
* MediaDrm APIs provide the ability to securely deliver session keys from
* an operator's session key server to a client device, based on the factory-installed
@@ -1531,4 +1567,31 @@
System.loadLibrary("media_jni");
native_init();
}
+
+ /**
+ * Definitions for the metrics that are reported via the
+ * {@link #getMetrics} call.
+ *
+ * @hide - not part of the public API at this time
+ */
+ public final static class MetricsConstants
+ {
+ private MetricsConstants() {}
+
+ /**
+ * Key to extract the number of successful {@link #openSession} calls
+ * from the {@link PersistableBundle} returned by a
+ * {@link #getMetrics} call.
+ */
+ public static final String OPEN_SESSION_OK_COUNT
+ = "/drm/mediadrm/open_session/ok/count";
+
+ /**
+ * Key to extract the number of failed {@link #openSession} calls
+ * from the {@link PersistableBundle} returned by a
+ * {@link #getMetrics} call.
+ */
+ public static final String OPEN_SESSION_ERROR_COUNT
+ = "/drm/mediadrm/open_session/error/count";
+ }
}
diff --git a/media/java/android/media/MediaItem2.java b/media/java/android/media/MediaItem2.java
new file mode 100644
index 0000000..96a87d5
--- /dev/null
+++ b/media/java/android/media/MediaItem2.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A class with information on a single media item with the metadata information.
+ * Media item are application dependent so we cannot guarantee that they contain the right values.
+ * <p>
+ * When it's sent to a controller or browser, it's anonymized and data descriptor wouldn't be sent.
+ * <p>
+ * This object isn't a thread safe.
+ *
+ * @hide
+ */
+// TODO(jaewan): Unhide and extends from DataSourceDesc.
+// Note) Feels like an anti-pattern. We should anonymize MediaItem2 to remove *all*
+// information in the DataSourceDesc. Why it should extends from this?
+// TODO(jaewan): Move this to updatable
+// Previously MediaBrowser.MediaItem
+public class MediaItem2 {
+ private final int mFlags;
+ private MediaMetadata2 mMetadata;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
+ public @interface Flags { }
+
+ /**
+ * Flag: Indicates that the item has children of its own.
+ */
+ public static final int FLAG_BROWSABLE = 1 << 0;
+
+ /**
+ * Flag: Indicates that the item is playable.
+ * <p>
+ * The id of this item may be passed to
+ * {@link MediaController2#playFromMediaId(String, Bundle)}
+ */
+ public static final int FLAG_PLAYABLE = 1 << 1;
+
+ /**
+ * Create a new media item.
+ *
+ * @param metadata metadata with the media id.
+ * @param flags The flags for this item.
+ */
+ public MediaItem2(@NonNull MediaMetadata2 metadata, @Flags int flags) {
+ mFlags = flags;
+ setMetadata(metadata);
+ }
+
+ /**
+ * Return this object as a bundle to share between processes.
+ *
+ * @return a new bundle instance
+ */
+ public Bundle toBundle() {
+ // TODO(jaewan): Fill here when we rebase.
+ return new Bundle();
+ }
+
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("MediaItem2{");
+ sb.append("mFlags=").append(mFlags);
+ sb.append(", mMetadata=").append(mMetadata);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ /**
+ * Gets the flags of the item.
+ */
+ public @Flags int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Returns whether this item is browsable.
+ * @see #FLAG_BROWSABLE
+ */
+ public boolean isBrowsable() {
+ return (mFlags & FLAG_BROWSABLE) != 0;
+ }
+
+ /**
+ * Returns whether this item is playable.
+ * @see #FLAG_PLAYABLE
+ */
+ public boolean isPlayable() {
+ return (mFlags & FLAG_PLAYABLE) != 0;
+ }
+
+ /**
+ * Set a metadata. Metadata shouldn't be null and should have non-empty media id.
+ *
+ * @param metadata
+ */
+ public void setMetadata(@NonNull MediaMetadata2 metadata) {
+ if (metadata == null) {
+ throw new IllegalArgumentException("metadata cannot be null");
+ }
+ if (TextUtils.isEmpty(metadata.getMediaId())) {
+ throw new IllegalArgumentException("metadata must have a non-empty media id");
+ }
+ mMetadata = metadata;
+ }
+
+ /**
+ * Returns the metadata of the media.
+ */
+ public @NonNull MediaMetadata2 getMetadata() {
+ return mMetadata;
+ }
+
+ /**
+ * Returns the media id in the {@link MediaMetadata2} for this item.
+ * @see MediaMetadata2#METADATA_KEY_MEDIA_ID
+ */
+ public @Nullable String getMediaId() {
+ return mMetadata.getMediaId();
+ }
+}
diff --git a/media/java/android/media/MediaLibraryService2.java b/media/java/android/media/MediaLibraryService2.java
new file mode 100644
index 0000000..b98936e
--- /dev/null
+++ b/media/java/android/media/MediaLibraryService2.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.media.MediaSession2.BuilderBase;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.update.ApiLoader;
+import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
+import android.media.update.MediaSession2Provider;
+import android.media.update.MediaSessionService2Provider;
+import android.os.Bundle;
+import android.service.media.MediaBrowserService.BrowserRoot;
+
+import java.util.List;
+
+/**
+ * Base class for media library services.
+ * <p>
+ * Media library services enable applications to browse media content provided by an application
+ * and ask the application to start playing it. They may also be used to control content that
+ * is already playing by way of a {@link MediaSession2}.
+ * <p>
+ * To extend this class, adding followings directly to your {@code AndroidManifest.xml}.
+ * <pre>
+ * <service android:name="component_name_of_your_implementation" >
+ * <intent-filter>
+ * <action android:name="android.media.MediaLibraryService2" />
+ * </intent-filter>
+ * </service></pre>
+ * <p>
+ * A {@link MediaLibraryService2} is extension of {@link MediaSessionService2}. IDs shouldn't
+ * be shared between the {@link MediaSessionService2} and {@link MediaSession2}. By
+ * default, an empty string will be used for ID of the service. If you want to specify an ID,
+ * declare metadata in the manifest as follows.
+ * @hide
+ */
+// TODO(jaewan): Unhide
+public abstract class MediaLibraryService2 extends MediaSessionService2 {
+ /**
+ * This is the interface name that a service implementing a session service should say that it
+ * support -- that is, this is the action it uses for its intent filter.
+ */
+ public static final String SERVICE_INTERFACE = "android.media.MediaLibraryService2";
+
+ /**
+ * Session for the media library service.
+ */
+ public class MediaLibrarySession extends MediaSession2 {
+ private final MediaLibrarySessionProvider mProvider;
+
+ MediaLibrarySession(Context context, MediaPlayerBase player, String id,
+ SessionCallback callback, VolumeProvider volumeProvider,
+ int ratingType, PendingIntent sessionActivity) {
+ super(context, player, id, callback, volumeProvider, ratingType, sessionActivity);
+ mProvider = (MediaLibrarySessionProvider) getProvider();
+ }
+
+ @Override
+ MediaSession2Provider createProvider(Context context, MediaPlayerBase player, String id,
+ SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+ PendingIntent sessionActivity) {
+ return ApiLoader.getProvider(context)
+ .createMediaLibraryService2MediaLibrarySession(this, context, player, id,
+ (MediaLibrarySessionCallback) callback, volumeProvider, ratingType,
+ sessionActivity);
+ }
+
+ /**
+ * Notify subscribed controller about change in a parent's children.
+ *
+ * @param controller controller to notify
+ * @param parentId
+ * @param options
+ */
+ public void notifyChildrenChanged(@NonNull ControllerInfo controller,
+ @NonNull String parentId, @NonNull Bundle options) {
+ mProvider.notifyChildrenChanged_impl(controller, parentId, options);
+ }
+
+ /**
+ * Notify subscribed controller about change in a parent's children.
+ *
+ * @param parentId parent id
+ * @param options optional bundle
+ */
+ // This is for the backward compatibility.
+ public void notifyChildrenChanged(@NonNull String parentId, @Nullable Bundle options) {
+ mProvider.notifyChildrenChanged_impl(parentId, options);
+ }
+ }
+
+ public static class MediaLibrarySessionCallback extends MediaSession2.SessionCallback {
+ /**
+ * Called to get the root information for browsing by a particular client.
+ * <p>
+ * The implementation should verify that the client package has permission
+ * to access browse media information before returning the root id; it
+ * should return null if the client is not allowed to access this
+ * information.
+ *
+ * @param controllerInfo information of the controller requesting access to browse media.
+ * @param rootHints An optional bundle of service-specific arguments to send
+ * to the media browser service when connecting and retrieving the
+ * root id for browsing, or null if none. The contents of this
+ * bundle may affect the information returned when browsing.
+ * @return The {@link BrowserRoot} for accessing this app's content or null.
+ * @see BrowserRoot#EXTRA_RECENT
+ * @see BrowserRoot#EXTRA_OFFLINE
+ * @see BrowserRoot#EXTRA_SUGGESTED
+ */
+ public @Nullable BrowserRoot onGetRoot(@NonNull ControllerInfo controllerInfo,
+ @Nullable Bundle rootHints) {
+ return null;
+ }
+
+ /**
+ * Called to get the search result. Return search result here for the browser.
+ * <p>
+ * Return an empty list for no search result, and return {@code null} for the error.
+ *
+ * @param query The search query sent from the media browser. It contains keywords separated
+ * by space.
+ * @param extras The bundle of service-specific arguments sent from the media browser.
+ * @return search result. {@code null} for error.
+ */
+ public @Nullable List<MediaItem2> onSearch(@NonNull ControllerInfo controllerInfo,
+ @NonNull String query, @Nullable Bundle extras) {
+ return null;
+ }
+
+ /**
+ * Called to get the search result . Return result here for the browser.
+ * <p>
+ * Return an empty list for no search result, and return {@code null} for the error.
+ *
+ * @param itemId item id to get media item.
+ * @return media item2. {@code null} for error.
+ */
+ public @Nullable MediaItem2 onLoadItem(@NonNull ControllerInfo controllerInfo,
+ @NonNull String itemId) {
+ return null;
+ }
+
+ /**
+ * Called to get the search result. Return search result here for the browser.
+ * <p>
+ * Return an empty list for no search result, and return {@code null} for the error.
+ *
+ * @param parentId parent id to get children
+ * @param page number of page
+ * @param pageSize size of the page
+ * @param options
+ * @return list of children. Can be {@code null}.
+ */
+ public @Nullable List<MediaItem2> onLoadChildren(@NonNull ControllerInfo controller,
+ @NonNull String parentId, int page, int pageSize, @Nullable Bundle options) {
+ return null;
+ }
+
+ /**
+ * Called when a controller subscribes to the parent.
+ *
+ * @param controller controller
+ * @param parentId parent id
+ * @param options optional bundle
+ */
+ public void onSubscribed(@NonNull ControllerInfo controller,
+ String parentId, @Nullable Bundle options) {
+ }
+
+ /**
+ * Called when a controller unsubscribes to the parent.
+ *
+ * @param controller controller
+ * @param parentId parent id
+ * @param options optional bundle
+ */
+ public void onUnsubscribed(@NonNull ControllerInfo controller,
+ String parentId, @Nullable Bundle options) {
+ }
+ }
+
+ /**
+ * Builder for {@link MediaLibrarySession}.
+ */
+ // TODO(jaewan): Move this to updatable.
+ public class MediaLibrarySessionBuilder
+ extends BuilderBase<MediaLibrarySessionBuilder, MediaLibrarySessionCallback> {
+ public MediaLibrarySessionBuilder(
+ @NonNull Context context, @NonNull MediaPlayerBase player,
+ @NonNull MediaLibrarySessionCallback callback) {
+ super(context, player);
+ setSessionCallback(callback);
+ }
+
+ @Override
+ public MediaLibrarySessionBuilder setSessionCallback(
+ @NonNull MediaLibrarySessionCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("MediaLibrarySessionCallback cannot be null");
+ }
+ return super.setSessionCallback(callback);
+ }
+
+ @Override
+ public MediaLibrarySession build() throws IllegalStateException {
+ return new MediaLibrarySession(mContext, mPlayer, mId, mCallback,
+ mVolumeProvider, mRatingType, mSessionActivity);
+ }
+ }
+
+ @Override
+ MediaSessionService2Provider createProvider() {
+ return ApiLoader.getProvider(this).createMediaLibraryService2(this);
+ }
+
+ /**
+ * Called when another app requested to start this service.
+ * <p>
+ * Library service will accept or reject the connection with the
+ * {@link MediaLibrarySessionCallback} in the created session.
+ * <p>
+ * Service wouldn't run if {@code null} is returned or session's ID doesn't match with the
+ * expected ID that you've specified through the AndroidManifest.xml.
+ * <p>
+ * This method will be called on the main thread.
+ *
+ * @param sessionId session id written in the AndroidManifest.xml.
+ * @return a new browser session
+ * @see MediaLibrarySessionBuilder
+ * @see #getSession()
+ * @throws RuntimeException if returned session is invalid
+ */
+ @Override
+ public @NonNull abstract MediaLibrarySession onCreateSession(String sessionId);
+
+ /**
+ * Contains information that the browser service needs to send to the client
+ * when first connected.
+ */
+ public static final class BrowserRoot {
+ /**
+ * The lookup key for a boolean that indicates whether the browser service should return a
+ * browser root for recently played media items.
+ *
+ * <p>When creating a media browser for a given media browser service, this key can be
+ * supplied as a root hint for retrieving media items that are recently played.
+ * If the media browser service can provide such media items, the implementation must return
+ * the key in the root hint when
+ * {@link MediaLibrarySessionCallback#onGetRoot(ControllerInfo, Bundle)} is called back.
+ *
+ * <p>The root hint may contain multiple keys.
+ *
+ * @see #EXTRA_OFFLINE
+ * @see #EXTRA_SUGGESTED
+ */
+ public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
+
+ /**
+ * The lookup key for a boolean that indicates whether the browser service should return a
+ * browser root for offline media items.
+ *
+ * <p>When creating a media browser for a given media browser service, this key can be
+ * supplied as a root hint for retrieving media items that are can be played without an
+ * internet connection.
+ * If the media browser service can provide such media items, the implementation must return
+ * the key in the root hint when
+ * {@link MediaLibrarySessionCallback#onGetRoot(ControllerInfo, Bundle)} is called back.
+ *
+ * <p>The root hint may contain multiple keys.
+ *
+ * @see #EXTRA_RECENT
+ * @see #EXTRA_SUGGESTED
+ */
+ public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+
+ /**
+ * The lookup key for a boolean that indicates whether the browser service should return a
+ * browser root for suggested media items.
+ *
+ * <p>When creating a media browser for a given media browser service, this key can be
+ * supplied as a root hint for retrieving the media items suggested by the media browser
+ * service. The list of media items passed in {@link android.media.browse.MediaBrowser.SubscriptionCallback#onChildrenLoaded(String, List)}
+ * is considered ordered by relevance, first being the top suggestion.
+ * If the media browser service can provide such media items, the implementation must return
+ * the key in the root hint when
+ * {@link MediaLibrarySessionCallback#onGetRoot(ControllerInfo, Bundle)} is called back.
+ *
+ * <p>The root hint may contain multiple keys.
+ *
+ * @see #EXTRA_RECENT
+ * @see #EXTRA_OFFLINE
+ */
+ public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
+
+ final private String mRootId;
+ final private Bundle mExtras;
+
+ /**
+ * Constructs a browser root.
+ * @param rootId The root id for browsing.
+ * @param extras Any extras about the browser service.
+ */
+ public BrowserRoot(@NonNull String rootId, @Nullable Bundle extras) {
+ if (rootId == null) {
+ throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. " +
+ "Use null for BrowserRoot instead.");
+ }
+ mRootId = rootId;
+ mExtras = extras;
+ }
+
+ /**
+ * Gets the root id for browsing.
+ */
+ public String getRootId() {
+ return mRootId;
+ }
+
+ /**
+ * Gets any extras about the browser service.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+ }
+}
diff --git a/media/java/android/media/MediaMetadata2.java b/media/java/android/media/MediaMetadata2.java
new file mode 100644
index 0000000..0e24db6
--- /dev/null
+++ b/media/java/android/media/MediaMetadata2.java
@@ -0,0 +1,815 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.ArrayMap;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+
+/**
+ * Contains metadata about an item, such as the title, artist, etc.
+ * @hide
+ */
+// TODO(jaewan): Move this to updatable
+public final class MediaMetadata2 {
+ private static final String TAG = "MediaMetadata2";
+
+ /**
+ * The title of the media.
+ */
+ public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+
+ /**
+ * The artist of the media.
+ */
+ public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+
+ /**
+ * The duration of the media in ms. A negative duration indicates that the
+ * duration is unknown (or infinite).
+ */
+ public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+
+ /**
+ * The album title for the media.
+ */
+ public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+
+ /**
+ * The author of the media.
+ */
+ public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+
+ /**
+ * The writer of the media.
+ */
+ public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+
+ /**
+ * The composer of the media.
+ */
+ public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+
+ /**
+ * The compilation status of the media.
+ */
+ public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+
+ /**
+ * The date the media was created or published. The format is unspecified
+ * but RFC 3339 is recommended.
+ */
+ public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+
+ /**
+ * The year the media was created or published as a long.
+ */
+ public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+
+ /**
+ * The genre of the media.
+ */
+ public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+
+ /**
+ * The track number for the media.
+ */
+ public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+
+ /**
+ * The number of tracks in the media's original source.
+ */
+ public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+
+ /**
+ * The disc number for the media's original source.
+ */
+ public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+
+ /**
+ * The artist for the album of the media's original source.
+ */
+ public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+
+ /**
+ * The artwork for the media as a {@link Bitmap}.
+ *
+ * The artwork should be relatively small and may be scaled down
+ * if it is too large. For higher resolution artwork
+ * {@link #METADATA_KEY_ART_URI} should be used instead.
+ */
+ public static final String METADATA_KEY_ART = "android.media.metadata.ART";
+
+ /**
+ * The artwork for the media as a Uri style String.
+ */
+ public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+
+ /**
+ * The artwork for the album of the media's original source as a
+ * {@link Bitmap}.
+ * The artwork should be relatively small and may be scaled down
+ * if it is too large. For higher resolution artwork
+ * {@link #METADATA_KEY_ALBUM_ART_URI} should be used instead.
+ */
+ public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+
+ /**
+ * The artwork for the album of the media's original source as a Uri style
+ * String.
+ */
+ public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+
+ /**
+ * The user's rating for the media.
+ *
+ * @see Rating
+ */
+ public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+
+ /**
+ * The overall rating for the media.
+ *
+ * @see Rating
+ */
+ public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+
+ /**
+ * A title that is suitable for display to the user. This will generally be
+ * the same as {@link #METADATA_KEY_TITLE} but may differ for some formats.
+ * When displaying media described by this metadata this should be preferred
+ * if present.
+ */
+ public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+
+ /**
+ * A subtitle that is suitable for display to the user. When displaying a
+ * second line for media described by this metadata this should be preferred
+ * to other fields if present.
+ */
+ public static final String METADATA_KEY_DISPLAY_SUBTITLE
+ = "android.media.metadata.DISPLAY_SUBTITLE";
+
+ /**
+ * A description that is suitable for display to the user. When displaying
+ * more information for media described by this metadata this should be
+ * preferred to other fields if present.
+ */
+ public static final String METADATA_KEY_DISPLAY_DESCRIPTION
+ = "android.media.metadata.DISPLAY_DESCRIPTION";
+
+ /**
+ * An icon or thumbnail that is suitable for display to the user. When
+ * displaying an icon for media described by this metadata this should be
+ * preferred to other fields if present. This must be a {@link Bitmap}.
+ *
+ * The icon should be relatively small and may be scaled down
+ * if it is too large. For higher resolution artwork
+ * {@link #METADATA_KEY_DISPLAY_ICON_URI} should be used instead.
+ */
+ public static final String METADATA_KEY_DISPLAY_ICON
+ = "android.media.metadata.DISPLAY_ICON";
+
+ /**
+ * An icon or thumbnail that is suitable for display to the user. When
+ * displaying more information for media described by this metadata the
+ * display description should be preferred to other fields when present.
+ * This must be a Uri style String.
+ */
+ public static final String METADATA_KEY_DISPLAY_ICON_URI
+ = "android.media.metadata.DISPLAY_ICON_URI";
+
+ /**
+ * A String key for identifying the content. This value is specific to the
+ * service providing the content. If used, this should be a persistent
+ * unique key for the underlying content. It may be used with
+ * {@link MediaController2#playFromMediaId(String, Bundle)}
+ * to initiate playback when provided by a {@link MediaBrowser2} connected to
+ * the same app.
+ */
+ public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+
+ /**
+ * A Uri formatted String representing the content. This value is specific to the
+ * service providing the content. It may be used with
+ * {@link MediaController2#playFromUri(Uri, Bundle)}
+ * to initiate playback when provided by a {@link MediaBrowser2} connected to
+ * the same app.
+ */
+ public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
+
+ /**
+ * The bluetooth folder type of the media specified in the section 6.10.2.2 of the Bluetooth
+ * AVRCP 1.5. It should be one of the following:
+ * <ul>
+ * <li>{@link #BT_FOLDER_TYPE_MIXED}</li>
+ * <li>{@link #BT_FOLDER_TYPE_TITLES}</li>
+ * <li>{@link #BT_FOLDER_TYPE_ALBUMS}</li>
+ * <li>{@link #BT_FOLDER_TYPE_ARTISTS}</li>
+ * <li>{@link #BT_FOLDER_TYPE_GENRES}</li>
+ * <li>{@link #BT_FOLDER_TYPE_PLAYLISTS}</li>
+ * <li>{@link #BT_FOLDER_TYPE_YEARS}</li>
+ * </ul>
+ */
+ public static final String METADATA_KEY_BT_FOLDER_TYPE
+ = "android.media.metadata.BT_FOLDER_TYPE";
+
+ /**
+ * The type of folder that is unknown or contains media elements of mixed types as specified in
+ * the section 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_MIXED = 0;
+
+ /**
+ * The type of folder that contains media elements only as specified in the section 6.10.2.2 of
+ * the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_TITLES = 1;
+
+ /**
+ * The type of folder that contains folders categorized by album as specified in the section
+ * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_ALBUMS = 2;
+
+ /**
+ * The type of folder that contains folders categorized by artist as specified in the section
+ * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_ARTISTS = 3;
+
+ /**
+ * The type of folder that contains folders categorized by genre as specified in the section
+ * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_GENRES = 4;
+
+ /**
+ * The type of folder that contains folders categorized by playlist as specified in the section
+ * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_PLAYLISTS = 5;
+
+ /**
+ * The type of folder that contains folders categorized by year as specified in the section
+ * 6.10.2.2 of the Bluetooth AVRCP 1.5.
+ */
+ public static final long BT_FOLDER_TYPE_YEARS = 6;
+
+ /**
+ * Whether the media is an advertisement. A value of 0 indicates it is not an advertisement. A
+ * value of 1 or non-zero indicates it is an advertisement. If not specified, this value is set
+ * to 0 by default.
+ */
+ public static final String METADATA_KEY_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
+
+ /**
+ * The download status of the media which will be used for later offline playback. It should be
+ * one of the following:
+ *
+ * <ul>
+ * <li>{@link #STATUS_NOT_DOWNLOADED}</li>
+ * <li>{@link #STATUS_DOWNLOADING}</li>
+ * <li>{@link #STATUS_DOWNLOADED}</li>
+ * </ul>
+ */
+ public static final String METADATA_KEY_DOWNLOAD_STATUS =
+ "android.media.metadata.DOWNLOAD_STATUS";
+
+ /**
+ * The status value to indicate the media item is not downloaded.
+ *
+ * @see #METADATA_KEY_DOWNLOAD_STATUS
+ */
+ public static final long STATUS_NOT_DOWNLOADED = 0;
+
+ /**
+ * The status value to indicate the media item is being downloaded.
+ *
+ * @see #METADATA_KEY_DOWNLOAD_STATUS
+ */
+ public static final long STATUS_DOWNLOADING = 1;
+
+ /**
+ * The status value to indicate the media item is downloaded for later offline playback.
+ *
+ * @see #METADATA_KEY_DOWNLOAD_STATUS
+ */
+ public static final long STATUS_DOWNLOADED = 2;
+
+ /**
+ * A {@link Bundle} extra.
+ * @hide
+ */
+ public static final String METADATA_KEY_EXTRA = "android.media.metadata.EXTRA";
+
+ /**
+ * @hide
+ */
+ @StringDef({METADATA_KEY_TITLE, METADATA_KEY_ARTIST, METADATA_KEY_ALBUM, METADATA_KEY_AUTHOR,
+ METADATA_KEY_WRITER, METADATA_KEY_COMPOSER, METADATA_KEY_COMPILATION,
+ METADATA_KEY_DATE, METADATA_KEY_GENRE, METADATA_KEY_ALBUM_ARTIST, METADATA_KEY_ART_URI,
+ METADATA_KEY_ALBUM_ART_URI, METADATA_KEY_DISPLAY_TITLE, METADATA_KEY_DISPLAY_SUBTITLE,
+ METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_KEY_DISPLAY_ICON_URI,
+ METADATA_KEY_MEDIA_ID, METADATA_KEY_MEDIA_URI})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TextKey {}
+
+ /**
+ * @hide
+ */
+ @StringDef({METADATA_KEY_DURATION, METADATA_KEY_YEAR, METADATA_KEY_TRACK_NUMBER,
+ METADATA_KEY_NUM_TRACKS, METADATA_KEY_DISC_NUMBER, METADATA_KEY_BT_FOLDER_TYPE,
+ METADATA_KEY_ADVERTISEMENT, METADATA_KEY_DOWNLOAD_STATUS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface LongKey {}
+
+ /**
+ * @hide
+ */
+ @StringDef({METADATA_KEY_ART, METADATA_KEY_ALBUM_ART, METADATA_KEY_DISPLAY_ICON})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BitmapKey {}
+
+ /**
+ * @hide
+ */
+ @StringDef({METADATA_KEY_USER_RATING, METADATA_KEY_RATING})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RatingKey {}
+
+ static final int METADATA_TYPE_LONG = 0;
+ static final int METADATA_TYPE_TEXT = 1;
+ static final int METADATA_TYPE_BITMAP = 2;
+ static final int METADATA_TYPE_RATING = 3;
+ static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
+
+ static {
+ METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
+ METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_BT_FOLDER_TYPE, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_URI, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ADVERTISEMENT, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DOWNLOAD_STATUS, METADATA_TYPE_LONG);
+ }
+
+ private static final @TextKey String[] PREFERRED_DESCRIPTION_ORDER = {
+ METADATA_KEY_TITLE,
+ METADATA_KEY_ARTIST,
+ METADATA_KEY_ALBUM,
+ METADATA_KEY_ALBUM_ARTIST,
+ METADATA_KEY_WRITER,
+ METADATA_KEY_AUTHOR,
+ METADATA_KEY_COMPOSER
+ };
+
+ private static final @BitmapKey String[] PREFERRED_BITMAP_ORDER = {
+ METADATA_KEY_DISPLAY_ICON,
+ METADATA_KEY_ART,
+ METADATA_KEY_ALBUM_ART
+ };
+
+ private static final @TextKey String[] PREFERRED_URI_ORDER = {
+ METADATA_KEY_DISPLAY_ICON_URI,
+ METADATA_KEY_ART_URI,
+ METADATA_KEY_ALBUM_ART_URI
+ };
+
+ final Bundle mBundle;
+
+ /**
+ * @hide
+ */
+ public MediaMetadata2(Bundle bundle) {
+ mBundle = new Bundle(bundle);
+ }
+
+ /**
+ * Returns true if the given key is contained in the metadata
+ *
+ * @param key a String key
+ * @return true if the key exists in this metadata, false otherwise
+ */
+ public boolean containsKey(String key) {
+ return mBundle.containsKey(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if no mapping of
+ * the desired type exists for the given key or a null value is explicitly
+ * associated with the key.
+ *
+ * @param key The key the value is stored under
+ * @return a CharSequence value, or null
+ */
+ public CharSequence getText(@TextKey String key) {
+ return mBundle.getCharSequence(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if no mapping of
+ * the desired type exists for the given key or a null value is explicitly
+ * associated with the key.
+ *
+ * @
+ * @return media id. Can be {@code null}
+ */
+ public @Nullable String getMediaId() {
+ return getString(METADATA_KEY_MEDIA_ID);
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if no mapping of
+ * the desired type exists for the given key or a null value is explicitly
+ * associated with the key.
+ *
+ * @param key The key the value is stored under
+ * @return a String value, or null
+ */
+ public String getString(@TextKey String key) {
+ CharSequence text = mBundle.getCharSequence(key);
+ if (text != null) {
+ return text.toString();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the value associated with the given key, or 0L if no long exists
+ * for the given key.
+ *
+ * @param key The key the value is stored under
+ * @return a long value
+ */
+ public long getLong(@LongKey String key) {
+ return mBundle.getLong(key, 0);
+ }
+
+ /**
+ * Return a {@link Rating2} for the given key or null if no rating exists for
+ * the given key.
+ *
+ * @param key The key the value is stored under
+ * @return A {@link Rating2} or null
+ */
+ public Rating2 getRating(@RatingKey String key) {
+ // TODO(jaewan): Add backward compatibility
+ Rating2 rating = null;
+ try {
+ rating = Rating2.fromBundle(mBundle.getBundle(key));
+ } catch (Exception e) {
+ // ignore, value was not a rating
+ Log.w(TAG, "Failed to retrieve a key as Rating.", e);
+ }
+ return rating;
+ }
+
+ /**
+ * Return a {@link Bitmap} for the given key or null if no bitmap exists for
+ * the given key.
+ *
+ * @param key The key the value is stored under
+ * @return A {@link Bitmap} or null
+ */
+ public Bitmap getBitmap(@BitmapKey String key) {
+ Bitmap bmp = null;
+ try {
+ bmp = mBundle.getParcelable(key);
+ } catch (Exception e) {
+ // ignore, value was not a bitmap
+ Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
+ }
+ return bmp;
+ }
+
+ /**
+ * Get the extra {@link Bundle} from the metadata object.
+ *
+ * @return A {@link Bundle} or {@code null}
+ */
+ public Bundle getExtra() {
+ try {
+ return mBundle.getBundle(METADATA_KEY_EXTRA);
+ } catch (Exception e) {
+ // ignore, value was not an bundle
+ Log.w(TAG, "Failed to retrieve an extra");
+ }
+ return null;
+ }
+
+ /**
+ * Get the number of fields in this metadata.
+ *
+ * @return The number of fields in the metadata.
+ */
+ public int size() {
+ return mBundle.size();
+ }
+
+ /**
+ * Returns a Set containing the Strings used as keys in this metadata.
+ *
+ * @return a Set of String keys
+ */
+ public Set<String> keySet() {
+ return mBundle.keySet();
+ }
+
+ /**
+ * Gets the bundle backing the metadata object. This is available to support
+ * backwards compatibility. Apps should not modify the bundle directly.
+ *
+ * @return The Bundle backing this metadata.
+ */
+ public Bundle getBundle() {
+ return mBundle;
+ }
+
+ /**
+ * Use to build MediaMetadata2 objects. The system defined metadata keys must
+ * use the appropriate data type.
+ */
+ public static final class Builder {
+ private final Bundle mBundle;
+
+ /**
+ * Create an empty Builder. Any field that should be included in the
+ * {@link MediaMetadata2} must be added.
+ */
+ public Builder() {
+ mBundle = new Bundle();
+ }
+
+ /**
+ * Create a Builder using a {@link MediaMetadata2} instance to set the
+ * initial values. All fields in the source metadata will be included in
+ * the new metadata. Fields can be overwritten by adding the same key.
+ *
+ * @param source
+ */
+ public Builder(MediaMetadata2 source) {
+ mBundle = new Bundle(source.mBundle);
+ }
+
+ /**
+ * Create a Builder using a {@link MediaMetadata2} instance to set
+ * initial values, but replace bitmaps with a scaled down copy if they
+ * are larger than maxBitmapSize.
+ *
+ * @param source The original metadata to copy.
+ * @param maxBitmapSize The maximum height/width for bitmaps contained
+ * in the metadata.
+ * @hide
+ */
+ public Builder(MediaMetadata2 source, int maxBitmapSize) {
+ this(source);
+ for (String key : mBundle.keySet()) {
+ Object value = mBundle.get(key);
+ if (value instanceof Bitmap) {
+ Bitmap bmp = (Bitmap) value;
+ if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) {
+ putBitmap(key, scaleBitmap(bmp, maxBitmapSize));
+ }
+ }
+ }
+ }
+
+ /**
+ * Put a CharSequence value into the metadata. Custom keys may be used,
+ * but if the METADATA_KEYs defined in this class are used they may only
+ * be one of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_TITLE}</li>
+ * <li>{@link #METADATA_KEY_ARTIST}</li>
+ * <li>{@link #METADATA_KEY_ALBUM}</li>
+ * <li>{@link #METADATA_KEY_AUTHOR}</li>
+ * <li>{@link #METADATA_KEY_WRITER}</li>
+ * <li>{@link #METADATA_KEY_COMPOSER}</li>
+ * <li>{@link #METADATA_KEY_DATE}</li>
+ * <li>{@link #METADATA_KEY_GENRE}</li>
+ * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
+ * <li>{@link #METADATA_KEY_ART_URI}</li>
+ * <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
+ * </ul>
+ *
+ * @param key The key for referencing this value
+ * @param value The CharSequence value to store
+ * @return The Builder to allow chaining
+ */
+ public Builder putText(@TextKey String key, CharSequence value) {
+ if (METADATA_KEYS_TYPE.containsKey(key)) {
+ if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a CharSequence");
+ }
+ }
+ mBundle.putCharSequence(key, value);
+ return this;
+ }
+
+ /**
+ * Put a String value into the metadata. Custom keys may be used, but if
+ * the METADATA_KEYs defined in this class are used they may only be one
+ * of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_TITLE}</li>
+ * <li>{@link #METADATA_KEY_ARTIST}</li>
+ * <li>{@link #METADATA_KEY_ALBUM}</li>
+ * <li>{@link #METADATA_KEY_AUTHOR}</li>
+ * <li>{@link #METADATA_KEY_WRITER}</li>
+ * <li>{@link #METADATA_KEY_COMPOSER}</li>
+ * <li>{@link #METADATA_KEY_DATE}</li>
+ * <li>{@link #METADATA_KEY_GENRE}</li>
+ * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
+ * <li>{@link #METADATA_KEY_ART_URI}</li>
+ * <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
+ * </ul>
+ *
+ * @param key The key for referencing this value
+ * @param value The String value to store
+ * @return The Builder to allow chaining
+ */
+ public Builder putString(@TextKey String key, String value) {
+ if (METADATA_KEYS_TYPE.containsKey(key)) {
+ if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a String");
+ }
+ }
+ mBundle.putCharSequence(key, value);
+ return this;
+ }
+
+ /**
+ * Put a long value into the metadata. Custom keys may be used, but if
+ * the METADATA_KEYs defined in this class are used they may only be one
+ * of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_DURATION}</li>
+ * <li>{@link #METADATA_KEY_TRACK_NUMBER}</li>
+ * <li>{@link #METADATA_KEY_NUM_TRACKS}</li>
+ * <li>{@link #METADATA_KEY_DISC_NUMBER}</li>
+ * <li>{@link #METADATA_KEY_YEAR}</li>
+ * <li>{@link #METADATA_KEY_BT_FOLDER_TYPE}</li>
+ * <li>{@link #METADATA_KEY_ADVERTISEMENT}</li>
+ * <li>{@link #METADATA_KEY_DOWNLOAD_STATUS}</li>
+ * </ul>
+ *
+ * @param key The key for referencing this value
+ * @param value The String value to store
+ * @return The Builder to allow chaining
+ */
+ public Builder putLong(@LongKey String key, long value) {
+ if (METADATA_KEYS_TYPE.containsKey(key)) {
+ if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_LONG) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a long");
+ }
+ }
+ mBundle.putLong(key, value);
+ return this;
+ }
+
+ /**
+ * Put a {@link Rating2} into the metadata. Custom keys may be used, but
+ * if the METADATA_KEYs defined in this class are used they may only be
+ * one of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_RATING}</li>
+ * <li>{@link #METADATA_KEY_USER_RATING}</li>
+ * </ul>
+ *
+ * @param key The key for referencing this value
+ * @param value The String value to store
+ * @return The Builder to allow chaining
+ */
+ public Builder putRating(@RatingKey String key, Rating2 value) {
+ if (METADATA_KEYS_TYPE.containsKey(key)) {
+ if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_RATING) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a Rating");
+ }
+ }
+ mBundle.putBundle(key, value.toBundle());
+
+ return this;
+ }
+
+ /**
+ * Put a {@link Bitmap} into the metadata. Custom keys may be used, but
+ * if the METADATA_KEYs defined in this class are used they may only be
+ * one of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_ART}</li>
+ * <li>{@link #METADATA_KEY_ALBUM_ART}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_ICON}</li>
+ * </ul>
+ * Large bitmaps may be scaled down by the system when
+ * {@link android.media.session.MediaSession#setMetadata} is called.
+ * To pass full resolution images {@link Uri Uris} should be used with
+ * {@link #putString}.
+ *
+ * @param key The key for referencing this value
+ * @param value The Bitmap to store
+ * @return The Builder to allow chaining
+ */
+ public Builder putBitmap(@BitmapKey String key, Bitmap value) {
+ if (METADATA_KEYS_TYPE.containsKey(key)) {
+ if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a Bitmap");
+ }
+ }
+ mBundle.putParcelable(key, value);
+ return this;
+ }
+
+ /**
+ * Set an extra {@link Bundle} into the metadata.
+ */
+ public Builder setExtra(Bundle bundle) {
+ mBundle.putBundle(METADATA_KEY_EXTRA, bundle);
+ return this;
+ }
+
+ /**
+ * Creates a {@link MediaMetadata2} instance with the specified fields.
+ *
+ * @return The new MediaMetadata2 instance
+ */
+ public MediaMetadata2 build() {
+ return new MediaMetadata2(mBundle);
+ }
+
+ private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
+ float maxSizeF = maxSize;
+ float widthScale = maxSizeF / bmp.getWidth();
+ float heightScale = maxSizeF / bmp.getHeight();
+ float scale = Math.min(widthScale, heightScale);
+ int height = (int) (bmp.getHeight() * scale);
+ int width = (int) (bmp.getWidth() * scale);
+ return Bitmap.createScaledBitmap(bmp, width, height, true);
+ }
+ }
+}
+
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
index 807535b..6fdf7a8 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -16,25 +16,29 @@
package android.media;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.MediaPlayerBase.PlaybackListener;
import android.media.session.MediaSession;
import android.media.session.MediaSession.Callback;
import android.media.session.PlaybackState;
import android.media.update.ApiLoader;
import android.media.update.MediaSession2Provider;
import android.media.update.MediaSession2Provider.ControllerInfoProvider;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
-import android.os.Process;
+import android.os.ResultReceiver;
import android.text.TextUtils;
import android.util.ArraySet;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -55,42 +59,55 @@
* instead. With it, your playback can be revived even after you've finished playback. See
* {@link MediaSessionService2} for details.
* <p>
- * A session can be obtained by {@link #getInstance(Context, Handler)}. The owner of the session may
- * pass its session token to other processes to allow them to create a {@link MediaController2}
- * to interact with the session.
+ * A session can be obtained by {@link Builder}. The owner of the session may pass its session token
+ * to other processes to allow them to create a {@link MediaController2} to interact with the
+ * session.
* <p>
- * To receive transport control commands, an underlying media player must be set with
- * {@link #setPlayer(MediaPlayerBase)}. Commands will be sent to the underlying player directly
- * on the thread that had been specified by {@link #getInstance(Context, Handler)}.
+ * When a session receive transport control commands, the session sends the commands directly to
+ * the the underlying media player set by {@link Builder} or {@link #setPlayer(MediaPlayerBase)}.
* <p>
- * When an app is finished performing playback it must call
- * {@link #setPlayer(MediaPlayerBase)} with {@code null} to clean up the session and notify any
- * controllers. It's developers responsibility of cleaning the session and releasing resources.
+ * When an app is finished performing playback it must call {@link #close()} to clean up the session
+ * and notify any controllers.
* <p>
- * MediaSession2 objects should be used on the handler's thread that is initially given by
- * {@link #getInstance(Context, Handler)}.
+ * {@link MediaSession2} objects should be used on the thread on the looper.
*
* @see MediaSessionService2
* @hide
*/
// TODO(jaewan): Unhide
// TODO(jaewan): Revisit comments. Currently it's borrowed from the MediaSession.
-// TODO(jaewan): Add explicit release(), and make token @NonNull. Session will be active while the
-// session exists, and controllers will be invalidated when session becomes inactive.
// TODO(jaewan): Should we support thread safe? It may cause tricky issue such as b/63797089
// TODO(jaewan): Should we make APIs for MediaSessionService2 public? It's helpful for
// developers that doesn't want to override from Browser, but user may not use this
// correctly.
-public final class MediaSession2 extends MediaPlayerBase {
+public class MediaSession2 implements AutoCloseable {
private final MediaSession2Provider mProvider;
// Note: Do not define IntDef because subclass can add more command code on top of these.
+ // TODO(jaewan): Shouldn't we pull out?
public static final int COMMAND_CODE_CUSTOM = 0;
public static final int COMMAND_CODE_PLAYBACK_START = 1;
public static final int COMMAND_CODE_PLAYBACK_PAUSE = 2;
public static final int COMMAND_CODE_PLAYBACK_STOP = 3;
public static final int COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM = 4;
public static final int COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM = 5;
+ public static final int COMMAND_CODE_PLAYBACK_PREPARE = 6;
+ public static final int COMMAND_CODE_PLAYBACK_FAST_FORWARD = 7;
+ public static final int COMMAND_CODE_PLAYBACK_REWIND = 8;
+ public static final int COMMAND_CODE_PLAYBACK_SEEK_TO = 9;
+ public static final int COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM = 10;
+
+ public static final int COMMAND_CODE_PLAYLIST_GET = 11;
+ public static final int COMMAND_CODE_PLAYLIST_ADD = 12;
+ public static final int COMMAND_CODE_PLAYLIST_REMOVE = 13;
+
+ public static final int COMMAND_CODE_PLAY_FROM_MEDIA_ID = 14;
+ public static final int COMMAND_CODE_PLAY_FROM_URI = 15;
+ public static final int COMMAND_CODE_PLAY_FROM_SEARCH = 16;
+
+ public static final int COMMAND_CODE_PREPARE_FROM_MEDIA_ID = 17;
+ public static final int COMMAND_CODE_PREPARE_FROM_URI = 18;
+ public static final int COMMAND_CODE_PREPARE_FROM_SEARCH = 19;
/**
* Define a command that a {@link MediaController2} can send to a {@link MediaSession2}.
@@ -102,11 +119,11 @@
// TODO(jaewan): Move this into the updatable.
public static final class Command {
private static final String KEY_COMMAND_CODE
- = "android.media.mediasession2.command.command_command";
+ = "android.media.media_session2.command.command_code";
private static final String KEY_COMMAND_CUSTOM_COMMAND
- = "android.media.mediasession2.command.custom_command";
+ = "android.media.media_session2.command.custom_command";
private static final String KEY_COMMAND_EXTRA
- = "android.media.mediasession2.command.extra";
+ = "android.media.media_session2.command.extra";
private final int mCommandCode;
// Nonnull if it's custom command
@@ -305,35 +322,149 @@
}
/**
- * Called when a controller sent a command to the session. You can also reject the request
- * by return {@code false}.
- * <p>
- * This method will be called on the session handler.
+ * Called when a controller is disconnected
+ *
+ * @param controller controller information
+ */
+ public void onDisconnected(@NonNull ControllerInfo controller) { }
+
+ /**
+ * Called when a controller sent a command to the session, and the command will be sent to
+ * the player directly unless you reject the request by {@code false}.
*
* @param controller controller information.
* @param command a command. This method will be called for every single command.
* @return {@code true} if you want to accept incoming command. {@code false} otherwise.
*/
+ // TODO(jaewan): Add more documentations (or make it clear) which commands can be filtered
+ // with this.
public boolean onCommandRequest(@NonNull ControllerInfo controller,
@NonNull Command command) {
return true;
}
+
+ /**
+ * Called when a controller set rating on the currently playing contents.
+ *
+ * @param
+ */
+ public void onSetRating(@NonNull ControllerInfo controller, @NonNull Rating2 rating) { }
+
+ /**
+ * Called when a controller sent a custom command.
+ *
+ * @param controller controller information
+ * @param customCommand custom command.
+ * @param args optional arguments
+ * @param cb optional result receiver
+ */
+ public void onCustomCommand(@NonNull ControllerInfo controller,
+ @NonNull Command customCommand, @Nullable Bundle args,
+ @Nullable ResultReceiver cb) { }
+
+ /**
+ * Override to handle requests to prepare for playing a specific mediaId.
+ * During the preparation, a session should not hold audio focus in order to allow other
+ * sessions play seamlessly. The state of playback should be updated to
+ * {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+ * <p>
+ * The playback of the prepared content should start in the later calls of
+ * {@link MediaSession2#play()}.
+ * <p>
+ * Override {@link #onPlayFromMediaId} to handle requests for starting
+ * playback without preparation.
+ */
+ public void onPlayFromMediaId(@NonNull ControllerInfo controller,
+ @NonNull String mediaId, @Nullable Bundle extras) { }
+
+ /**
+ * Override to handle requests to prepare playback from a search query. An empty query
+ * indicates that the app may prepare any music. The implementation should attempt to make a
+ * smart choice about what to play. During the preparation, a session should not hold audio
+ * focus in order to allow other sessions play seamlessly. The state of playback should be
+ * updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+ * <p>
+ * The playback of the prepared content should start in the later calls of
+ * {@link MediaSession2#play()}.
+ * <p>
+ * Override {@link #onPlayFromSearch} to handle requests for starting playback without
+ * preparation.
+ */
+ public void onPlayFromSearch(@NonNull ControllerInfo controller,
+ @NonNull String query, @Nullable Bundle extras) { }
+
+ /**
+ * Override to handle requests to prepare a specific media item represented by a URI.
+ * During the preparation, a session should not hold audio focus in order to allow
+ * other sessions play seamlessly. The state of playback should be updated to
+ * {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+ * <p>
+ * The playback of the prepared content should start in the later calls of
+ * {@link MediaSession2#play()}.
+ * <p>
+ * Override {@link #onPlayFromUri} to handle requests for starting playback without
+ * preparation.
+ */
+ public void onPlayFromUri(@NonNull ControllerInfo controller,
+ @NonNull String uri, @Nullable Bundle extras) { }
+
+ /**
+ * Override to handle requests to play a specific mediaId.
+ */
+ public void onPrepareFromMediaId(@NonNull ControllerInfo controller,
+ @NonNull String mediaId, @Nullable Bundle extras) { }
+
+ /**
+ * Override to handle requests to begin playback from a search query. An
+ * empty query indicates that the app may play any music. The
+ * implementation should attempt to make a smart choice about what to
+ * play.
+ */
+ public void onPrepareFromSearch(@NonNull ControllerInfo controller,
+ @NonNull String query, @Nullable Bundle extras) { }
+
+ /**
+ * Override to handle requests to play a specific media item represented by a URI.
+ */
+ public void prepareFromUri(@NonNull ControllerInfo controller,
+ @NonNull Uri uri, @Nullable Bundle extras) { }
+
+ /**
+ * Called when a controller wants to add a {@link MediaItem2} at the specified position
+ * in the play queue.
+ * <p>
+ * The item from the media controller wouldn't have valid data source descriptor because
+ * it would have been anonymized when it's sent to the remote process.
+ *
+ * @param item The media item to be inserted.
+ * @param index The index at which the item is to be inserted.
+ */
+ public void onAddPlaylistItem(@NonNull ControllerInfo controller,
+ @NonNull MediaItem2 item, int index) { }
+
+ /**
+ * Called when a controller wants to remove the {@link MediaItem2}
+ *
+ * @param item
+ */
+ // Can we do this automatically?
+ public void onRemovePlaylistItem(@NonNull MediaItem2 item) { }
};
/**
- * Builder for {@link MediaSession2}.
- * <p>
- * Any incoming event from the {@link MediaController2} will be handled on the thread
- * that created session with the {@link Builder#build()}.
+ * Base builder class for MediaSession2 and its subclass.
+ *
+ * @hide
*/
- // TODO(jaewan): Move this to updatable
- // TODO(jaewan): Add setRatingType()
- // TODO(jaewan): Add setSessionActivity()
- public final static class Builder {
- private final Context mContext;
- private final MediaPlayerBase mPlayer;
- private String mId;
- private SessionCallback mCallback;
+ static abstract class BuilderBase
+ <T extends MediaSession2.BuilderBase<T, C>, C extends SessionCallback> {
+ final Context mContext;
+ final MediaPlayerBase mPlayer;
+ String mId;
+ C mCallback;
+ VolumeProvider mVolumeProvider;
+ int mRatingType;
+ PendingIntent mSessionActivity;
/**
* Constructor.
@@ -343,17 +474,14 @@
* @throws IllegalArgumentException if any parameter is null, or the player is a
* {@link MediaSession2} or {@link MediaController2}.
*/
- public Builder(@NonNull Context context, @NonNull MediaPlayerBase player) {
+ // TODO(jaewan): Also need executor
+ public BuilderBase(@NonNull Context context, @NonNull MediaPlayerBase player) {
if (context == null) {
throw new IllegalArgumentException("context shouldn't be null");
}
if (player == null) {
throw new IllegalArgumentException("player shouldn't be null");
}
- if (player instanceof MediaSession2 || player instanceof MediaController2) {
- throw new IllegalArgumentException("player doesn't accept MediaSession2 nor"
- + " MediaController2");
- }
mContext = context;
mPlayer = player;
// Ensure non-null
@@ -361,6 +489,50 @@
}
/**
+ * Set volume provider to configure this session to use remote volume handling.
+ * This must be called to receive volume button events, otherwise the system
+ * will adjust the appropriate stream volume for this session's player.
+ * <p>
+ * Set {@code null} to reset.
+ *
+ * @param volumeProvider The provider that will handle volume changes. Can be {@code null}
+ */
+ public T setVolumeProvider(@Nullable VolumeProvider volumeProvider) {
+ mVolumeProvider = volumeProvider;
+ return (T) this;
+ }
+
+ /**
+ * Set the style of rating used by this session. Apps trying to set the
+ * rating should use this style. Must be one of the following:
+ * <ul>
+ * <li>{@link Rating2#RATING_NONE}</li>
+ * <li>{@link Rating2#RATING_3_STARS}</li>
+ * <li>{@link Rating2#RATING_4_STARS}</li>
+ * <li>{@link Rating2#RATING_5_STARS}</li>
+ * <li>{@link Rating2#RATING_HEART}</li>
+ * <li>{@link Rating2#RATING_PERCENTAGE}</li>
+ * <li>{@link Rating2#RATING_THUMB_UP_DOWN}</li>
+ * </ul>
+ */
+ public T setRatingType(@Rating2.Style int type) {
+ mRatingType = type;
+ return (T) this;
+ }
+
+ /**
+ * Set an intent for launching UI for this Session. This can be used as a
+ * quick link to an ongoing media screen. The intent should be for an
+ * activity that may be started using {@link Activity#startActivity(Intent)}.
+ *
+ * @param pi The intent to launch to show UI for this session.
+ */
+ public T setSessionActivity(@Nullable PendingIntent pi) {
+ mSessionActivity = pi;
+ return (T) this;
+ }
+
+ /**
* Set ID of the session. If it's not set, an empty string with used to create a session.
* <p>
* Use this if and only if your app supports multiple playback at the same time and also
@@ -370,12 +542,12 @@
* @throws IllegalArgumentException if id is {@code null}
* @return
*/
- public Builder setId(@NonNull String id) {
+ public T setId(@NonNull String id) {
if (id == null) {
throw new IllegalArgumentException("id shouldn't be null");
}
mId = id;
- return this;
+ return (T) this;
}
/**
@@ -384,9 +556,9 @@
* @param callback session callback.
* @return
*/
- public Builder setSessionCallback(@Nullable SessionCallback callback) {
+ public T setSessionCallback(@Nullable C callback) {
mCallback = callback;
- return this;
+ return (T) this;
}
/**
@@ -396,11 +568,30 @@
* @throws IllegalStateException if the session with the same id is already exists for the
* package.
*/
+ public abstract MediaSession2 build() throws IllegalStateException;
+ }
+
+ /**
+ * Builder for {@link MediaSession2}.
+ * <p>
+ * Any incoming event from the {@link MediaController2} will be handled on the thread
+ * that created session with the {@link Builder#build()}.
+ */
+ // TODO(jaewan): Move this to updatable
+ // TODO(jaewan): Add setRatingType()
+ // TODO(jaewan): Add setSessionActivity()
+ public static final class Builder extends BuilderBase<Builder, SessionCallback> {
+ public Builder(Context context, @NonNull MediaPlayerBase player) {
+ super(context, player);
+ }
+
+ @Override
public MediaSession2 build() throws IllegalStateException {
if (mCallback == null) {
mCallback = new SessionCallback();
}
- return new MediaSession2(mContext, mPlayer, mId, mCallback);
+ return new MediaSession2(mContext, mPlayer, mId, mCallback,
+ mVolumeProvider, mRatingType, mSessionActivity);
}
}
@@ -473,12 +664,214 @@
@Override
public String toString() {
+ // TODO(jaewan): Move this to updatable.
return "ControllerInfo {pkg=" + getPackageName() + ", uid=" + getUid() + ", trusted="
+ isTrusted() + "}";
}
}
/**
+ * Button for a {@link Command} that will be shown by the controller.
+ * <p>
+ * It's up to the controller's decision to respect or ignore this customization request.
+ */
+ // TODO(jaewan): Move this to updatable.
+ public static class CommandButton {
+ private static final String KEY_COMMAND
+ = "android.media.media_session2.command_button.command";
+ private static final String KEY_ICON_RES_ID
+ = "android.media.media_session2.command_button.icon_res_id";
+ private static final String KEY_DISPLAY_NAME
+ = "android.media.media_session2.command_button.display_name";
+ private static final String KEY_EXTRA
+ = "android.media.media_session2.command_button.extra";
+ private static final String KEY_ENABLED
+ = "android.media.media_session2.command_button.enabled";
+
+ private Command mCommand;
+ private int mIconResId;
+ private String mDisplayName;
+ private Bundle mExtra;
+ private boolean mEnabled;
+
+ private CommandButton(@Nullable Command command, int iconResId,
+ @Nullable String displayName, Bundle extra, boolean enabled) {
+ mCommand = command;
+ mIconResId = iconResId;
+ mDisplayName = displayName;
+ mExtra = extra;
+ mEnabled = enabled;
+ }
+
+ /**
+ * Get command associated with this button. Can be {@code null} if the button isn't enabled
+ * and only providing placeholder.
+ *
+ * @return command or {@code null}
+ */
+ public @Nullable Command getCommand() {
+ return mCommand;
+ }
+
+ /**
+ * Resource id of the button in this package. Can be {@code 0} if the command is predefined
+ * and custom icon isn't needed.
+ *
+ * @return resource id of the icon. Can be {@code 0}.
+ */
+ public int getIconResId() {
+ return mIconResId;
+ }
+
+ /**
+ * Display name of the button. Can be {@code null} or empty if the command is predefined
+ * and custom name isn't needed.
+ *
+ * @return custom display name. Can be {@code null} or empty.
+ */
+ public @Nullable String getDisplayName() {
+ return mDisplayName;
+ }
+
+ /**
+ * Extra information of the button. It's private information between session and controller.
+ *
+ * @return
+ */
+ public @Nullable Bundle getExtra() {
+ return mExtra;
+ }
+
+ /**
+ * Return whether it's enabled
+ *
+ * @return {@code true} if enabled. {@code false} otherwise.
+ */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ /**
+ * @hide
+ */
+ // TODO(jaewan): @SystemApi
+ public @NonNull Bundle toBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putBundle(KEY_COMMAND, mCommand.toBundle());
+ bundle.putInt(KEY_ICON_RES_ID, mIconResId);
+ bundle.putString(KEY_DISPLAY_NAME, mDisplayName);
+ bundle.putBundle(KEY_EXTRA, mExtra);
+ bundle.putBoolean(KEY_ENABLED, mEnabled);
+ return bundle;
+ }
+
+ /**
+ * @hide
+ */
+ // TODO(jaewan): @SystemApi
+ public static @Nullable CommandButton fromBundle(Bundle bundle) {
+ Builder builder = new Builder();
+ builder.setCommand(Command.fromBundle(bundle.getBundle(KEY_COMMAND)));
+ builder.setIconResId(bundle.getInt(KEY_ICON_RES_ID, 0));
+ builder.setDisplayName(bundle.getString(KEY_DISPLAY_NAME));
+ builder.setExtra(bundle.getBundle(KEY_EXTRA));
+ builder.setEnabled(bundle.getBoolean(KEY_ENABLED));
+ try {
+ return builder.build();
+ } catch (IllegalStateException e) {
+ // Malformed or version mismatch. Return null for now.
+ return null;
+ }
+ }
+
+ /**
+ * Builder for {@link CommandButton}.
+ */
+ public static class Builder {
+ private Command mCommand;
+ private int mIconResId;
+ private String mDisplayName;
+ private Bundle mExtra;
+ private boolean mEnabled;
+
+ public Builder() {
+ mEnabled = true;
+ }
+
+ public Builder setCommand(Command command) {
+ mCommand = command;
+ return this;
+ }
+
+ public Builder setIconResId(int resId) {
+ mIconResId = resId;
+ return this;
+ }
+
+ public Builder setDisplayName(String displayName) {
+ mDisplayName = displayName;
+ return this;
+ }
+
+ public Builder setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ return this;
+ }
+
+ public Builder setExtra(Bundle extra) {
+ mExtra = extra;
+ return this;
+ }
+
+ public CommandButton build() {
+ if (mEnabled && mCommand == null) {
+ throw new IllegalStateException("Enabled button needs Command"
+ + " for controller to invoke the command");
+ }
+ if (mCommand != null && mCommand.getCommandCode() == COMMAND_CODE_CUSTOM
+ && (mIconResId == 0 || TextUtils.isEmpty(mDisplayName))) {
+ throw new IllegalStateException("Custom commands needs icon and"
+ + " and name to display");
+ }
+ return new CommandButton(mCommand, mIconResId, mDisplayName, mExtra, mEnabled);
+ }
+ }
+ }
+
+ /**
+ * Parameter for the playlist.
+ */
+ // TODO(jaewan): add fromBundle()/toBundle()
+ public class PlaylistParam {
+ private MediaMetadata2 mPlaylistMetadata;
+
+ // It's mixture of shuffle mode and repeat mode. Needs to be separated for avrcp 1.6 support
+ // PlaybackState#REPEAT_MODE_ALL
+ // PlaybackState#REPEAT_MODE_GROUP <- for avrcp 1.6
+ // PlaybackState#REPEAT_MODE_INVALID
+ // PlaybackState#REPEAT_MODE_NONE
+ // PlaybackState#REPEAT_MODE_ONE
+ // PlaybackState#SHUFFLE_MODE_ALL
+ // PlaybackState#SHUFFLE_MODE_GROUP <- for avrcp 1.6
+ // PlaybackState#SHUFFLE_MODE_INVALID
+ // PlaybackState#SHUFFLE_MODE_NONE
+ private int mLoopingMode;
+
+ public PlaylistParam(int loopingMode, @Nullable MediaMetadata2 playlistMetadata) {
+ mLoopingMode = loopingMode;
+ mPlaylistMetadata = playlistMetadata;
+ }
+
+ public int getLoopingMode() {
+ return mLoopingMode;
+ }
+
+ public MediaMetadata2 getPlaylistMetadata() {
+ return mPlaylistMetadata;
+ }
+ }
+
+ /**
* Constructor is hidden and apps can only instantiate indirectly through {@link Builder}.
* <p>
* This intended behavior and here's the reasons.
@@ -492,11 +885,20 @@
* framework had to add heuristics to figure out if an app is
* @hide
*/
- private MediaSession2(Context context, MediaPlayerBase player, String id,
- SessionCallback callback) {
+ MediaSession2(Context context, MediaPlayerBase player, String id,
+ SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+ PendingIntent sessionActivity) {
super();
- mProvider = ApiLoader.getProvider(context)
- .createMediaSession2(this, context, player, id, callback);
+ mProvider = createProvider(context, player, id, callback,
+ volumeProvider, ratingType, sessionActivity);
+ }
+
+ MediaSession2Provider createProvider(Context context, MediaPlayerBase player, String id,
+ SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+ PendingIntent sessionActivity) {
+ return ApiLoader.getProvider(context)
+ .createMediaSession2(this, context, player, id, callback,
+ volumeProvider, ratingType, sessionActivity);
}
/**
@@ -515,20 +917,36 @@
* If the new player is successfully set, {@link PlaybackListener}
* will be called to tell the current playback state of the new player.
* <p>
- * Calling this method with {@code null} will disconnect binding connection between the
- * controllers and also release this object.
+ * You can also specify a volume provider. If so, playback in the player is considered as
+ * remote playback.
*
* @param player a {@link MediaPlayerBase} that handles actual media playback in your app.
- * It shouldn't be {@link MediaSession2} nor {@link MediaController2}.
- * @throws IllegalArgumentException if the player is either {@link MediaSession2}
- * or {@link MediaController2}.
+ * @throws IllegalArgumentException if the player is {@code null}.
*/
- // TODO(jaewan): Add release instead of setPlayer(null).
- public void setPlayer(MediaPlayerBase player) throws IllegalArgumentException {
+ public void setPlayer(@NonNull MediaPlayerBase player) {
mProvider.setPlayer_impl(player);
}
/**
+ * Set the underlying {@link MediaPlayerBase} with the volume provider for remote playback.
+ *
+ * @param player a {@link MediaPlayerBase} that handles actual media playback in your app.
+ * @param volumeProvider a volume provider
+ * @see #setPlayer(MediaPlayerBase)
+ * @see Builder#setVolumeProvider(VolumeProvider)
+ * @throws IllegalArgumentException if a parameter is {@code null}.
+ */
+ public void setPlayer(@NonNull MediaPlayerBase player, @NonNull VolumeProvider volumeProvider)
+ throws IllegalArgumentException {
+ mProvider.setPlayer_impl(player, volumeProvider);
+ }
+
+ @Override
+ public void close() {
+ mProvider.close_impl();
+ }
+
+ /**
* @return player
*/
public @Nullable MediaPlayerBase getPlayer() {
@@ -547,61 +965,195 @@
return mProvider.getConnectedControllers_impl();
}
- @Override
+ /**
+ * Sets the {@link AudioAttributes} to be used during the playback of the video.
+ *
+ * @param attributes non-null <code>AudioAttributes</code>.
+ */
+ public void setAudioAttributes(@NonNull AudioAttributes attributes) {
+ mProvider.setAudioAttributes_impl(attributes);
+ }
+
+ /**
+ * Sets which type of audio focus will be requested during the playback, or configures playback
+ * to not request audio focus. Valid values for focus requests are
+ * {@link AudioManager#AUDIOFOCUS_GAIN}, {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT},
+ * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, and
+ * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. Or use
+ * {@link AudioManager#AUDIOFOCUS_NONE} to express that audio focus should not be
+ * requested when playback starts. You can for instance use this when playing a silent animation
+ * through this class, and you don't want to affect other audio applications playing in the
+ * background.
+ *
+ * @param focusGain the type of audio focus gain that will be requested, or
+ * {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during
+ * playback.
+ */
+ public void setAudioFocusRequest(int focusGain) {
+ mProvider.setAudioFocusRequest_impl(focusGain);
+ }
+
+ /**
+ * Sets ordered list of {@link CommandButton} for controllers to build UI with it.
+ * <p>
+ * It's up to controller's decision how to represent the layout in its own UI.
+ * Here's the same way
+ * (layout[i] means a CommandButton at index i in the given list)
+ * For 5 icons row
+ * layout[3] layout[1] layout[0] layout[2] layout[4]
+ * For 3 icons row
+ * layout[1] layout[0] layout[2]
+ * For 5 icons row with overflow icon (can show +5 extra buttons with overflow button)
+ * expanded row: layout[5] layout[6] layout[7] layout[8] layout[9]
+ * main row: layout[3] layout[1] layout[0] layout[2] layout[4]
+ * <p>
+ * This API can be called in the {@link SessionCallback#onConnect(ControllerInfo)}.
+ *
+ * @param controller controller to specify layout.
+ * @param layout oredered list of layout.
+ */
+ public void setCustomLayout(@NonNull ControllerInfo controller,
+ @NonNull List<CommandButton> layout) {
+ mProvider.setCustomLayout_impl(controller, layout);
+ }
+
+ /**
+ * Set the new allowed command group for the controller
+ *
+ * @param controller controller to change allowed commands
+ * @param commands new allowed commands
+ */
+ public void setAllowedCommands(@NonNull ControllerInfo controller,
+ @NonNull CommandGroup commands) {
+ mProvider.setAllowedCommands_impl(controller, commands);
+ }
+
+ /**
+ * Notify changes in metadata of previously set playlist. Controller will get the whole set of
+ * playlist again.
+ */
+ public void notifyMetadataChanged() {
+ mProvider.notifyMetadataChanged_impl();
+ }
+
+ /**
+ * Send custom command to all connected controllers.
+ *
+ * @param command a command
+ * @param args optional argument
+ */
+ public void sendCustomCommand(@NonNull Command command, @Nullable Bundle args) {
+ mProvider.sendCustomCommand_impl(command, args);
+ }
+
+ /**
+ * Send custom command to a specific controller.
+ *
+ * @param command a command
+ * @param args optional argument
+ * @param receiver result receiver for the session
+ */
+ public void sendCustomCommand(@NonNull ControllerInfo controller, @NonNull Command command,
+ @Nullable Bundle args, @Nullable ResultReceiver receiver) {
+ // Equivalent to the MediaController.sendCustomCommand(Action action, ResultReceiver r);
+ mProvider.sendCustomCommand_impl(controller, command, args, receiver);
+ }
+
+ /**
+ * Play playback
+ */
public void play() {
mProvider.play_impl();
}
- @Override
+ /**
+ * Pause playback
+ */
public void pause() {
mProvider.pause_impl();
}
- @Override
+ /**
+ * Stop playback
+ */
public void stop() {
mProvider.stop_impl();
}
- @Override
+ /**
+ * Rewind playback
+ */
public void skipToPrevious() {
mProvider.skipToPrevious_impl();
}
- @Override
+ /**
+ * Rewind playback
+ */
public void skipToNext() {
mProvider.skipToNext_impl();
}
- @Override
- public @NonNull PlaybackState getPlaybackState() {
- return mProvider.getPlaybackState_impl();
+ /**
+ * Request that the player prepare its playback. In other words, other sessions can continue
+ * to play during the preparation of this session. This method can be used to speed up the
+ * start of the playback. Once the preparation is done, the session will change its playback
+ * state to {@link PlaybackState#STATE_PAUSED}. Afterwards, {@link #play} can be called to
+ * start playback.
+ */
+ public void prepare() {
+ mProvider.prepare_impl();
}
/**
- * Add a {@link PlaybackListener} to listen changes in the
- * underlying {@link MediaPlayerBase} which is previously set by
- * {@link #setPlayer(MediaPlayerBase)}.
- * <p>
- * Added listeners will be also called when the underlying player is changed.
- *
- * @param listener the listener that will be run
- * @param handler the Handler that will receive the listener
- * @throws IllegalArgumentException when either the listener or handler is {@code null}.
+ * Start fast forwarding. If playback is already fast forwarding this may increase the rate.
*/
- // TODO(jaewan): Can handler be null? Follow API guideline after it's finalized.
- @Override
- public void addPlaybackListener(@NonNull PlaybackListener listener, @NonNull Handler handler) {
- mProvider.addPlaybackListener_impl(listener, handler);
+ public void fastForward() {
+ mProvider.fastForward_impl();
}
/**
- * Remove previously added {@link PlaybackListener}.
- *
- * @param listener the listener to be removed
- * @throws IllegalArgumentException if the listener is {@code null}.
+ * Start rewinding. If playback is already rewinding this may increase the rate.
*/
- @Override
- public void removePlaybackListener(PlaybackListener listener) {
- mProvider.removePlaybackListener_impl(listener);
+ public void rewind() {
+ mProvider.rewind_impl();
+ }
+
+ /**
+ * Move to a new location in the media stream.
+ *
+ * @param pos Position to move to, in milliseconds.
+ */
+ public void seekTo(long pos) {
+ mProvider.seekTo_impl(pos);
+ }
+
+ /**
+ * Sets the index of current DataSourceDesc in the play list to be played.
+ *
+ * @param index the index of DataSourceDesc in the play list you want to play
+ * @throws IllegalArgumentException if the play list is null
+ * @throws NullPointerException if index is outside play list range
+ */
+ public void setCurrentPlaylistItem(int index) {
+ mProvider.setCurrentPlaylistItem_impl(index);
+ }
+
+ /**
+ * @hide
+ */
+ public void skipForward() {
+ // To match with KEYCODE_MEDIA_SKIP_FORWARD
+ }
+
+ /**
+ * @hide
+ */
+ public void skipBackward() {
+ // To match with KEYCODE_MEDIA_SKIP_BACKWARD
+ }
+
+ public void setPlaylist(@NonNull List<MediaItem2> playlist, @NonNull PlaylistParam param) {
+ mProvider.setPlaylist_impl(playlist, param);
}
}
diff --git a/media/java/android/media/MediaSessionService2.java b/media/java/android/media/MediaSessionService2.java
index f1f5467..1a12d68 100644
--- a/media/java/android/media/MediaSessionService2.java
+++ b/media/java/android/media/MediaSessionService2.java
@@ -29,7 +29,7 @@
import android.os.IBinder;
/**
- * Service version of the {@link MediaSession2}.
+ * Base class for media session services, which is the service version of the {@link MediaSession2}.
* <p>
* It's highly recommended for an app to use this instead of {@link MediaSession2} if it wants
* to keep media playback in the background.
@@ -43,11 +43,11 @@
* </ul>
* For example, user's voice command can start playback of your app even when it's not running.
* <p>
- * To use this class, adding followings directly to your {@code AndroidManifest.xml}.
+ * To extend this class, adding followings directly to your {@code AndroidManifest.xml}.
* <pre>
* <service android:name="component_name_of_your_implementation" >
* <intent-filter>
- * <action android:name="android.media.session.MediaSessionService2" />
+ * <action android:name="android.media.MediaSessionService2" />
* </intent-filter>
* </service></pre>
* <p>
@@ -58,7 +58,7 @@
* <pre>
* <service android:name="component_name_of_your_implementation" >
* <intent-filter>
- * <action android:name="android.media.session.MediaSessionService2" />
+ * <action android:name="android.media.MediaSessionService2" />
* </intent-filter>
* <meta-data android:name="android.media.session"
* android:value="session_id"/>
@@ -120,8 +120,7 @@
* This is the interface name that a service implementing a session service should say that it
* support -- that is, this is the action it uses for its intent filter.
*/
- public static final String SERVICE_INTERFACE =
- "android.media.session.MediaSessionService2";
+ public static final String SERVICE_INTERFACE = "android.media.MediaSessionService2";
/**
* Name under which a MediaSessionService2 component publishes information about itself.
@@ -129,21 +128,13 @@
*/
public static final String SERVICE_META_DATA = "android.media.session";
- /**
- * Default notification channel ID used by {@link #onUpdateNotification(PlaybackState)}.
- *
- */
- public static final String DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID = "media_session_service";
-
- /**
- * Default notification channel ID used by {@link #onUpdateNotification(PlaybackState)}.
- *
- */
- public static final int DEFAULT_MEDIA_NOTIFICATION_ID = 1001;
-
public MediaSessionService2() {
super();
- mProvider = ApiLoader.getProvider(this).createMediaSessionService2(this);
+ mProvider = createProvider();
+ }
+
+ MediaSessionService2Provider createProvider() {
+ return ApiLoader.getProvider(this).createMediaSessionService2(this);
}
/**
@@ -168,32 +159,24 @@
* Service wouldn't run if {@code null} is returned or session's ID doesn't match with the
* expected ID that you've specified through the AndroidManifest.xml.
* <p>
- * This method will be call on the main thread.
+ * This method will be called on the main thread.
*
* @param sessionId session id written in the AndroidManifest.xml.
* @return a new session
* @see MediaSession2.Builder
* @see #getSession()
*/
- // TODO(jaewan): Replace this with onCreateSession(). Its sesssion callback will replace
- // this abstract method.
- // TODO(jaewan): Should we also include/documents notification listener access?
- // TODO(jaewan): Is term accepted/rejected correct? For permission, granted is more common.
- // TODO(jaewan): Return ConnectResult() that encapsulate supported action and player.
public @NonNull abstract MediaSession2 onCreateSession(String sessionId);
/**
* Called when the playback state of this session is changed, and notification needs update.
- * <p>
- * Default media style notification will be shown if you don't override this or return
- * {@code null}. Override this method to show your own notification UI.
+ * Override this method to show your own notification UI.
* <p>
* With the notification returned here, the service become foreground service when the playback
* is started. It becomes background service after the playback is stopped.
*
* @param state playback state
- * @return a {@link MediaNotification}. If it's {@code null}, default notification will be shown
- * instead.
+ * @return a {@link MediaNotification}. If it's {@code null}, notification wouldn't be shown.
*/
// TODO(jaewan): Also add metadata
public MediaNotification onUpdateNotification(PlaybackState state) {
diff --git a/media/java/android/media/PlaybackState2.java b/media/java/android/media/PlaybackState2.java
new file mode 100644
index 0000000..3740aea
--- /dev/null
+++ b/media/java/android/media/PlaybackState2.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Playback state for a {@link MediaPlayerBase}, to be shared between {@link MediaSession2} and
+ * {@link MediaController2}. This includes a playback state {@link #STATE_PLAYING},
+ * the current playback position and extra.
+ * @hide
+ */
+// TODO(jaewan): Move to updatable
+// TODO(jaewan): Add fromBundle() toBundle()
+public final class PlaybackState2 {
+ private static final String TAG = "PlaybackState2";
+
+ // TODO(jaewan): Replace states from MediaPlayer2
+ /**
+ * @hide
+ */
+ @IntDef({STATE_NONE, STATE_STOPPED, STATE_PREPARED, STATE_PAUSED, STATE_PLAYING,
+ STATE_FINISH, STATE_BUFFERING, STATE_ERROR})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface State {}
+
+ /**
+ * This is the default playback state and indicates that no media has been
+ * added yet, or the performer has been reset and has no content to play.
+ */
+ public final static int STATE_NONE = 0;
+
+ /**
+ * State indicating this item is currently stopped.
+ */
+ public final static int STATE_STOPPED = 1;
+
+ /**
+ * State indicating this item is currently prepared
+ */
+ public final static int STATE_PREPARED = 2;
+
+ /**
+ * State indicating this item is currently paused.
+ */
+ public final static int STATE_PAUSED = 3;
+
+ /**
+ * State indicating this item is currently playing.
+ */
+ public final static int STATE_PLAYING = 4;
+
+ /**
+ * State indicating the playback reaches the end of the item.
+ */
+ public final static int STATE_FINISH = 5;
+
+ /**
+ * State indicating this item is currently buffering and will begin playing
+ * when enough data has buffered.
+ */
+ public final static int STATE_BUFFERING = 6;
+
+ /**
+ * State indicating this item is currently in an error state. The error
+ * message should also be set when entering this state.
+ */
+ public final static int STATE_ERROR = 7;
+
+ /**
+ * Use this value for the position to indicate the position is not known.
+ */
+ public final static long PLAYBACK_POSITION_UNKNOWN = -1;
+
+ private final int mState;
+ private final long mPosition;
+ private final long mBufferedPosition;
+ private final float mSpeed;
+ private final CharSequence mErrorMessage;
+ private final long mUpdateTime;
+ private final long mActiveItemId;
+
+ private PlaybackState2(int state, long position, long updateTime, float speed,
+ long bufferedPosition, long activeItemId, CharSequence error) {
+ mState = state;
+ mPosition = position;
+ mSpeed = speed;
+ mUpdateTime = updateTime;
+ mBufferedPosition = bufferedPosition;
+ mActiveItemId = activeItemId;
+ mErrorMessage = error;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder bob = new StringBuilder("PlaybackState {");
+ bob.append("state=").append(mState);
+ bob.append(", position=").append(mPosition);
+ bob.append(", buffered position=").append(mBufferedPosition);
+ bob.append(", speed=").append(mSpeed);
+ bob.append(", updated=").append(mUpdateTime);
+ bob.append(", active item id=").append(mActiveItemId);
+ bob.append(", error=").append(mErrorMessage);
+ bob.append("}");
+ return bob.toString();
+ }
+
+ /**
+ * Get the current state of playback. One of the following:
+ * <ul>
+ * <li> {@link PlaybackState2#STATE_NONE}</li>
+ * <li> {@link PlaybackState2#STATE_STOPPED}</li>
+ * <li> {@link PlaybackState2#STATE_PLAYING}</li>
+ * <li> {@link PlaybackState2#STATE_PAUSED}</li>
+ * <li> {@link PlaybackState2#STATE_FAST_FORWARDING}</li>
+ * <li> {@link PlaybackState2#STATE_REWINDING}</li>
+ * <li> {@link PlaybackState2#STATE_BUFFERING}</li>
+ * <li> {@link PlaybackState2#STATE_ERROR}</li>
+ * <li> {@link PlaybackState2#STATE_CONNECTING}</li>
+ * <li> {@link PlaybackState2#STATE_SKIPPING_TO_PREVIOUS}</li>
+ * <li> {@link PlaybackState2#STATE_SKIPPING_TO_NEXT}</li>
+ * <li> {@link PlaybackState2#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
+ * </ul>
+ */
+ @State
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Get the current playback position in ms.
+ */
+ public long getPosition() {
+ return mPosition;
+ }
+
+ /**
+ * Get the current buffered position in ms. This is the farthest playback
+ * point that can be reached from the current position using only buffered
+ * content.
+ */
+ public long getBufferedPosition() {
+ return mBufferedPosition;
+ }
+
+ /**
+ * Get the current playback speed as a multiple of normal playback. This
+ * should be negative when rewinding. A value of 1 means normal playback and
+ * 0 means paused.
+ *
+ * @return The current speed of playback.
+ */
+ public float getPlaybackSpeed() {
+ return mSpeed;
+ }
+
+ /**
+ * Get a user readable error message. This should be set when the state is
+ * {@link PlaybackState2#STATE_ERROR}.
+ */
+ public CharSequence getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /**
+ * Get the elapsed real time at which position was last updated. If the
+ * position has never been set this will return 0;
+ *
+ * @return The last time the position was updated.
+ */
+ public long getLastPositionUpdateTime() {
+ return mUpdateTime;
+ }
+
+ /**
+ * Get the id of the currently active item in the playlist.
+ *
+ * @return The id of the currently active item in the queue
+ */
+ public long getCurrentPlaylistItemIndex() {
+ return mActiveItemId;
+ }
+}
\ No newline at end of file
diff --git a/media/java/android/media/Rating2.java b/media/java/android/media/Rating2.java
new file mode 100644
index 0000000..67e5e72
--- /dev/null
+++ b/media/java/android/media/Rating2.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.IntDef;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A class to encapsulate rating information used as content metadata.
+ * A rating is defined by its rating style (see {@link #RATING_HEART},
+ * {@link #RATING_THUMB_UP_DOWN}, {@link #RATING_3_STARS}, {@link #RATING_4_STARS},
+ * {@link #RATING_5_STARS} or {@link #RATING_PERCENTAGE}) and the actual rating value (which may
+ * be defined as "unrated"), both of which are defined when the rating instance is constructed
+ * through one of the factory methods.
+ * @hide
+ */
+// TODO(jaewan): Move this to updatable
+public final class Rating2 {
+ private static final String TAG = "Rating2";
+
+ private static final String KEY_STYLE = "android.media.rating2.style";
+ private static final String KEY_VALUE = "android.media.rating2.value";
+
+ /**
+ * @hide
+ */
+ @IntDef({RATING_NONE, RATING_HEART, RATING_THUMB_UP_DOWN, RATING_3_STARS, RATING_4_STARS,
+ RATING_5_STARS, RATING_PERCENTAGE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Style {}
+
+ /**
+ * @hide
+ */
+ @IntDef({RATING_3_STARS, RATING_4_STARS, RATING_5_STARS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StarStyle {}
+
+ /**
+ * Indicates a rating style is not supported. A Rating2 will never have this
+ * type, but can be used by other classes to indicate they do not support
+ * Rating2.
+ */
+ public final static int RATING_NONE = 0;
+
+ /**
+ * A rating style with a single degree of rating, "heart" vs "no heart". Can be used to
+ * indicate the content referred to is a favorite (or not).
+ */
+ public final static int RATING_HEART = 1;
+
+ /**
+ * A rating style for "thumb up" vs "thumb down".
+ */
+ public final static int RATING_THUMB_UP_DOWN = 2;
+
+ /**
+ * A rating style with 0 to 3 stars.
+ */
+ public final static int RATING_3_STARS = 3;
+
+ /**
+ * A rating style with 0 to 4 stars.
+ */
+ public final static int RATING_4_STARS = 4;
+
+ /**
+ * A rating style with 0 to 5 stars.
+ */
+ public final static int RATING_5_STARS = 5;
+
+ /**
+ * A rating style expressed as a percentage.
+ */
+ public final static int RATING_PERCENTAGE = 6;
+
+ private final static float RATING_NOT_RATED = -1.0f;
+
+ private final int mRatingStyle;
+
+ private final float mRatingValue;
+
+ private Rating2(@Style int ratingStyle, float rating) {
+ mRatingStyle = ratingStyle;
+ mRatingValue = rating;
+ }
+
+ @Override
+ public String toString() {
+ return "Rating2:style=" + mRatingStyle + " rating="
+ + (mRatingValue < 0.0f ? "unrated" : String.valueOf(mRatingValue));
+ }
+
+ /**
+ * Create an instance from bundle object, previoulsy created by {@link #toBundle()}
+ *
+ * @param bundle bundle
+ * @return new Rating2 instance
+ */
+ public static Rating2 fromBundle(Bundle bundle) {
+ return new Rating2(bundle.getInt(KEY_STYLE), bundle.getFloat(KEY_VALUE));
+ }
+
+ /**
+ * Return bundle for this object to share across the process.
+ * @return bundle of this object
+ */
+ public Bundle toBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putInt(KEY_STYLE, mRatingStyle);
+ bundle.putFloat(KEY_VALUE, mRatingValue);
+ return bundle;
+ }
+
+ /**
+ * Return a Rating2 instance with no rating.
+ * Create and return a new Rating2 instance with no rating known for the given
+ * rating style.
+ * @param ratingStyle one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN},
+ * {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS},
+ * or {@link #RATING_PERCENTAGE}.
+ * @return null if an invalid rating style is passed, a new Rating2 instance otherwise.
+ */
+ public static Rating2 newUnratedRating(@Style int ratingStyle) {
+ switch(ratingStyle) {
+ case RATING_HEART:
+ case RATING_THUMB_UP_DOWN:
+ case RATING_3_STARS:
+ case RATING_4_STARS:
+ case RATING_5_STARS:
+ case RATING_PERCENTAGE:
+ return new Rating2(ratingStyle, RATING_NOT_RATED);
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Return a Rating2 instance with a heart-based rating.
+ * Create and return a new Rating2 instance with a rating style of {@link #RATING_HEART},
+ * and a heart-based rating.
+ * @param hasHeart true for a "heart selected" rating, false for "heart unselected".
+ * @return a new Rating2 instance.
+ */
+ public static Rating2 newHeartRating(boolean hasHeart) {
+ return new Rating2(RATING_HEART, hasHeart ? 1.0f : 0.0f);
+ }
+
+ /**
+ * Return a Rating2 instance with a thumb-based rating.
+ * Create and return a new Rating2 instance with a {@link #RATING_THUMB_UP_DOWN}
+ * rating style, and a "thumb up" or "thumb down" rating.
+ * @param thumbIsUp true for a "thumb up" rating, false for "thumb down".
+ * @return a new Rating2 instance.
+ */
+ public static Rating2 newThumbRating(boolean thumbIsUp) {
+ return new Rating2(RATING_THUMB_UP_DOWN, thumbIsUp ? 1.0f : 0.0f);
+ }
+
+ /**
+ * Return a Rating2 instance with a star-based rating.
+ * Create and return a new Rating2 instance with one of the star-base rating styles
+ * and the given integer or fractional number of stars. Non integer values can for instance
+ * be used to represent an average rating value, which might not be an integer number of stars.
+ * @param starRatingStyle one of {@link #RATING_3_STARS}, {@link #RATING_4_STARS},
+ * {@link #RATING_5_STARS}.
+ * @param starRating a number ranging from 0.0f to 3.0f, 4.0f or 5.0f according to
+ * the rating style.
+ * @return null if the rating style is invalid, or the rating is out of range,
+ * a new Rating2 instance otherwise.
+ */
+ public static Rating2 newStarRating(@StarStyle int starRatingStyle, float starRating) {
+ float maxRating = -1.0f;
+ switch(starRatingStyle) {
+ case RATING_3_STARS:
+ maxRating = 3.0f;
+ break;
+ case RATING_4_STARS:
+ maxRating = 4.0f;
+ break;
+ case RATING_5_STARS:
+ maxRating = 5.0f;
+ break;
+ default:
+ Log.e(TAG, "Invalid rating style (" + starRatingStyle + ") for a star rating");
+ return null;
+ }
+ if ((starRating < 0.0f) || (starRating > maxRating)) {
+ Log.e(TAG, "Trying to set out of range star-based rating");
+ return null;
+ }
+ return new Rating2(starRatingStyle, starRating);
+ }
+
+ /**
+ * Return a Rating2 instance with a percentage-based rating.
+ * Create and return a new Rating2 instance with a {@link #RATING_PERCENTAGE}
+ * rating style, and a rating of the given percentage.
+ * @param percent the value of the rating
+ * @return null if the rating is out of range, a new Rating2 instance otherwise.
+ */
+ public static Rating2 newPercentageRating(float percent) {
+ if ((percent < 0.0f) || (percent > 100.0f)) {
+ Log.e(TAG, "Invalid percentage-based rating value");
+ return null;
+ } else {
+ return new Rating2(RATING_PERCENTAGE, percent);
+ }
+ }
+
+ /**
+ * Return whether there is a rating value available.
+ * @return true if the instance was not created with {@link #newUnratedRating(int)}.
+ */
+ public boolean isRated() {
+ return mRatingValue >= 0.0f;
+ }
+
+ /**
+ * Return the rating style.
+ * @return one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN},
+ * {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS},
+ * or {@link #RATING_PERCENTAGE}.
+ */
+ @Style
+ public int getRatingStyle() {
+ return mRatingStyle;
+ }
+
+ /**
+ * Return whether the rating is "heart selected".
+ * @return true if the rating is "heart selected", false if the rating is "heart unselected",
+ * if the rating style is not {@link #RATING_HEART} or if it is unrated.
+ */
+ public boolean hasHeart() {
+ if (mRatingStyle != RATING_HEART) {
+ return false;
+ } else {
+ return (mRatingValue == 1.0f);
+ }
+ }
+
+ /**
+ * Return whether the rating is "thumb up".
+ * @return true if the rating is "thumb up", false if the rating is "thumb down",
+ * if the rating style is not {@link #RATING_THUMB_UP_DOWN} or if it is unrated.
+ */
+ public boolean isThumbUp() {
+ if (mRatingStyle != RATING_THUMB_UP_DOWN) {
+ return false;
+ } else {
+ return (mRatingValue == 1.0f);
+ }
+ }
+
+ /**
+ * Return the star-based rating value.
+ * @return a rating value greater or equal to 0.0f, or a negative value if the rating style is
+ * not star-based, or if it is unrated.
+ */
+ public float getStarRating() {
+ switch (mRatingStyle) {
+ case RATING_3_STARS:
+ case RATING_4_STARS:
+ case RATING_5_STARS:
+ if (isRated()) {
+ return mRatingValue;
+ }
+ default:
+ return -1.0f;
+ }
+ }
+
+ /**
+ * Return the percentage-based rating value.
+ * @return a rating value greater or equal to 0.0f, or a negative value if the rating style is
+ * not percentage-based, or if it is unrated.
+ */
+ public float getPercentRating() {
+ if ((mRatingStyle != RATING_PERCENTAGE) || !isRated()) {
+ return -1.0f;
+ } else {
+ return mRatingValue;
+ }
+ }
+}
diff --git a/media/java/android/media/SessionToken.java b/media/java/android/media/SessionToken.java
index b470dea..53fc24a 100644
--- a/media/java/android/media/SessionToken.java
+++ b/media/java/android/media/SessionToken.java
@@ -42,12 +42,13 @@
// TODO(jaewan): Find better name for this (SessionToken or Session2Token)
public final class SessionToken {
@Retention(RetentionPolicy.SOURCE)
- @IntDef(value = {TYPE_SESSION, TYPE_SESSION_SERVICE})
+ @IntDef(value = {TYPE_SESSION, TYPE_SESSION_SERVICE, TYPE_LIBRARY_SERVICE})
public @interface TokenType {
}
public static final int TYPE_SESSION = 0;
public static final int TYPE_SESSION_SERVICE = 1;
+ public static final int TYPE_LIBRARY_SERVICE = 2;
private static final String KEY_TYPE = "android.media.token.type";
private static final String KEY_PACKAGE_NAME = "android.media.token.package_name";
@@ -73,6 +74,7 @@
* @hide
*/
// TODO(jaewan): UID is also needed.
+ // TODO(jaewan): Unhide
public SessionToken(@TokenType int type, @NonNull String packageName, @NonNull String id,
@Nullable String serviceName, @Nullable IMediaSession2 sessionBinder) {
// TODO(jaewan): Add sanity check.
diff --git a/media/java/android/media/update/MediaBrowser2Provider.java b/media/java/android/media/update/MediaBrowser2Provider.java
new file mode 100644
index 0000000..e48711d
--- /dev/null
+++ b/media/java/android/media/update/MediaBrowser2Provider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.update;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+public interface MediaBrowser2Provider extends MediaController2Provider {
+ void getBrowserRoot_impl(Bundle rootHints);
+
+ void subscribe_impl(String parentId, Bundle options);
+ void unsubscribe_impl(String parentId, Bundle options);
+
+ void getItem_impl(String mediaId);
+ void getChildren_impl(String parentId, int page, int pageSize, Bundle options);
+ void search_impl(String query, int page, int pageSize, Bundle extras);
+}
diff --git a/media/java/android/media/update/MediaControlView2Provider.java b/media/java/android/media/update/MediaControlView2Provider.java
index 83763b4..6b38c92 100644
--- a/media/java/android/media/update/MediaControlView2Provider.java
+++ b/media/java/android/media/update/MediaControlView2Provider.java
@@ -40,13 +40,8 @@
void show_impl(int timeout);
boolean isShowing_impl();
void hide_impl();
- void showCCButton_impl();
- boolean isPlaying_impl();
- int getCurrentPosition_impl();
- int getBufferPercentage_impl();
- boolean canPause_impl();
- boolean canSeekBackward_impl();
- boolean canSeekForward_impl();
void showSubtitle_impl();
void hideSubtitle_impl();
+ void setPrevNextListeners_impl(View.OnClickListener next, View.OnClickListener prev);
+ void setButtonVisibility_impl(int button, boolean visible);
}
diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java
index b15d6db..8f5a643 100644
--- a/media/java/android/media/update/MediaController2Provider.java
+++ b/media/java/android/media/update/MediaController2Provider.java
@@ -16,13 +16,49 @@
package android.media.update;
+import android.app.PendingIntent;
+import android.media.MediaController2.PlaybackInfo;
+import android.media.MediaItem2;
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.PlaylistParam;
+import android.media.PlaybackState2;
+import android.media.Rating2;
import android.media.SessionToken;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+import java.util.List;
/**
* @hide
*/
-public interface MediaController2Provider extends MediaPlayerBaseProvider {
- void release_impl();
+public interface MediaController2Provider extends TransportControlProvider {
+ void close_impl();
SessionToken getSessionToken_impl();
boolean isConnected_impl();
+
+ PendingIntent getSessionActivity_impl();
+ int getRatingType_impl();
+
+ void setVolumeTo_impl(int value, int flags);
+ void adjustVolume_impl(int direction, int flags);
+ PlaybackInfo getPlaybackInfo_impl();
+
+ void prepareFromUri_impl(Uri uri, Bundle extras);
+ void prepareFromSearch_impl(String query, Bundle extras);
+ void prepareMediaId_impl(String mediaId, Bundle extras);
+ void playFromSearch_impl(String query, Bundle extras);
+ void playFromUri_impl(String uri, Bundle extras);
+ void playFromMediaId_impl(String mediaId, Bundle extras);
+
+ void setRating_impl(Rating2 rating);
+ void sendCustomCommand_impl(Command command, Bundle args, ResultReceiver cb);
+ List<MediaItem2> getPlaylist_impl();
+
+ void removePlaylistItem_impl(MediaItem2 index);
+ void addPlaylistItem_impl(int index, MediaItem2 item);
+
+ PlaylistParam getPlaylistParam_impl();
+ PlaybackState2 getPlaybackState_impl();
}
diff --git a/media/java/android/media/update/MediaLibraryService2Provider.java b/media/java/android/media/update/MediaLibraryService2Provider.java
new file mode 100644
index 0000000..dac5784
--- /dev/null
+++ b/media/java/android/media/update/MediaLibraryService2Provider.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.update;
+
+import android.media.MediaSession2.ControllerInfo;
+import android.os.Bundle; /**
+ * @hide
+ */
+public interface MediaLibraryService2Provider extends MediaSessionService2Provider {
+ // Nothing new for now
+
+ interface MediaLibrarySessionProvider extends MediaSession2Provider {
+ void notifyChildrenChanged_impl(ControllerInfo controller, String parentId, Bundle options);
+ void notifyChildrenChanged_impl(String parentId, Bundle options);
+ }
+}
diff --git a/media/java/android/media/update/MediaSession2Provider.java b/media/java/android/media/update/MediaSession2Provider.java
index 36fd182..511686d 100644
--- a/media/java/android/media/update/MediaSession2Provider.java
+++ b/media/java/android/media/update/MediaSession2Provider.java
@@ -16,20 +16,44 @@
package android.media.update;
+import android.media.AudioAttributes;
+import android.media.MediaItem2;
import android.media.MediaPlayerBase;
+import android.media.MediaSession2;
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandButton;
+import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.PlaylistParam;
import android.media.SessionToken;
+import android.media.VolumeProvider;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
import java.util.List;
/**
* @hide
*/
-public interface MediaSession2Provider extends MediaPlayerBaseProvider {
- void setPlayer_impl(MediaPlayerBase player) throws IllegalArgumentException;
+public interface MediaSession2Provider extends TransportControlProvider {
+ void close_impl();
+ void setPlayer_impl(MediaPlayerBase player);
+ void setPlayer_impl(MediaPlayerBase player, VolumeProvider volumeProvider);
MediaPlayerBase getPlayer_impl();
SessionToken getToken_impl();
List<ControllerInfo> getConnectedControllers_impl();
+ void setCustomLayout_impl(ControllerInfo controller, List<CommandButton> layout);
+ void setAudioAttributes_impl(AudioAttributes attributes);
+ void setAudioFocusRequest_impl(int focusGain);
+
+ void setAllowedCommands_impl(ControllerInfo controller, CommandGroup commands);
+ void notifyMetadataChanged_impl();
+ void sendCustomCommand_impl(ControllerInfo controller, Command command, Bundle args,
+ ResultReceiver receiver);
+ void sendCustomCommand_impl(Command command, Bundle args);
+ void setPlaylist_impl(List<MediaItem2> playlist, MediaSession2.PlaylistParam param);
/**
* @hide
diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java
index 91c9c66..64968d6 100644
--- a/media/java/android/media/update/StaticProvider.java
+++ b/media/java/android/media/update/StaticProvider.java
@@ -17,13 +17,24 @@
package android.media.update;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.Context;
import android.media.IMediaSession2Callback;
+import android.media.MediaBrowser2;
+import android.media.MediaBrowser2.BrowserCallback;
import android.media.MediaController2;
+import android.media.MediaController2.ControllerCallback;
+import android.media.MediaLibraryService2;
+import android.media.MediaLibraryService2.MediaLibrarySession;
+import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
import android.media.MediaPlayerBase;
import android.media.MediaSession2;
+import android.media.MediaSession2.SessionCallback;
import android.media.MediaSessionService2;
import android.media.SessionToken;
+import android.media.VolumeProvider;
+import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
+import android.media.update.MediaSession2Provider.ControllerInfoProvider;
import android.util.AttributeSet;
import android.widget.MediaControlView2;
import android.widget.VideoView2;
@@ -47,13 +58,24 @@
@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes);
MediaSession2Provider createMediaSession2(MediaSession2 mediaSession2, Context context,
- MediaPlayerBase player, String id, MediaSession2.SessionCallback callback);
- MediaSession2Provider.ControllerInfoProvider createMediaSession2ControllerInfoProvider(
+ MediaPlayerBase player, String id, SessionCallback callback,
+ VolumeProvider volumeProvider, int ratingType,
+ PendingIntent sessionActivity);
+ ControllerInfoProvider createMediaSession2ControllerInfoProvider(
MediaSession2.ControllerInfo instance, Context context, int uid, int pid,
String packageName, IMediaSession2Callback callback);
MediaController2Provider createMediaController2(
MediaController2 instance, Context context, SessionToken token,
- MediaController2.ControllerCallback callback, Executor executor);
+ ControllerCallback callback, Executor executor);
+ MediaBrowser2Provider createMediaBrowser2(
+ MediaBrowser2 instance, Context context, SessionToken token,
+ BrowserCallback callback, Executor executor);
MediaSessionService2Provider createMediaSessionService2(
MediaSessionService2 instance);
+ MediaSessionService2Provider createMediaLibraryService2(
+ MediaLibraryService2 instance);
+ MediaLibrarySessionProvider createMediaLibraryService2MediaLibrarySession(
+ MediaLibrarySession instance, Context context, MediaPlayerBase player, String id,
+ MediaLibrarySessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+ PendingIntent sessionActivity);
}
diff --git a/media/java/android/media/update/MediaPlayerBaseProvider.java b/media/java/android/media/update/TransportControlProvider.java
similarity index 77%
rename from media/java/android/media/update/MediaPlayerBaseProvider.java
rename to media/java/android/media/update/TransportControlProvider.java
index 5b13e74..5217a9d 100644
--- a/media/java/android/media/update/MediaPlayerBaseProvider.java
+++ b/media/java/android/media/update/TransportControlProvider.java
@@ -23,14 +23,17 @@
/**
* @hide
*/
-public interface MediaPlayerBaseProvider {
+// TODO(jaewan): SystemApi
+public interface TransportControlProvider {
void play_impl();
void pause_impl();
void stop_impl();
void skipToPrevious_impl();
void skipToNext_impl();
- PlaybackState getPlaybackState_impl();
- void addPlaybackListener_impl(MediaPlayerBase.PlaybackListener listener, Handler handler);
- void removePlaybackListener_impl(MediaPlayerBase.PlaybackListener listener);
+ void prepare_impl();
+ void fastForward_impl();
+ void rewind_impl();
+ void seekTo_impl(long pos);
+ void setCurrentPlaylistItem_impl(int index);
}
diff --git a/media/java/android/media/update/VideoView2Provider.java b/media/java/android/media/update/VideoView2Provider.java
index b7a24e5..416ea98 100644
--- a/media/java/android/media/update/VideoView2Provider.java
+++ b/media/java/android/media/update/VideoView2Provider.java
@@ -17,10 +17,12 @@
package android.media.update;
import android.media.AudioAttributes;
+import android.media.MediaPlayerBase;
import android.net.Uri;
import android.widget.MediaControlView2;
import android.widget.VideoView2;
+import java.util.List;
import java.util.Map;
/**
@@ -50,10 +52,12 @@
int getAudioSessionId_impl();
void showSubtitle_impl();
void hideSubtitle_impl();
+ void setFullScreen_impl(boolean fullScreen);
void setSpeed_impl(float speed);
float getSpeed_impl();
void setAudioFocusRequest_impl(int focusGain);
void setAudioAttributes_impl(AudioAttributes attributes);
+ void setRouteAttributes_impl(List<String> routeCategories, MediaPlayerBase player);
void setVideoPath_impl(String path);
void setVideoURI_impl(Uri uri);
void setVideoURI_impl(Uri uri, Map<String, String> headers);
@@ -65,4 +69,5 @@
void setOnErrorListener_impl(VideoView2.OnErrorListener l);
void setOnInfoListener_impl(VideoView2.OnInfoListener l);
void setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l);
+ void setFullScreenChangedListener_impl(VideoView2.OnFullScreenChangedListener l);
}
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 1dddbee..95b07f1 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -19,6 +19,7 @@
#include <utils/Log.h>
#include "android_media_MediaDrm.h"
+#include "android_media_MediaMetricsJNI.h"
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/Log.h"
@@ -113,6 +114,8 @@
jint kKeyRequestTypeInitial;
jint kKeyRequestTypeRenewal;
jint kKeyRequestTypeRelease;
+ jint kKeyRequestTypeNone;
+ jint kKeyRequestTypeUpdate;
} gKeyRequestTypes;
struct CertificateTypes {
@@ -691,6 +694,10 @@
gKeyRequestTypes.kKeyRequestTypeRenewal = env->GetStaticIntField(clazz, field);
GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RELEASE", "I");
gKeyRequestTypes.kKeyRequestTypeRelease = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_NONE", "I");
+ gKeyRequestTypes.kKeyRequestTypeNone = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_UPDATE", "I");
+ gKeyRequestTypes.kKeyRequestTypeUpdate = env->GetStaticIntField(clazz, field);
FIND_CLASS(clazz, "android/media/MediaDrm$ProvisionRequest");
GET_FIELD_ID(gFields.provisionRequest.data, clazz, "mData", "[B");
@@ -921,6 +928,15 @@
env->SetIntField(keyObj, gFields.keyRequest.requestType,
gKeyRequestTypes.kKeyRequestTypeRelease);
break;
+ case DrmPlugin::kKeyRequestType_None:
+ env->SetIntField(keyObj, gFields.keyRequest.requestType,
+ gKeyRequestTypes.kKeyRequestTypeNone);
+ break;
+ case DrmPlugin::kKeyRequestType_Update:
+ env->SetIntField(keyObj, gFields.keyRequest.requestType,
+ gKeyRequestTypes.kKeyRequestTypeUpdate);
+ break;
+
default:
throwStateException(env, "DRM plugin failure: unknown key request type",
ERROR_DRM_UNKNOWN);
@@ -1603,6 +1619,31 @@
return match;
}
+static jobject
+android_media_MediaDrm_native_getMetrics(JNIEnv *env, jobject thiz)
+{
+ sp<IDrm> drm = GetDrm(env, thiz);
+ if (drm == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "MediaDrm obj is null");
+ return NULL;
+ }
+
+ // Retrieve current metrics snapshot from drm.
+ MediaAnalyticsItem item ;
+ status_t err = drm->getMetrics(&item);
+ if (err != OK) {
+ ALOGE("getMetrics failed: %d", (int)err);
+ return (jobject) NULL;
+ }
+
+ jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, &item, NULL);
+ if (mybundle == NULL) {
+ ALOGE("getMetrics metric conversion failed");
+ }
+
+ return mybundle;
+}
static jbyteArray android_media_MediaDrm_signRSANative(
JNIEnv *env, jobject /* thiz */, jobject jdrm, jbyteArray jsessionId,
@@ -1739,6 +1780,9 @@
{ "signRSANative", "(Landroid/media/MediaDrm;[BLjava/lang/String;[B[B)[B",
(void *)android_media_MediaDrm_signRSANative },
+
+ { "getMetricsNative", "()Landroid/os/PersistableBundle;",
+ (void *)android_media_MediaDrm_native_getMetrics },
};
int register_android_media_Drm(JNIEnv *env) {
diff --git a/nfc-extras/tests/Android.mk b/nfc-extras/tests/Android.mk
index 34d6508..51396d3 100644
--- a/nfc-extras/tests/Android.mk
+++ b/nfc-extras/tests/Android.mk
@@ -19,7 +19,7 @@
LOCAL_MODULE_TAGS := tests
LOCAL_JAVA_LIBRARIES := \
- android.test.runner \
+ android.test.runner.stubs \
com.android.nfc_extras \
android.test.base.stubs
diff --git a/packages/MtpDocumentsProvider/AndroidManifest.xml b/packages/MtpDocumentsProvider/AndroidManifest.xml
index 8d79f62..c0a59b3 100644
--- a/packages/MtpDocumentsProvider/AndroidManifest.xml
+++ b/packages/MtpDocumentsProvider/AndroidManifest.xml
@@ -3,6 +3,7 @@
package="com.android.mtp"
android:sharedUserId="android.media">
<uses-feature android:name="android.hardware.usb.host" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.MANAGE_USB" />
<application android:label="@string/app_label">
<provider
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 3c46d99..d001e66 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -21,10 +21,9 @@
import android.os.UserManager;
import android.print.PrintManager;
import android.provider.Settings;
-
import com.android.internal.util.UserIcons;
import com.android.settingslib.drawable.UserIconDrawable;
-
+import com.android.settingslib.wrapper.LocationManagerWrapper;
import java.text.NumberFormat;
public class Utils {
@@ -45,6 +44,24 @@
com.android.internal.R.drawable.ic_wifi_signal_4
};
+ public static void updateLocationEnabled(Context context, boolean enabled, int userId) {
+ Intent intent = new Intent(LocationManager.MODE_CHANGING_ACTION);
+
+ final int oldMode = Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF, userId);
+ final int newMode = enabled
+ ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY
+ : Settings.Secure.LOCATION_MODE_OFF;
+ intent.putExtra(CURRENT_MODE_KEY, oldMode);
+ intent.putExtra(NEW_MODE_KEY, newMode);
+ context.sendBroadcastAsUser(
+ intent, UserHandle.of(userId), android.Manifest.permission.WRITE_SECURE_SETTINGS);
+ LocationManager locationManager =
+ (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+ LocationManagerWrapper wrapper = new LocationManagerWrapper(locationManager);
+ wrapper.setLocationEnabledForUser(enabled, UserHandle.of(userId));
+ }
+
public static boolean updateLocationMode(Context context, int oldMode, int newMode, int userId) {
Intent intent = new Intent(LocationManager.MODE_CHANGING_ACTION);
intent.putExtra(CURRENT_MODE_KEY, oldMode);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wrapper/LocationManagerWrapper.java b/packages/SettingsLib/src/com/android/settingslib/wrapper/LocationManagerWrapper.java
new file mode 100644
index 0000000..1a268a6
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wrapper/LocationManagerWrapper.java
@@ -0,0 +1,64 @@
+/*
+ * 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.wrapper;
+
+import android.location.LocationManager;
+import android.os.UserHandle;
+
+/**
+ * This class replicates some methods of android.location.LocationManager that are new and not
+ * yet available in our current version of Robolectric. It provides a thin wrapper to call the real
+ * methods in production and a mock in tests.
+ */
+public class LocationManagerWrapper {
+
+ private LocationManager mLocationManager;
+
+ public LocationManagerWrapper(LocationManager locationManager) {
+ mLocationManager = locationManager;
+ }
+
+ /** Returns the real {@code LocationManager} object */
+ public LocationManager getLocationManager() {
+ return mLocationManager;
+ }
+
+ /** Wraps {@code LocationManager.isProviderEnabled} method */
+ public boolean isProviderEnabled(String provider) {
+ return mLocationManager.isProviderEnabled(provider);
+ }
+
+ /** Wraps {@code LocationManager.setProviderEnabledForUser} method */
+ public void setProviderEnabledForUser(String provider, boolean enabled, UserHandle userHandle) {
+ mLocationManager.setProviderEnabledForUser(provider, enabled, userHandle);
+ }
+
+ /** Wraps {@code LocationManager.isLocationEnabled} method */
+ public boolean isLocationEnabled() {
+ return mLocationManager.isLocationEnabled();
+ }
+
+ /** Wraps {@code LocationManager.isLocationEnabledForUser} method */
+ public boolean isLocationEnabledForUser(UserHandle userHandle) {
+ return mLocationManager.isLocationEnabledForUser(userHandle);
+ }
+
+ /** Wraps {@code LocationManager.setLocationEnabledForUser} method */
+ public void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) {
+ mLocationManager.setLocationEnabledForUser(enabled, userHandle);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 976bbee..327c1c8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -15,28 +15,8 @@
*/
package com.android.settingslib;
-import android.app.ActivityManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.location.LocationManager;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import java.util.HashMap;
-import java.util.Map;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatcher;
-import org.mockito.ArgumentMatchers;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
@@ -44,7 +24,28 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
import android.content.res.Resources;
+import android.location.LocationManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.text.TextUtils;
+import com.android.settingslib.wrapper.LocationManagerWrapper;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowSettings;
@@ -53,7 +54,9 @@
@Config(
manifest = TestConfig.MANIFEST_PATH,
sdk = TestConfig.SDK_VERSION,
- shadows = {UtilsTest.ShadowSecure.class})
+ shadows = {
+ UtilsTest.ShadowSecure.class,
+ UtilsTest.ShadowLocationManagerWrapper.class})
public class UtilsTest {
private static final double[] TEST_PERCENTAGES = {0, 0.4, 0.5, 0.6, 49, 49.3, 49.8, 50, 100};
private static final String PERCENTAGE_0 = "0%";
@@ -63,10 +66,14 @@
private static final String PERCENTAGE_100 = "100%";
private Context mContext;
+ @Mock
+ private LocationManager mLocationManager;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
+ when(mContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager);
ShadowSecure.reset();
}
@@ -86,6 +93,17 @@
}
@Test
+ public void testUpdateLocationEnabled_sendBroadcast() {
+ int currentUserId = ActivityManager.getCurrentUser();
+ Utils.updateLocationEnabled(mContext, true, currentUserId);
+
+ verify(mContext).sendBroadcastAsUser(
+ argThat(actionMatches(LocationManager.MODE_CHANGING_ACTION)),
+ ArgumentMatchers.eq(UserHandle.of(currentUserId)),
+ ArgumentMatchers.eq(WRITE_SECURE_SETTINGS));
+ }
+
+ @Test
public void testFormatPercentage_RoundTrue_RoundUpIfPossible() {
final String[] expectedPercentages = {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1,
PERCENTAGE_1, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_50,
@@ -137,8 +155,26 @@
return true;
}
+ @Implementation
+ public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {
+ if (map.containsKey(name)) {
+ return map.get(name);
+ } else {
+ return def;
+ }
+ }
+
public static void reset() {
map.clear();
}
}
+
+ @Implements(value = LocationManagerWrapper.class)
+ public static class ShadowLocationManagerWrapper {
+
+ @Implementation
+ public void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) {
+ // Do nothing
+ }
+ }
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index c7ba4d6..dd89df1 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -35,6 +35,7 @@
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.provider.Settings;
+import android.provider.SettingsValidators.Validator;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.BackupUtils;
@@ -155,6 +156,9 @@
new ArraySet<String>(Arrays.asList(new String[] {
KEY_NETWORK_POLICIES,
KEY_WIFI_NEW_CONFIG,
+ KEY_SYSTEM,
+ KEY_SECURE,
+ KEY_GLOBAL,
}));
private SettingsHelper mSettingsHelper;
@@ -223,6 +227,15 @@
Log.d(TAG, "onRestore(): appVersionCode: " + appVersionCode
+ "; Build.VERSION.SDK_INT: " + Build.VERSION.SDK_INT);
}
+
+ boolean overrideRestoreAnyVersion = Settings.Global.getInt(getContentResolver(),
+ Settings.Global.OVERRIDE_SETTINGS_PROVIDER_RESTORE_ANY_VERSION, 0) == 1;
+ if ((appVersionCode > Build.VERSION.SDK_INT) && overrideRestoreAnyVersion) {
+ Log.w(TAG, "Ignoring restore from API" + appVersionCode + " to API"
+ + Build.VERSION.SDK_INT + " due to settings flag override.");
+ return;
+ }
+
// versionCode of com.android.providers.settings corresponds to SDK_INT
mRestoredFromSdkInt = appVersionCode;
@@ -571,15 +584,19 @@
// Figure out the white list and redirects to the global table. We restore anything
// in either the backup whitelist or the legacy-restore whitelist for this table.
final String[] whitelist;
+ Map<String, Validator> validators = null;
if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
whitelist = concat(Settings.Secure.SETTINGS_TO_BACKUP,
Settings.Secure.LEGACY_RESTORE_SETTINGS);
+ validators = Settings.Secure.VALIDATORS;
} else if (contentUri.equals(Settings.System.CONTENT_URI)) {
whitelist = concat(Settings.System.SETTINGS_TO_BACKUP,
Settings.System.LEGACY_RESTORE_SETTINGS);
+ validators = Settings.System.VALIDATORS;
} else if (contentUri.equals(Settings.Global.CONTENT_URI)) {
whitelist = concat(Settings.Global.SETTINGS_TO_BACKUP,
Settings.Global.LEGACY_RESTORE_SETTINGS);
+ validators = Settings.Global.VALIDATORS;
} else {
throw new IllegalArgumentException("Unknown URI: " + contentUri);
}
@@ -627,6 +644,13 @@
continue;
}
+ // only restore the settings that have valid values
+ if (!isValidSettingValue(key, value, validators)) {
+ Log.w(TAG, "Attempted restore of " + key + " setting, but its value didn't pass"
+ + " validation, value: " + value);
+ continue;
+ }
+
final Uri destination = (movedToGlobal != null && movedToGlobal.contains(key))
? Settings.Global.CONTENT_URI
: contentUri;
@@ -639,6 +663,15 @@
}
}
+ private boolean isValidSettingValue(String key, String value,
+ Map<String, Validator> validators) {
+ if (key == null || validators == null) {
+ return false;
+ }
+ Validator validator = validators.get(key);
+ return (validator != null) && validator.validate(value);
+ }
+
private final String[] concat(String[] first, @Nullable String[] second) {
if (second == null || second.length == 0) {
return first;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index fc765f4..cfd33a1 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -16,6 +16,7 @@
package com.android.providers.settings;
+import android.os.Process;
import com.android.internal.R;
import com.android.internal.app.LocalePicker;
import com.android.internal.annotations.VisibleForTesting;
@@ -288,12 +289,12 @@
}
final String GPS = LocationManager.GPS_PROVIDER;
boolean enabled =
- GPS.equals(value) ||
+ GPS.equals(value) ||
value.startsWith(GPS + ",") ||
value.endsWith("," + GPS) ||
value.contains("," + GPS + ",");
- Settings.Secure.setLocationProviderEnabled(
- mContext.getContentResolver(), GPS, enabled);
+ LocationManager lm = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+ lm.setProviderEnabledForUser(GPS, enabled, Process.myUserHandle());
}
private void setSoundEffects(boolean enable) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 5a75681..d556db4 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1120,8 +1120,8 @@
Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
GlobalSettingsProto.NOTIFICATION_SNOOZE_OPTIONS);
dumpSetting(s, p,
- Settings.Global.ZRAM_ENABLED,
- GlobalSettingsProto.ZRAM_ENABLED);
+ Settings.Global.ZRAM_ENABLED,
+ GlobalSettingsProto.ZRAM_ENABLED);
dumpSetting(s, p,
Settings.Global.ENABLE_SMART_REPLIES_IN_NOTIFICATIONS,
GlobalSettingsProto.ENABLE_SMART_REPLIES_IN_NOTIFICATIONS);
@@ -1129,8 +1129,14 @@
Settings.Global.SHOW_FIRST_CRASH_DIALOG,
GlobalSettingsProto.SHOW_FIRST_CRASH_DIALOG);
dumpSetting(s, p,
- Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED,
- GlobalSettingsProto.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED);
+ Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED,
+ GlobalSettingsProto.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG,
+ GlobalSettingsProto.SHOW_RESTART_IN_CRASH_DIALOG);
+ dumpSetting(s, p,
+ Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG,
+ GlobalSettingsProto.SHOW_MUTE_IN_CRASH_DIALOG);
}
/** Dump a single {@link SettingsState.Setting} to a proto buf */
@@ -1223,9 +1229,6 @@
dumpSetting(s, p,
Settings.Secure.LOCATION_MODE,
SecureSettingsProto.LOCATION_MODE);
- dumpSetting(s, p,
- Settings.Secure.LOCATION_PREVIOUS_MODE,
- SecureSettingsProto.LOCATION_PREVIOUS_MODE);
// Settings.Secure.LOCK_BIOMETRIC_WEAK_FLAGS intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
@@ -1758,6 +1761,9 @@
dumpSetting(s, p,
Settings.Secure.BACKUP_MANAGER_CONSTANTS,
SecureSettingsProto.BACKUP_MANAGER_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Secure.BLUETOOTH_ON_WHILE_DRIVING,
+ SecureSettingsProto.BLUETOOTH_ON_WHILE_DRIVING);
}
private static void dumpProtoSystemSettingsLocked(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 2a697b8..b7d6da4 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1647,6 +1647,15 @@
restriction = UserManager.DISALLOW_AIRPLANE_MODE;
break;
+ case Settings.Secure.DOZE_ENABLED:
+ case Settings.Secure.DOZE_ALWAYS_ON:
+ case Settings.Secure.DOZE_PULSE_ON_PICK_UP:
+ case Settings.Secure.DOZE_PULSE_ON_LONG_PRESS:
+ case Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP:
+ if ("0".equals(value)) return false;
+ restriction = UserManager.DISALLOW_AMBIENT_DISPLAY;
+ break;
+
default:
if (setting != null && setting.startsWith(Settings.Global.DATA_ROAMING)) {
if ("0".equals(value)) return false;
@@ -2892,11 +2901,14 @@
for (int i = 0; i < users.size(); i++) {
final int userId = users.get(i).id;
+ // Do we have to increment the generation for users that are not running?
+ // Yeah let's assume so...
+ final int key = makeKey(SETTINGS_TYPE_SECURE, userId);
+ mGenerationRegistry.incrementGeneration(key);
+
if (!mUserManager.isUserRunning(UserHandle.of(userId))) {
continue;
}
-
- final int key = makeKey(SETTINGS_TYPE_GLOBAL, userId);
final Uri uri = getNotificationUriFor(key, Secure.LOCATION_PROVIDERS_ALLOWED);
mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 64b2ae6..79299aa 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -44,6 +44,7 @@
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
<uses-permission android:name="android.permission.MANAGE_USB" />
<uses-permission android:name="android.permission.USE_RESERVED_DISK" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- System tool permissions granted to the shell. -->
<uses-permission android:name="android.permission.REAL_GET_TASKS" />
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 73fcdd7..251ae2d 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -27,7 +27,12 @@
LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
+RELATIVE_FINGERPRINT_PATH := ../../core/java/android/hardware/fingerprint
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-Iaidl-files-under, src) \
+ $(call all-Iaidl-files-under, $(RELATIVE_FINGERPRINT_PATH))
LOCAL_STATIC_ANDROID_LIBRARIES := \
SystemUIPluginLib \
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4b2e62c..0b6e11b 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -89,6 +89,7 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" />
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+ <uses-permission android:name="android.permission.START_ACTIVITY_AS_CALLER" />
<uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
<uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
@@ -555,6 +556,22 @@
</intent-filter>
</activity>
+ <activity android:name=".chooser.ChooserActivity"
+ android:theme="@*android:style/Theme.NoDisplay"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true"
+ android:documentLaunchMode="never"
+ android:relinquishTaskIdentity="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ android:process=":ui"
+ android:visibleToInstantApps="true">
+ <intent-filter>
+ <action android:name="android.intent.action.CHOOSER_UI" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.VOICE" />
+ </intent-filter>
+ </activity>
+
<!-- Doze with notifications, run in main sysui process for every user -->
<service
android:name=".doze.DozeService"
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_bg.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_bg.xml
new file mode 100644
index 0000000..4a77af9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_bg.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/fingerprint_dialog_bg_color" />
+ <corners android:radius="1dp"
+ android:topLeftRadius="16dp"
+ android:topRightRadius="16dp"
+ android:bottomLeftRadius="0dp"
+ android:bottomRightRadius="0dp"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_icon.xml b/packages/SystemUI/res/drawable/fingerprint_icon.xml
new file mode 100644
index 0000000..76a86ae
--- /dev/null
+++ b/packages/SystemUI/res/drawable/fingerprint_icon.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="60dp"
+ android:height="60dp"
+ android:viewportWidth="60"
+ android:viewportHeight="60">
+
+ <path
+ android:fillColor="#1A73E8"
+ android:fillType="evenOdd"
+ android:strokeWidth="1"
+ android:pathData="M 30 0 C 46.5685424949 0 60 13.4314575051 60 30 C 60 46.5685424949 46.5685424949 60 30 60 C 13.4314575051 60 0 46.5685424949 0 30 C 0 13.4314575051 13.4314575051 0 30 0 Z" />
+ <group
+ android:translateX="17.727273"
+ android:translateY="16.363636">
+ <path
+ android:fillColor="#FFFFFF"
+ android:strokeWidth="1"
+ android:pathData="M20.3065726,3.44516129 C20.1974817,3.44516129 20.0883908,3.41788856
+19.9929362,3.36334311 C17.3747544,2.01334311 15.111118,1.44061584
+12.3974817,1.44061584 C9.69748166,1.44061584 7.1338453,2.08152493
+4.80202711,3.36334311 C4.47475439,3.54061584 4.06566348,3.41788856
+3.87475439,3.09061584 C3.69748166,2.76334311 3.82020893,2.34061584
+4.14748166,2.16334311 C6.6838453,0.786070381 9.46566348,0.0769794721
+12.3974817,0.0769794721 C15.3020271,0.0769794721 17.8383908,0.717888563
+20.6202089,2.14970674 C20.961118,2.32697947 21.0838453,2.73607038
+20.9065726,3.06334311 C20.7838453,3.30879765 20.5520271,3.44516129
+20.3065726,3.44516129 L20.3065726,3.44516129 Z M0.792936205,10.6042522
+C0.656572568,10.6042522 0.520208932,10.5633431 0.397481659,10.4815249
+C0.0838452956,10.2633431 0.0156634774,9.84061584 0.233845296,9.52697947
+C1.5838453,7.61788856 3.30202711,6.11788856 5.34748166,5.06788856
+C9.62929984,2.85879765 15.111118,2.84516129 19.4065726,5.0542522
+C21.4520271,6.1042522 23.1702089,7.59061584 24.5202089,9.48607038
+C24.7383908,9.78607038 24.6702089,10.222434 24.3565726,10.4406158
+C24.0429362,10.6587977 23.6202089,10.5906158 23.4020271,10.2769795
+C22.1747544,8.55879765 20.6202089,7.20879765 18.7792998,6.26788856
+C14.8656635,4.26334311 9.86111802,4.26334311 5.96111802,6.28152493
+C4.10657257,7.23607038 2.55202711,8.59970674 1.32475439,10.3178886
+C1.21566348,10.5087977 1.01111802,10.6042522 0.792936205,10.6042522
+L0.792936205,10.6042522 Z M9.31566348,27.0633431 C9.13839075,27.0633431
+8.96111802,26.9951613 8.83839075,26.8587977 C7.65202711,25.672434
+7.01111802,24.9087977 6.09748166,23.2587977 C5.15657257,21.5815249
+4.66566348,19.5360704 4.66566348,17.3406158 C4.66566348,13.2906158
+8.12929984,9.99061584 12.3838453,9.99061584 C16.6383908,9.99061584
+20.1020271,13.2906158 20.1020271,17.3406158 C20.1020271,17.722434
+19.8020271,18.022434 19.4202089,18.022434 C19.0383908,18.022434
+18.7383908,17.722434 18.7383908,17.3406158 C18.7383908,14.0406158
+15.8883908,11.3542522 12.3838453,11.3542522 C8.87929984,11.3542522
+6.02929984,14.0406158 6.02929984,17.3406158 C6.02929984,19.3042522
+6.46566348,21.1178886 7.29748166,22.5906158 C8.17020893,24.1587977
+8.77020893,24.8269795 9.82020893,25.8906158 C10.0792998,26.1633431
+10.0792998,26.5860704 9.82020893,26.8587977 C9.67020893,26.9951613
+9.4929362,27.0633431 9.31566348,27.0633431 Z M19.0929362,24.5406158
+C17.4702089,24.5406158 16.0383908,24.1315249 14.8656635,23.3269795
+C12.8338453,21.9497067 11.6202089,19.7133431 11.6202089,17.3406158
+C11.6202089,16.9587977 11.9202089,16.6587977 12.3020271,16.6587977
+C12.6838453,16.6587977 12.9838453,16.9587977 12.9838453,17.3406158
+C12.9838453,19.2633431 13.9656635,21.0769795 15.6292998,22.1951613
+C16.5974817,22.8497067 17.7292998,23.1633431 19.0929362,23.1633431
+C19.4202089,23.1633431 19.9656635,23.122434 20.511118,23.0269795
+C20.8792998,22.9587977 21.2338453,23.2042522 21.3020271,23.5860704
+C21.3702089,23.9542522 21.1247544,24.3087977 20.7429362,24.3769795
+C19.9656635,24.5269795 19.2838453,24.5406158 19.0929362,24.5406158
+L19.0929362,24.5406158 Z M16.3520271,27.3497067 C16.2974817,27.3497067
+16.2292998,27.3360704 16.1747544,27.322434 C14.0065726,26.722434
+12.5883908,25.9178886 11.1020271,24.4587977 C9.1929362,22.5633431
+8.1429362,20.0406158 8.1429362,17.3406158 C8.1429362,15.1315249
+10.0247544,13.3315249 12.3429362,13.3315249 C14.661118,13.3315249
+16.5429362,15.1315249 16.5429362,17.3406158 C16.5429362,18.7997067
+17.811118,19.9860704 19.3792998,19.9860704 C20.9474817,19.9860704
+22.2156635,18.7997067 22.2156635,17.3406158 C22.2156635,12.1997067
+17.7838453,8.02697947 12.3292998,8.02697947 C8.45657257,8.02697947
+4.91111802,10.1815249 3.31566348,13.522434 C2.7838453,14.6269795
+2.51111802,15.922434 2.51111802,17.3406158 C2.51111802,18.4042522
+2.60657257,20.0815249 3.42475439,22.2633431 C3.56111802,22.6178886
+3.3838453,23.0133431 3.02929984,23.1360704 C2.67475439,23.272434
+2.27929984,23.0815249 2.15657257,22.7406158 C1.48839075,20.9542522
+1.16111802,19.1815249 1.16111802,17.3406158 C1.16111802,15.7042522
+1.47475439,14.2178886 2.08839075,12.922434 C3.90202711,9.11788856
+7.92475439,6.64970674 12.3292998,6.64970674 C18.5338453,6.64970674
+23.5792998,11.4360704 23.5792998,17.3269795 C23.5792998,19.5360704
+21.6974817,21.3360704 19.3792998,21.3360704 C17.061118,21.3360704
+15.1792998,19.5360704 15.1792998,17.3269795 C15.1792998,15.8678886
+13.911118,14.6815249 12.3429362,14.6815249 C10.7747544,14.6815249
+9.50657257,15.8678886 9.50657257,17.3269795 C9.50657257,19.6587977
+10.4065726,21.8406158 12.0565726,23.4769795 C13.3520271,24.7587977
+14.5929362,25.4678886 16.5156635,25.9997067 C16.8838453,26.0951613
+17.0883908,26.4769795 16.9929362,26.8315249 C16.9247544,27.1451613
+16.6383908,27.3497067 16.3520271,27.3497067 L16.3520271,27.3497067 Z" />
+ </group>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_chevron_up.xml b/packages/SystemUI/res/drawable/ic_chevron_up.xml
new file mode 100644
index 0000000..835d0ad
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_chevron_up.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/fingerprint_dialog.xml b/packages/SystemUI/res/layout/fingerprint_dialog.xml
new file mode 100644
index 0000000..e5f62b3
--- /dev/null
+++ b/packages/SystemUI/res/layout/fingerprint_dialog.xml
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="bottom"
+ android:background="@color/fingerprint_dialog_dim_color"
+ android:orientation="vertical">
+
+ <!-- This is not a Space since Spaces cannot be clicked -->
+ <View
+ android:id="@+id/space"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+
+ <LinearLayout
+ android:id="@+id/dialog"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:elevation="2dp"
+ android:background="@drawable/fingerprint_dialog_bg">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:elevation="2dp">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/fingerprint_dialog_icon_size"
+ android:layout_height="@dimen/fingerprint_dialog_icon_size"
+ android:layout_marginTop="16dp"
+ android:layout_marginStart="16dp"
+ android:scaleType="centerInside" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@+id/icon"
+ android:layout_marginEnd="16dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="16dp"
+ android:textSize="20sp"
+ android:maxLines="1"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:textColor="@color/fingerprint_dialog_text_color"/>
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@+id/icon"
+ android:layout_below="@+id/title"
+ android:layout_marginEnd="16dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="4dp"
+ android:textSize="12sp"
+ android:maxLines="2"
+ android:textColor="@color/fingerprint_dialog_text_color"/>
+
+ </RelativeLayout>
+
+ <TextView
+ android:id="@+id/description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="16dp"
+ android:layout_marginStart="16dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="20dp"
+ android:textSize="16sp"
+ android:maxLines="4"
+ android:textColor="@color/fingerprint_dialog_text_color"/>
+
+ <ImageView
+ android:id="@+id/fingerprint_icon"
+ android:layout_width="@dimen/fingerprint_dialog_fp_icon_size"
+ android:layout_height="@dimen/fingerprint_dialog_fp_icon_size"
+ android:layout_gravity="center_horizontal"
+ android:scaleType="centerInside"
+ android:src="@drawable/fingerprint_icon"/>
+
+ <TextView
+ android:id="@+id/error"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd = "16dp"
+ android:layout_marginStart="16dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="60dp"
+ android:textSize="12sp"
+ android:visibility="invisible"
+ android:gravity="center_horizontal"
+ android:textColor="@color/fingerprint_error_message_color"/>
+
+ <LinearLayout android:id="@+id/buttonPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="54dip"
+ android:orientation="vertical" >
+ <LinearLayout
+ style="?android:attr/buttonBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingTop="4dip"
+ android:paddingStart="2dip"
+ android:paddingEnd="2dip"
+ android:measureWithLargestChild="true">
+ <LinearLayout android:id="@+id/leftSpacer"
+ android:layout_weight="0.25"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:visibility="gone" />
+ <!-- Positive Button -->
+ <Button android:id="@+id/button1"
+ android:layout_width="0dip"
+ android:layout_gravity="start"
+ android:layout_weight="1"
+ style="?android:attr/buttonBarButtonStyle"
+ android:maxLines="2"
+ android:layout_height="wrap_content"/>
+ <!-- Negative Button -->
+ <Button android:id="@+id/button2"
+ android:layout_width="0dip"
+ android:layout_gravity="end"
+ android:layout_weight="1"
+ style="?android:attr/buttonBarButtonStyle"
+ android:maxLines="2"
+ android:layout_height="wrap_content" />
+ <LinearLayout android:id="@+id/rightSpacer"
+ android:layout_width="0dip"
+ android:layout_weight="0.25"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:visibility="gone" />
+ </LinearLayout>
+ </LinearLayout>
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/output_chooser.xml b/packages/SystemUI/res/layout/output_chooser.xml
index b96f447..b9f7b15 100644
--- a/packages/SystemUI/res/layout/output_chooser.xml
+++ b/packages/SystemUI/res/layout/output_chooser.xml
@@ -15,47 +15,56 @@
limitations under the License.
-->
<!-- extends LinearLayout -->
-<com.android.systemui.volume.OutputChooserLayout
+<com.android.systemui.HardwareUiLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:sysui="http://schemas.android.com/apk/res-auto"
- android:id="@+id/output_chooser"
- android:minWidth="320dp"
- android:minHeight="320dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
- android:padding="20dp" >
-
- <TextView
- android:id="@+id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textDirection="locale"
- android:textAppearance="@style/TextAppearance.QS.DetailHeader"
- android:layout_marginBottom="20dp" />
-
- <com.android.systemui.qs.AutoSizingList
- android:id="@android:id/list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_marginBottom="0dp"
+ android:clipToPadding="false"
+ android:theme="@style/qs_theme"
+ android:clipChildren="false">
+ <com.android.systemui.volume.OutputChooserLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:sysui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/output_chooser"
+ android:layout_width="@dimen/output_chooser_panel_width"
+ android:layout_height="@dimen/output_chooser_panel_width"
+ android:layout_gravity="center_vertical|end"
android:orientation="vertical"
- sysui:itemHeight="@dimen/qs_detail_item_height"
- style="@style/AutoSizingList"/>
-
- <LinearLayout
- android:id="@android:id/empty"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:gravity="center"
- android:orientation="vertical">
+ android:translationZ="8dp"
+ android:padding="20dp" >
<TextView
- android:id="@+id/empty_text"
+ android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textDirection="locale"
- android:layout_marginTop="20dp"
- android:textAppearance="@style/TextAppearance.QS.DetailEmpty"/>
- </LinearLayout>
-</com.android.systemui.volume.OutputChooserLayout>
+ android:textAppearance="@style/TextAppearance.QS.DetailHeader"
+ android:layout_marginBottom="20dp" />
+
+ <com.android.systemui.qs.AutoSizingList
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ sysui:itemHeight="@dimen/qs_detail_item_height"
+ style="@style/AutoSizingList"/>
+
+ <LinearLayout
+ android:id="@android:id/empty"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/empty_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textDirection="locale"
+ android:layout_marginTop="20dp"
+ android:textAppearance="@style/TextAppearance.QS.DetailEmpty"/>
+ </LinearLayout>
+ </com.android.systemui.volume.OutputChooserLayout>
+</com.android.systemui.HardwareUiLayout>
diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
index e0f0ed9..cd3271f 100644
--- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml
+++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
@@ -13,13 +13,25 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<View
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/quick_qs_status_icons"
android:layout_width="match_parent"
android:layout_height="20dp"
- android:layout_marginBottom="22dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="14dp"
android:layout_below="@id/quick_status_bar_system_icons"
>
-</View>
+ <com.android.systemui.statusbar.phone.StatusIconContainer
+ android:id="@+id/statusIcons"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+
+ <include layout="@layout/signal_cluster_view"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/signal_cluster_margin_start" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/recents_swipe_up_onboarding.xml b/packages/SystemUI/res/layout/recents_swipe_up_onboarding.xml
new file mode 100644
index 0000000..b3d5c90
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_swipe_up_onboarding.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="48dp"
+ android:layout_width="match_parent"
+ android:background="@android:color/black"
+ android:layout_gravity="center">
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?android:attr/listDivider"
+ android:gravity="top"/>
+ <TextView
+ android:id="@+id/onboarding_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:text="@string/recents_swipe_up_onboarding"
+ android:textColor="@android:color/white"
+ android:textSize="16sp"
+ android:drawableBottom="@drawable/ic_chevron_up"/>
+ <ImageView
+ android:id="@+id/dismiss"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="12dp"
+ android:layout_marginEnd="6dp"
+ android:src="@drawable/ic_close_white"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:layout_gravity="center_vertical|end"/>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 5108f58..117cd14 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -41,6 +41,7 @@
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:background="@drawable/rounded_bg_full"
+ android:translationZ="8dp"
android:orientation="horizontal" >
<!-- volume rows added and removed here! :-) -->
</LinearLayout>
@@ -57,6 +58,7 @@
android:background="@drawable/rounded_bg_full"
android:gravity="center"
android:layout_gravity="end"
+ android:translationZ="8dp"
android:orientation="vertical" >
<TextView
diff --git a/packages/SystemUI/res/layout/wireless_charging_layout.xml b/packages/SystemUI/res/layout/wireless_charging_layout.xml
new file mode 100644
index 0000000..113282b
--- /dev/null
+++ b/packages/SystemUI/res/layout/wireless_charging_layout.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- Circle animation -->
+ <com.android.systemui.charging.WirelessChargingView
+ android:id="@+id/wireless_charging_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:elevation="4dp"/>
+
+ <!-- Text inside circle -->
+ <LinearLayout
+ android:id="@+id/wireless_charging_text_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/wireless_charging_percentage"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:textSize="32sp"
+ android:textColor="?attr/wallpaperTextColor"/>
+
+ <TextView
+ android:id="@+id/wireless_charging_secondary_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:textColor="?attr/wallpaperTextColor"/>
+ </LinearLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 0f4c3b8..4fcfdf7 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -156,6 +156,14 @@
<color name="zen_introduction">#ffffffff</color>
+
<color name="smart_reply_button_text">#ff4285f4</color><!-- blue 500 -->
<color name="smart_reply_button_background">#fff7f7f7</color>
+
+ <!-- Fingerprint dialog colors -->
+ <color name="fingerprint_dialog_bg_color">#f4ffffff</color> <!-- 96% white -->
+ <color name="fingerprint_dialog_text_color">#ff424242</color> <!-- gray 800-->
+ <color name="fingerprint_dialog_dim_color">#80000000</color> <!-- 50% black -->
+ <color name="fingerprint_error_message_color">#ff5722</color>
+
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index b33f857..6768470 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -350,6 +350,7 @@
<item>com.android.systemui.globalactions.GlobalActionsComponent</item>
<item>com.android.systemui.RoundedCorners</item>
<item>com.android.systemui.EmulatedDisplayCutout</item>
+ <item>com.android.systemui.fingerprint.FingerprintDialogImpl</item>
</string-array>
<!-- SystemUI vender service, used in config_systemUIServiceComponents. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 887d3cb..7ee9eda 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -267,6 +267,8 @@
<dimen name="volume_dialog_panel_width">120dp</dimen>
+ <dimen name="output_chooser_panel_width">320dp</dimen>
+
<!-- Gravity for the notification panel -->
<integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top -->
@@ -579,7 +581,7 @@
<dimen name="keyguard_affordance_icon_width">24dp</dimen>
<dimen name="keyguard_indication_margin_bottom">65dp</dimen>
- <dimen name="keyguard_indication_margin_bottom_ambient">30dp</dimen>
+ <dimen name="keyguard_indication_margin_bottom_ambient">16dp</dimen>
<!-- The text size for battery level -->
<dimen name="battery_level_text_size">12sp</dimen>
@@ -873,7 +875,7 @@
<dimen name="rounded_corner_radius">0dp</dimen>
<dimen name="rounded_corner_content_padding">0dp</dimen>
<dimen name="nav_content_padding">0dp</dimen>
- <dimen name="nav_quick_scrub_track_edge_padding">32dp</dimen>
+ <dimen name="nav_quick_scrub_track_edge_padding">42dp</dimen>
<dimen name="nav_quick_scrub_track_thickness">2dp</dimen>
<!-- Intended corner radius when drawing the mobile signal -->
@@ -889,4 +891,16 @@
<dimen name="smart_reply_button_spacing">8dp</dimen>
<dimen name="smart_reply_button_padding_vertical">4dp</dimen>
<dimen name="smart_reply_button_font_size">14sp</dimen>
+
+ <dimen name="fingerprint_dialog_icon_size">44dp</dimen>
+ <dimen name="fingerprint_dialog_fp_icon_size">60dp</dimen>
+ <dimen name="fingerprint_dialog_animation_translation_offset">350dp</dimen>
+
+ <!-- WirelessCharging Animation values -->
+ <!-- Starting text size of batteryLevel for wireless charging animation -->
+ <dimen name="config_batteryLevelTextSizeStart" format="float">5.0</dimen>
+ <!-- Ending text size of batteryLevel for wireless charging animation -->
+ <dimen name="config_batteryLevelTextSizeEnd" format="float">32.0</dimen>
+ <!-- Wireless Charging battery level text animation duration -->
+ <integer name="config_batteryLevelTextAnimationDuration">400</integer>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8d6b38f..8ea1225 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -459,7 +459,7 @@
<string name="accessibility_casting">@string/quick_settings_casting</string>
<!-- Content description of the work mode icon in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_work_mode">Work mode</string>
+ <string name="accessibility_work_mode">@string/quick_settings_work_mode_on_label</string>
<!-- Content description to tell the user that this button will remove an application from recents -->
<string name="accessibility_recents_item_will_be_dismissed">Dismiss <xliff:g id="app" example="Calendar">%s</xliff:g>.</string>
@@ -764,6 +764,11 @@
<string name="quick_settings_tethering_label">Tethering</string>
<!-- QuickSettings: Hotspot. [CHAR LIMIT=NONE] -->
<string name="quick_settings_hotspot_label">Hotspot</string>
+ <!-- QuickSettings: Hotspot: Secondary label for how many devices are connected to the hotspot [CHAR LIMIT=NONE] -->
+ <plurals name="quick_settings_hotspot_num_devices">
+ <item quantity="one">%d device</item>
+ <item quantity="other">%d devices</item>
+ </plurals>
<!-- QuickSettings: Notifications [CHAR LIMIT=NONE] -->
<string name="quick_settings_notifications_label">Notifications</string>
<!-- QuickSettings: Flashlight [CHAR LIMIT=NONE] -->
@@ -782,8 +787,14 @@
<string name="quick_settings_cellular_detail_data_limit"><xliff:g id="data_limit" example="2.0 GB">%s</xliff:g> limit</string>
<!-- QuickSettings: Cellular detail panel, data warning format string [CHAR LIMIT=NONE] -->
<string name="quick_settings_cellular_detail_data_warning"><xliff:g id="data_limit" example="2.0 GB">%s</xliff:g> warning</string>
- <!-- QuickSettings: Work mode [CHAR LIMIT=NONE] -->
- <string name="quick_settings_work_mode_label">Work mode</string>
+ <!-- QuickSettings: This string is in the easy-to-view settings that a user can pull down from
+ the top of their phone's screen. This is a label for a toggle to turn the work profile on and
+ off. "Work profile" means a separate profile on a user's phone that's specifically for their
+ work apps and managed by their company. "Work" is used as an adjective. [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_work_mode_on_label">Work profile</string>
+ <!-- QuickSettings: This is a label for a toggle to turn the work profile on and off and this is
+ shown when work profile is off. [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_work_mode_off_label">Notifications & apps are off</string>
<!-- QuickSettings: Label for the toggle to activate Night display (renamed "Night Light" with title caps). [CHAR LIMIT=20] -->
<string name="quick_settings_night_display_label">Night Light</string>
<!-- QuickSettings: Secondary text for when the Night Light will be enabled at sunset. [CHAR LIMIT=20] -->
@@ -820,6 +831,8 @@
<string name="recents_stack_action_button_label">Clear all</string>
<!-- Recents: Hint text that shows on the drop targets to start multiwindow. [CHAR LIMIT=NONE] -->
<string name="recents_drag_hint_message">Drag here to use split screen</string>
+ <!-- Recents: Text that shows above the nav bar after launching a few apps. [CHAR LIMIT=NONE] -->
+ <string name="recents_swipe_up_onboarding">Swipe up to switch apps</string>
<!-- Recents: MultiStack add stack split horizontal radio button. [CHAR LIMIT=NONE] -->
<string name="recents_multistack_add_stack_dialog_split_horizontal">Split Horizontal</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 1c99d38..c9a6ea9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -30,6 +30,7 @@
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.IAssistDataReceiver;
+import android.app.WindowConfiguration.ActivityType;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -96,11 +97,14 @@
* @return the top running task (can be {@code null}).
*/
public ActivityManager.RunningTaskInfo getRunningTask() {
+ return getRunningTask(ACTIVITY_TYPE_RECENTS /* ignoreActivityType */);
+ }
+
+ public ActivityManager.RunningTaskInfo getRunningTask(@ActivityType int ignoreActivityType) {
// Note: The set of running tasks from the system is ordered by recency
try {
List<ActivityManager.RunningTaskInfo> tasks =
- ActivityManager.getService().getFilteredTasks(1,
- ACTIVITY_TYPE_RECENTS /* ignoreActivityType */,
+ ActivityManager.getService().getFilteredTasks(1, ignoreActivityType,
WINDOWING_MODE_PINNED /* ignoreWindowingMode */);
if (tasks.isEmpty()) {
return null;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 19afcf5..9e4b405 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1257,7 +1257,8 @@
mFingerprintCancelSignal.cancel();
}
mFingerprintCancelSignal = new CancellationSignal();
- mFpm.authenticate(null, mFingerprintCancelSignal, 0, mAuthenticationCallback, null, userId);
+ mFpm.authenticate(null, mFingerprintCancelSignal, 0, mAuthenticationCallback, null,
+ userId);
setFingerprintRunningState(FINGERPRINT_STATE_RUNNING);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
index ca34345..9481788 100644
--- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
@@ -58,6 +58,7 @@
private boolean mRoundedDivider;
private int mRotation = ROTATION_NONE;
private boolean mRotatedBackground;
+ private boolean mSwapOrientation = true;
public HardwareUiLayout(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -145,6 +146,10 @@
updateRotation();
}
+ public void setSwapOrientation(boolean swapOrientation) {
+ mSwapOrientation = swapOrientation;
+ }
+
private void updateRotation() {
int rotation = RotationUtils.getRotation(getContext());
if (rotation != mRotation) {
@@ -173,7 +178,9 @@
if (to == ROTATION_SEASCAPE) {
swapOrder(linearLayout);
}
- linearLayout.setOrientation(LinearLayout.HORIZONTAL);
+ if (mSwapOrientation) {
+ linearLayout.setOrientation(LinearLayout.HORIZONTAL);
+ }
swapDimens(this.mChild);
}
} else {
@@ -184,7 +191,9 @@
if (from == ROTATION_SEASCAPE) {
swapOrder(linearLayout);
}
- linearLayout.setOrientation(LinearLayout.VERTICAL);
+ if (mSwapOrientation) {
+ linearLayout.setOrientation(LinearLayout.VERTICAL);
+ }
swapDimens(mChild);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
index 0be522b..244c1b9 100644
--- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
@@ -195,6 +195,10 @@
return mOverviewProxy;
}
+ public ComponentName getLauncherComponent() {
+ return mLauncherComponentName;
+ }
+
private void disconnectFromLauncherService() {
if (mOverviewProxy != null) {
mOverviewProxy.asBinder().unlinkToDeath(mOverviewServiceDeathRcpt, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 4437d31..9319bc6 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -48,6 +48,8 @@
Key.QS_WORK_ADDED,
Key.QS_NIGHTDISPLAY_ADDED,
Key.SEEN_MULTI_USER,
+ Key.NUM_APPS_LAUNCHED,
+ Key.HAS_SWIPED_UP_FOR_RECENTS,
})
public @interface Key {
@Deprecated
@@ -75,6 +77,8 @@
@Deprecated
String QS_NIGHTDISPLAY_ADDED = "QsNightDisplayAdded";
String SEEN_MULTI_USER = "HasSeenMultiUser";
+ String NUM_APPS_LAUNCHED = "NumAppsLaunched";
+ String HAS_SWIPED_UP_FOR_RECENTS = "HasSwipedUpForRecents";
}
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
new file mode 100644
index 0000000..348855b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.charging;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+
+/**
+ * A WirelessChargingAnimation is a view containing view + animation for wireless charging.
+ * @hide
+ */
+public class WirelessChargingAnimation {
+
+ public static final long DURATION = 1400;
+ private static final String TAG = "WirelessChargingView";
+ private static final boolean LOCAL_LOGV = false;
+
+ private final WirelessChargingView mCurrentWirelessChargingView;
+ private static WirelessChargingView mPreviousWirelessChargingView;
+
+ /**
+ * Constructs an empty WirelessChargingAnimation object. If looper is null,
+ * Looper.myLooper() is used. Must set
+ * {@link WirelessChargingAnimation#mCurrentWirelessChargingView}
+ * before calling {@link #show} - can be done through {@link #makeWirelessChargingAnimation}.
+ * @hide
+ */
+ public WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper, int
+ batteryLevel) {
+ mCurrentWirelessChargingView = new WirelessChargingView(context, looper,
+ batteryLevel);
+ }
+
+ /**
+ * Creates a wireless charging animation object populated with next view.
+ * @hide
+ */
+ public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context,
+ @Nullable Looper looper, int batteryLevel) {
+ return new WirelessChargingAnimation(context, looper, batteryLevel);
+ }
+
+ /**
+ * Show the view for the specified duration.
+ */
+ public void show() {
+ if (mCurrentWirelessChargingView == null ||
+ mCurrentWirelessChargingView.mNextView == null) {
+ throw new RuntimeException("setView must have been called");
+ }
+
+ if (mPreviousWirelessChargingView != null) {
+ mPreviousWirelessChargingView.hide(0);
+ }
+
+ mPreviousWirelessChargingView = mCurrentWirelessChargingView;
+ mCurrentWirelessChargingView.show();
+ mCurrentWirelessChargingView.hide(DURATION);
+ }
+
+ private static class WirelessChargingView {
+ private static final int SHOW = 0;
+ private static final int HIDE = 1;
+
+ private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+ private final Handler mHandler;
+
+ private int mGravity;
+
+ private View mView;
+ private View mNextView;
+ private WindowManager mWM;
+
+ public WirelessChargingView(Context context, @Nullable Looper looper, int batteryLevel) {
+ mNextView = new WirelessChargingLayout(context, batteryLevel);
+ mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;
+
+ final WindowManager.LayoutParams params = mParams;
+ params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.format = PixelFormat.TRANSLUCENT;
+
+ params.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+ params.setTitle("Charging Animation");
+ params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+
+ params.dimAmount = .3f;
+
+ if (looper == null) {
+ // Use Looper.myLooper() if looper is not specified.
+ looper = Looper.myLooper();
+ if (looper == null) {
+ throw new RuntimeException(
+ "Can't display wireless animation on a thread that has not called "
+ + "Looper.prepare()");
+ }
+ }
+
+ mHandler = new Handler(looper, null) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SHOW: {
+ handleShow();
+ break;
+ }
+ case HIDE: {
+ handleHide();
+ // Don't do this in handleHide() because it is also invoked by
+ // handleShow()
+ mNextView = null;
+ break;
+ }
+ }
+ }
+ };
+ }
+
+ public void show() {
+ if (LOCAL_LOGV) Log.v(TAG, "SHOW: " + this);
+ mHandler.obtainMessage(SHOW).sendToTarget();
+ }
+
+ public void hide(long duration) {
+ if (LOCAL_LOGV) Log.v(TAG, "HIDE: " + this);
+ mHandler.sendMessageDelayed(Message.obtain(mHandler, HIDE), duration);
+ }
+
+ private void handleShow() {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView="
+ + mNextView);
+ }
+
+ if (mView != mNextView) {
+ // remove the old view if necessary
+ handleHide();
+ mView = mNextView;
+ Context context = mView.getContext().getApplicationContext();
+ String packageName = mView.getContext().getOpPackageName();
+ if (context == null) {
+ context = mView.getContext();
+ }
+ mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ // We can resolve the Gravity here by using the Locale for getting
+ // the layout direction
+ final Configuration config = mView.getContext().getResources().getConfiguration();
+ final int gravity = Gravity.getAbsoluteGravity(mGravity,
+ config.getLayoutDirection());
+ mParams.gravity = gravity;
+ if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
+ mParams.horizontalWeight = 1.0f;
+ }
+ if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
+ mParams.verticalWeight = 1.0f;
+ }
+ mParams.packageName = packageName;
+ mParams.hideTimeoutMilliseconds = DURATION;
+
+ if (mView.getParent() != null) {
+ if (LOCAL_LOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
+ mWM.removeView(mView);
+ }
+ if (LOCAL_LOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
+
+ try {
+ mWM.addView(mView, mParams);
+ } catch (WindowManager.BadTokenException e) {
+ Slog.d(TAG, "Unable to add wireless charging view. " + e);
+ }
+ }
+ }
+
+ private void handleHide() {
+ if (LOCAL_LOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
+ if (mView != null) {
+ if (mView.getParent() != null) {
+ if (LOCAL_LOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
+ mWM.removeViewImmediate(mView);
+ }
+
+ mView = null;
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
new file mode 100644
index 0000000..c78ea56
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.charging;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+import java.text.NumberFormat;
+
+/**
+ * @hide
+ */
+public class WirelessChargingLayout extends FrameLayout {
+ private final static int UNKNOWN_BATTERY_LEVEL = -1;
+
+ public WirelessChargingLayout(Context context) {
+ super(context);
+ init(context, null);
+ }
+
+ public WirelessChargingLayout(Context context, int batterylLevel) {
+ super(context);
+ init(context, null, batterylLevel);
+ }
+
+ public WirelessChargingLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs);
+ }
+
+ private void init(Context c, AttributeSet attrs) {
+ init(c, attrs, -1);
+ }
+
+ private void init(Context c, AttributeSet attrs, int batteryLevel) {
+ final int mBatteryLevel = batteryLevel;
+
+ inflate(c, R.layout.wireless_charging_layout, this);
+
+ // where the circle animation occurs:
+ final WirelessChargingView mChargingView = findViewById(R.id.wireless_charging_view);
+
+ // amount of battery:
+ final TextView mPercentage = findViewById(R.id.wireless_charging_percentage);
+
+ // (optional) time until full charge if available
+ final TextView mSecondaryText = findViewById(R.id.wireless_charging_secondary_text);
+
+ if (batteryLevel != UNKNOWN_BATTERY_LEVEL) {
+ mPercentage.setText(NumberFormat.getPercentInstance().format(mBatteryLevel / 100f));
+
+ ValueAnimator animator = ObjectAnimator.ofFloat(mPercentage, "textSize",
+ getContext().getResources().getFloat(R.dimen.config_batteryLevelTextSizeStart),
+ getContext().getResources().getFloat(R.dimen.config_batteryLevelTextSizeEnd));
+
+ animator.setDuration((long) getContext().getResources().getInteger(
+ R.integer.config_batteryLevelTextAnimationDuration));
+ animator.start();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java
new file mode 100644
index 0000000..f5edf52
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.charging;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+
+final class WirelessChargingView extends View {
+
+ private Interpolator mInterpolator;
+ private float mPathGone;
+ private float mInterpolatedPathGone;
+ private long mAnimationStartTime;
+ private long mStartSpinCircleAnimationTime;
+ private long mAnimationOffset = 500;
+ private long mTotalAnimationDuration = WirelessChargingAnimation.DURATION - mAnimationOffset;
+ private long mExpandingCircle = (long) (mTotalAnimationDuration * .9);
+ private long mSpinCircleAnimationTime = mTotalAnimationDuration - mExpandingCircle;
+
+ private boolean mFinishedAnimatingSpinningCircles = false;
+
+ private int mStartAngle = -90;
+ private int mNumSmallCircles = 20;
+ private int mSmallCircleRadius = 10;
+
+ private int mMainCircleStartRadius = 100;
+ private int mMainCircleEndRadius = 230;
+ private int mMainCircleCurrentRadius = mMainCircleStartRadius;
+
+ private int mCenterX;
+ private int mCenterY;
+
+ private Paint mPaint;
+ private Context mContext;
+
+ public WirelessChargingView(Context context) {
+ super(context);
+ init(context, null);
+ }
+
+ public WirelessChargingView(Context context, AttributeSet attr) {
+ super(context, attr);
+ init(context, attr);
+ }
+
+ public WirelessChargingView(Context context, AttributeSet attr, int styleAttr) {
+ super(context, attr, styleAttr);
+ init(context, attr);
+ }
+
+ public void init(Context context, AttributeSet attr) {
+ mContext = context;
+ setupPaint();
+ mInterpolator = new DecelerateInterpolator();
+ }
+
+ private void setupPaint() {
+ mPaint = new Paint();
+ mPaint.setColor(Utils.getColorAttr(mContext, R.attr.wallpaperTextColor));
+ }
+
+ @Override
+ protected void onDraw(final Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (mAnimationStartTime == 0) {
+ mAnimationStartTime = System.currentTimeMillis();
+ }
+
+ updateDrawingParameters();
+ drawCircles(canvas);
+
+ if (!mFinishedAnimatingSpinningCircles) {
+ invalidate();
+ }
+ }
+
+ /**
+ * Draws a larger circle of radius {@link WirelessChargingView#mMainCircleEndRadius} composed of
+ * {@link WirelessChargingView#mNumSmallCircles} smaller circles
+ * @param canvas
+ */
+ private void drawCircles(Canvas canvas) {
+ mCenterX = canvas.getWidth() / 2;
+ mCenterY = canvas.getHeight() / 2;
+
+ // angleOffset makes small circles look like they're moving around the main circle
+ float angleOffset = mPathGone * 10;
+
+ // draws mNumSmallCircles to compose a larger, main circle
+ for (int circle = 0; circle < mNumSmallCircles; circle++) {
+ double angle = ((mStartAngle + angleOffset) * Math.PI / 180) + (circle * ((2 * Math.PI)
+ / mNumSmallCircles));
+
+ int x = (int) (mCenterX + Math.cos(angle) * (mMainCircleCurrentRadius +
+ mSmallCircleRadius));
+ int y = (int) (mCenterY + Math.sin(angle) * (mMainCircleCurrentRadius +
+ mSmallCircleRadius));
+
+ canvas.drawCircle(x, y, mSmallCircleRadius, mPaint);
+ }
+
+ if (mMainCircleCurrentRadius >= mMainCircleEndRadius && !isSpinCircleAnimationStarted()) {
+ mStartSpinCircleAnimationTime = System.currentTimeMillis();
+ }
+
+ if (isSpinAnimationFinished()) {
+ mFinishedAnimatingSpinningCircles = true;
+ }
+ }
+
+ private boolean isSpinCircleAnimationStarted() {
+ return mStartSpinCircleAnimationTime != 0;
+ }
+
+ private boolean isSpinAnimationFinished() {
+ return isSpinCircleAnimationStarted() && System.currentTimeMillis() -
+ mStartSpinCircleAnimationTime > mSpinCircleAnimationTime;
+ }
+
+ private void updateDrawingParameters() {
+ mPathGone = getPathGone(System.currentTimeMillis());
+ mInterpolatedPathGone = mInterpolator.getInterpolation(mPathGone);
+
+ if (mPathGone < 1.0f) {
+ mMainCircleCurrentRadius = mMainCircleStartRadius + (int) (mInterpolatedPathGone *
+ (mMainCircleEndRadius - mMainCircleStartRadius));
+ } else {
+ mMainCircleCurrentRadius = mMainCircleEndRadius;
+ }
+ }
+
+ /**
+ * @return decimal depicting how far along the creation of the larger circle (of circles) is
+ * For values < 1.0, the larger circle is being drawn
+ * For values > 1.0 the larger circle has been drawn and further animation can occur
+ */
+ private float getPathGone(long now) {
+ return (float) (now - mAnimationStartTime) / (mExpandingCircle);
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java b/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java
new file mode 100644
index 0000000..085ece7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.chooser;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.systemui.R;
+
+import java.lang.Thread;
+import java.util.ArrayList;
+
+public final class ChooserActivity extends Activity {
+
+ private static final String TAG = "ChooserActivity";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ChooserHelper.onChoose(this);
+ finish();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java b/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java
new file mode 100644
index 0000000..ac22568
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.chooser;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.systemui.R;
+
+public class ChooserHelper {
+
+ private static final String TAG = "ChooserHelper";
+
+ static void onChoose(Activity activity) {
+ final Intent thisIntent = activity.getIntent();
+ final Bundle thisExtras = thisIntent.getExtras();
+ final Intent chosenIntent = thisIntent.getParcelableExtra(Intent.EXTRA_INTENT);
+ final Bundle options = thisIntent.getParcelableExtra(ActivityManager.EXTRA_OPTIONS);
+ final IBinder permissionToken =
+ thisExtras.getBinder(ActivityManager.EXTRA_PERMISSION_TOKEN);
+ final boolean ignoreTargetSecurity =
+ thisIntent.getBooleanExtra(ActivityManager.EXTRA_IGNORE_TARGET_SECURITY, false);
+ final int userId = thisIntent.getIntExtra(Intent.EXTRA_USER_ID, -1);
+ activity.startActivityAsCaller(
+ chosenIntent, options, permissionToken, ignoreTargetSecurity, userId);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java
new file mode 100644
index 0000000..262c71a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.fingerprint;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.fingerprint.FingerprintDialog;
+import android.hardware.fingerprint.IFingerprintDialogReceiver;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.WindowManager;
+
+import com.android.internal.os.SomeArgs;
+import com.android.systemui.SystemUI;
+import com.android.systemui.statusbar.CommandQueue;
+
+public class FingerprintDialogImpl extends SystemUI implements CommandQueue.Callbacks {
+ private static final String TAG = "FingerprintDialogImpl";
+ private static final boolean DEBUG = true;
+
+ protected static final int MSG_SHOW_DIALOG = 1;
+ protected static final int MSG_FINGERPRINT_AUTHENTICATED = 2;
+ protected static final int MSG_FINGERPRINT_HELP = 3;
+ protected static final int MSG_FINGERPRINT_ERROR = 4;
+ protected static final int MSG_HIDE_DIALOG = 5;
+ protected static final int MSG_BUTTON_NEGATIVE = 6;
+ protected static final int MSG_USER_CANCELED = 7;
+ protected static final int MSG_BUTTON_POSITIVE = 8;
+ protected static final int MSG_CLEAR_MESSAGE = 9;
+
+
+ private FingerprintDialogView mDialogView;
+ private WindowManager mWindowManager;
+ private IFingerprintDialogReceiver mReceiver;
+ private boolean mDialogShowing;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_SHOW_DIALOG:
+ handleShowDialog((SomeArgs) msg.obj);
+ break;
+ case MSG_FINGERPRINT_AUTHENTICATED:
+ handleFingerprintAuthenticated();
+ break;
+ case MSG_FINGERPRINT_HELP:
+ handleFingerprintHelp((String) msg.obj);
+ break;
+ case MSG_FINGERPRINT_ERROR:
+ handleFingerprintError((String) msg.obj);
+ break;
+ case MSG_HIDE_DIALOG:
+ handleHideDialog((Boolean) msg.obj);
+ break;
+ case MSG_BUTTON_NEGATIVE:
+ handleButtonNegative();
+ break;
+ case MSG_USER_CANCELED:
+ handleUserCanceled();
+ break;
+ case MSG_BUTTON_POSITIVE:
+ handleButtonPositive();
+ break;
+ case MSG_CLEAR_MESSAGE:
+ handleClearMessage();
+ break;
+ }
+ }
+ };
+
+ @Override
+ public void start() {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+ return;
+ }
+ getComponent(CommandQueue.class).addCallbacks(this);
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mDialogView = new FingerprintDialogView(mContext, mHandler);
+ }
+
+ @Override
+ public void showFingerprintDialog(Bundle bundle, IFingerprintDialogReceiver receiver) {
+ if (DEBUG) Log.d(TAG, "showFingerprintDialog");
+ // Remove these messages as they are part of the previous client
+ mHandler.removeMessages(MSG_FINGERPRINT_ERROR);
+ mHandler.removeMessages(MSG_FINGERPRINT_HELP);
+ mHandler.removeMessages(MSG_FINGERPRINT_AUTHENTICATED);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = bundle;
+ args.arg2 = receiver;
+ mHandler.obtainMessage(MSG_SHOW_DIALOG, args).sendToTarget();
+ }
+
+ @Override
+ public void onFingerprintAuthenticated() {
+ if (DEBUG) Log.d(TAG, "onFingerprintAuthenticated");
+ mHandler.obtainMessage(MSG_FINGERPRINT_AUTHENTICATED).sendToTarget();
+ }
+
+ @Override
+ public void onFingerprintHelp(String message) {
+ if (DEBUG) Log.d(TAG, "onFingerprintHelp: " + message);
+ mHandler.obtainMessage(MSG_FINGERPRINT_HELP, message).sendToTarget();
+ }
+
+ @Override
+ public void onFingerprintError(String error) {
+ if (DEBUG) Log.d(TAG, "onFingerprintError: " + error);
+ mHandler.obtainMessage(MSG_FINGERPRINT_ERROR, error).sendToTarget();
+ }
+
+ @Override
+ public void hideFingerprintDialog() {
+ if (DEBUG) Log.d(TAG, "hideFingerprintDialog");
+ mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget();
+ }
+
+ private void handleShowDialog(SomeArgs args) {
+ if (DEBUG) Log.d(TAG, "handleShowDialog");
+ if (mDialogShowing) {
+ Log.w(TAG, "Dialog already showing");
+ return;
+ }
+ mReceiver = (IFingerprintDialogReceiver) args.arg2;
+ mDialogView.setBundle((Bundle)args.arg1);
+ mWindowManager.addView(mDialogView, mDialogView.getLayoutParams());
+ mDialogShowing = true;
+ }
+
+ private void handleFingerprintAuthenticated() {
+ if (DEBUG) Log.d(TAG, "handleFingerprintAuthenticated");
+ handleHideDialog(false /* userCanceled */);
+ }
+
+ private void handleFingerprintHelp(String message) {
+ if (DEBUG) Log.d(TAG, "handleFingerprintHelp: " + message);
+ mDialogView.showHelpMessage(message);
+ }
+
+ private void handleFingerprintError(String error) {
+ if (DEBUG) Log.d(TAG, "handleFingerprintError: " + error);
+ if (!mDialogShowing) {
+ if (DEBUG) Log.d(TAG, "Dialog already dismissed");
+ return;
+ }
+ mDialogView.showErrorMessage(error);
+ }
+
+ private void handleHideDialog(boolean userCanceled) {
+ if (DEBUG) Log.d(TAG, "handleHideDialog");
+ if (!mDialogShowing) {
+ // This can happen if there's a race and we get called from both
+ // onAuthenticated and onError, etc.
+ Log.w(TAG, "Dialog already dismissed, userCanceled: " + userCanceled);
+ return;
+ }
+ if (userCanceled) {
+ try {
+ mReceiver.onDialogDismissed(FingerprintDialog.DISMISSED_REASON_USER_CANCEL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException when hiding dialog", e);
+ }
+ }
+ mReceiver = null;
+ mWindowManager.removeView(mDialogView);
+ mDialogShowing = false;
+ }
+
+ private void handleButtonNegative() {
+ if (mReceiver == null) {
+ Log.e(TAG, "Receiver is null");
+ return;
+ }
+ try {
+ mReceiver.onDialogDismissed(FingerprintDialog.DISMISSED_REASON_NEGATIVE);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote exception when handling negative button", e);
+ }
+ handleHideDialog(false /* userCanceled */);
+ }
+
+ private void handleButtonPositive() {
+ if (mReceiver == null) {
+ Log.e(TAG, "Receiver is null");
+ return;
+ }
+ try {
+ mReceiver.onDialogDismissed(FingerprintDialog.DISMISSED_REASON_POSITIVE);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote exception when handling positive button", e);
+ }
+ handleHideDialog(false /* userCanceled */);
+ }
+
+ private void handleClearMessage() {
+ mDialogView.clearMessage();
+ }
+
+ private void handleUserCanceled() {
+ handleHideDialog(true /* userCanceled */);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java
new file mode 100644
index 0000000..19bc2ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.fingerprint;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.hardware.fingerprint.FingerprintDialog;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+
+/**
+ * This class loads the view for the system-provided dialog. The view consists of:
+ * Application Icon, Title, Subtitle, Description, Fingerprint Icon, Error/Help message area,
+ * and positive/negative buttons.
+ */
+public class FingerprintDialogView extends LinearLayout {
+
+ private static final String TAG = "FingerprintDialogView";
+
+ private static final int ANIMATION_DURATION = 250; // ms
+
+ private final IBinder mWindowToken = new Binder();
+ private final ActivityManagerWrapper mActivityManagerWrapper;
+ private final PackageManagerWrapper mPackageManageWrapper;
+ private final Interpolator mLinearOutSlowIn;
+ private final Interpolator mFastOutLinearIn;
+ private final float mAnimationTranslationOffset;
+
+ private ViewGroup mLayout;
+ private final TextView mErrorText;
+ private Handler mHandler;
+ private Bundle mBundle;
+ private final LinearLayout mDialog;
+
+ public FingerprintDialogView(Context context, Handler handler) {
+ super(context);
+ mHandler = handler;
+ mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
+ mPackageManageWrapper = PackageManagerWrapper.getInstance();
+ mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
+ mFastOutLinearIn = Interpolators.FAST_OUT_LINEAR_IN;
+ mAnimationTranslationOffset = getResources()
+ .getDimension(R.dimen.fingerprint_dialog_animation_translation_offset);
+
+ // Create the dialog
+ LayoutInflater factory = LayoutInflater.from(getContext());
+ mLayout = (ViewGroup) factory.inflate(R.layout.fingerprint_dialog, this, false);
+ addView(mLayout);
+
+ mDialog = mLayout.findViewById(R.id.dialog);
+
+ mErrorText = mLayout.findViewById(R.id.error);
+
+ mLayout.setOnKeyListener(new View.OnKeyListener() {
+ boolean downPressed = false;
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (keyCode != KeyEvent.KEYCODE_BACK) {
+ return false;
+ }
+ if (event.getAction() == KeyEvent.ACTION_DOWN && downPressed == false) {
+ downPressed = true;
+ } else if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ downPressed = false;
+ } else if (event.getAction() == KeyEvent.ACTION_UP && downPressed == true) {
+ downPressed = false;
+ mHandler.obtainMessage(FingerprintDialogImpl.MSG_USER_CANCELED).sendToTarget();
+ }
+ return true;
+ }
+ });
+
+ final View space = mLayout.findViewById(R.id.space);
+ final Button negative = mLayout.findViewById(R.id.button2);
+ final Button positive = mLayout.findViewById(R.id.button1);
+
+ space.setClickable(true);
+ space.setOnTouchListener((View view, MotionEvent event) -> {
+ mHandler.obtainMessage(FingerprintDialogImpl.MSG_HIDE_DIALOG, true /* userCanceled*/)
+ .sendToTarget();
+ return true;
+ });
+
+ negative.setOnClickListener((View v) -> {
+ mHandler.obtainMessage(FingerprintDialogImpl.MSG_BUTTON_NEGATIVE).sendToTarget();
+ });
+
+ positive.setOnClickListener((View v) -> {
+ mHandler.obtainMessage(FingerprintDialogImpl.MSG_BUTTON_POSITIVE).sendToTarget();
+ });
+
+ mLayout.setFocusableInTouchMode(true);
+ mLayout.requestFocus();
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ final TextView title = mLayout.findViewById(R.id.title);
+ final TextView subtitle = mLayout.findViewById(R.id.subtitle);
+ final TextView description = mLayout.findViewById(R.id.description);
+ final Button negative = mLayout.findViewById(R.id.button2);
+ final ImageView image = mLayout.findViewById(R.id.icon);
+ final Button positive = mLayout.findViewById(R.id.button1);
+ final ImageView fingerprint_icon = mLayout.findViewById(R.id.fingerprint_icon);
+
+ title.setText(mBundle.getCharSequence(FingerprintDialog.KEY_TITLE));
+ title.setSelected(true);
+ subtitle.setText(mBundle.getCharSequence(FingerprintDialog.KEY_SUBTITLE));
+ description.setText(mBundle.getCharSequence(FingerprintDialog.KEY_DESCRIPTION));
+ negative.setText(mBundle.getCharSequence(FingerprintDialog.KEY_NEGATIVE_TEXT));
+ image.setImageDrawable(getAppIcon());
+
+ final CharSequence positiveText =
+ mBundle.getCharSequence(FingerprintDialog.KEY_POSITIVE_TEXT);
+ positive.setText(positiveText); // needs to be set for marquee to work
+ if (positiveText != null) {
+ positive.setVisibility(View.VISIBLE);
+ } else {
+ positive.setVisibility(View.GONE);
+ }
+
+ // Dim the background and slide the dialog up
+ mDialog.setTranslationY(mAnimationTranslationOffset);
+ mLayout.setAlpha(0f);
+ postOnAnimation(new Runnable() {
+ @Override
+ public void run() {
+ mLayout.animate()
+ .alpha(1f)
+ .setDuration(ANIMATION_DURATION)
+ .setInterpolator(mLinearOutSlowIn)
+ .withLayer()
+ .start();
+ mDialog.animate()
+ .translationY(0)
+ .setDuration(ANIMATION_DURATION)
+ .setInterpolator(mLinearOutSlowIn)
+ .withLayer()
+ .start();
+ }
+ });
+ }
+
+ public void setBundle(Bundle bundle) {
+ mBundle = bundle;
+ }
+
+ protected void clearMessage() {
+ mErrorText.setVisibility(View.INVISIBLE);
+ }
+
+ private void showMessage(String message) {
+ mHandler.removeMessages(FingerprintDialogImpl.MSG_CLEAR_MESSAGE);
+ mErrorText.setText(message);
+ mErrorText.setVisibility(View.VISIBLE);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(FingerprintDialogImpl.MSG_CLEAR_MESSAGE),
+ FingerprintDialog.HIDE_DIALOG_DELAY);
+ }
+
+ public void showHelpMessage(String message) {
+ showMessage(message);
+ }
+
+ public void showErrorMessage(String error) {
+ showMessage(error);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(FingerprintDialogImpl.MSG_HIDE_DIALOG,
+ false /* userCanceled */), FingerprintDialog.HIDE_DIALOG_DELAY);
+ }
+
+ private Drawable getAppIcon() {
+ final ActivityManager.RunningTaskInfo taskInfo = mActivityManagerWrapper.getRunningTask();
+ final ComponentName cn = taskInfo.topActivity;
+ final int userId = mActivityManagerWrapper.getCurrentUserId();
+ final ActivityInfo activityInfo = mPackageManageWrapper.getActivityInfo(cn, userId);
+ return mActivityManagerWrapper.getBadgedActivityIcon(activityInfo, userId);
+ }
+
+ public WindowManager.LayoutParams getLayoutParams() {
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.setTitle("FingerprintDialogView");
+ lp.token = mWindowToken;
+ return lp;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index f06cda0..aa08562 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -14,6 +14,10 @@
package com.android.systemui.globalactions;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dependency;
import com.android.systemui.SysUiServiceProvider;
@@ -25,10 +29,6 @@
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ExtensionController.Extension;
-import android.content.Context;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-
public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager {
private GlobalActions mPlugin;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index a97b35c..17ede65 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -39,7 +39,8 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSDetail.Callback;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.SignalClusterView;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
import com.android.systemui.statusbar.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
@@ -56,6 +57,10 @@
protected QuickQSPanel mHeaderQsPanel;
protected QSTileHost mHost;
+ private TintedIconManager mIconManager;
+ private TouchAnimator mAlphaAnimator;
+
+ private View mQuickQsStatusIcons;
private View mDate;
@@ -71,16 +76,25 @@
mHeaderQsPanel = findViewById(R.id.quick_qs_panel);
mDate = findViewById(R.id.date);
mDate.setOnClickListener(this);
+ mQuickQsStatusIcons = findViewById(R.id.quick_qs_status_icons);
+ mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
updateResources();
- // Set light text on the header icons because they will always be on a black background
- int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground);
Rect tintArea = new Rect(0, 0, 0, 0);
+ int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground);
+ float intensity = colorForeground == Color.WHITE ? 0 : 1;
+ int fillColor = fillColorForIntensity(intensity, getContext());
+
+ // Set light text on the header icons because they will always be on a black background
applyDarkness(R.id.clock, tintArea, 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
+ applyDarkness(id.signal_cluster, tintArea, intensity, colorForeground);
+
+ // Set the correct tint for the status icons so they contrast
+ mIconManager.setTint(fillColor);
BatteryMeterView battery = findViewById(R.id.battery);
battery.setFillColor(Color.WHITE);
@@ -96,6 +110,13 @@
}
}
+ private int fillColorForIntensity(float intensity, Context context) {
+ if (intensity == 0) {
+ return context.getColor(R.color.light_mode_icon_color_dual_tone_fill);
+ }
+ return context.getColor(R.color.dark_mode_icon_color_dual_tone_fill);
+ }
+
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -109,6 +130,13 @@
}
private void updateResources() {
+ updateAlphaAnimator();
+ }
+
+ private void updateAlphaAnimator() {
+ mAlphaAnimator = new TouchAnimator.Builder()
+ .addFloat(mQuickQsStatusIcons, "alpha", 1, 0)
+ .build();
}
public int getCollapsedHeight() {
@@ -127,6 +155,9 @@
}
public void setExpansion(float headerExpansionFraction) {
+ if (mAlphaAnimator != null ) {
+ mAlphaAnimator.setPosition(headerExpansionFraction);
+ }
}
@Override
@@ -142,6 +173,7 @@
@Override
public void onAttachedToWindow() {
SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
+ Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
}
@Override
@@ -149,17 +181,10 @@
public void onDetachedFromWindow() {
setListening(false);
SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this);
+ Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager);
super.onDetachedFromWindow();
}
- @Override
- public void onClick(View v) {
- if (v == mDate) {
- Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(new Intent(
- AlarmClock.ACTION_SHOW_ALARMS), 0);
- }
- }
-
public void setListening(boolean listening) {
if (listening == mListening) {
return;
@@ -168,6 +193,14 @@
mListening = listening;
}
+ @Override
+ public void onClick(View v) {
+ if(v == mDate){
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(new Intent(
+ AlarmClock.ACTION_SHOW_ALARMS),0);
+ }
+ }
+
public void updateEverything() {
post(() -> setClickable(false));
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 4ceace3..2ee66d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -177,29 +177,26 @@
state.value = newValue;
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.slash.isSlashed = !state.value;
+ state.label = getTileLabel();
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME);
switch (zen) {
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
- state.label = mContext.getString(R.string.quick_settings_dnd_priority_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_dnd_priority_on);
break;
case Global.ZEN_MODE_NO_INTERRUPTIONS:
state.icon = TOTAL_SILENCE;
- state.label = mContext.getString(R.string.quick_settings_dnd_none_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_dnd_none_on);
break;
case ZEN_MODE_ALARMS:
state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
- state.label = mContext.getString(R.string.quick_settings_dnd_alarms_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_dnd_alarms_on);
break;
default:
state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
- state.label = mContext.getString(R.string.quick_settings_dnd_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_dnd);
break;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 910b6b1..e1b58fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -37,7 +38,7 @@
/** Quick settings tile: Hotspot **/
public class HotspotTile extends QSTileImpl<AirplaneBooleanState> {
static final Intent TETHER_SETTINGS = new Intent().setComponent(new ComponentName(
- "com.android.settings", "com.android.settings.TetherSettings"));
+ "com.android.settings", "com.android.settings.TetherSettings"));
private final Icon mEnabledStatic = ResourceIcon.get(R.drawable.ic_hotspot);
private final Icon mUnavailable = ResourceIcon.get(R.drawable.ic_hotspot_unavailable);
@@ -115,11 +116,19 @@
state.label = mContext.getString(R.string.quick_settings_hotspot_label);
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_CONFIG_TETHERING);
- if (arg instanceof Boolean) {
- state.value = (boolean) arg;
+
+ final int numConnectedDevices;
+ if (arg instanceof CallbackInfo) {
+ CallbackInfo info = (CallbackInfo) arg;
+ state.value = info.enabled;
+ numConnectedDevices = info.numConnectedDevices;
} else {
state.value = mController.isHotspotEnabled();
+ numConnectedDevices = mController.getNumConnectedDevices();
}
+
+ state.secondaryLabel = getSecondaryLabel(state.value, numConnectedDevices);
+
state.icon = mEnabledStatic;
state.isAirplaneMode = mAirplaneMode.getValue() != 0;
state.isTransient = mController.isHotspotTransient();
@@ -133,6 +142,18 @@
: state.value || state.isTransient ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
+ @Nullable
+ private String getSecondaryLabel(boolean enabled, int numConnectedDevices) {
+ if (numConnectedDevices > 0 && enabled) {
+ return mContext.getResources().getQuantityString(
+ R.plurals.quick_settings_hotspot_num_devices,
+ numConnectedDevices,
+ numConnectedDevices);
+ }
+
+ return null;
+ }
+
@Override
public int getMetricsCategory() {
return MetricsEvent.QS_HOTSPOT;
@@ -148,9 +169,30 @@
}
private final class Callback implements HotspotController.Callback {
+ final CallbackInfo mCallbackInfo = new CallbackInfo();
+
@Override
- public void onHotspotChanged(boolean enabled) {
- refreshState(enabled);
+ public void onHotspotChanged(boolean enabled, int numConnectedDevices) {
+ mCallbackInfo.enabled = enabled;
+ mCallbackInfo.numConnectedDevices = numConnectedDevices;
+ refreshState(mCallbackInfo);
}
- };
+ }
+
+ /**
+ * Holder for any hotspot state info that needs to passed from the callback to
+ * {@link #handleUpdateState(State, Object)}.
+ */
+ protected static final class CallbackInfo {
+ boolean enabled;
+ int numConnectedDevices;
+
+ @Override
+ public String toString() {
+ return new StringBuilder("CallbackInfo[")
+ .append("enabled=").append(enabled)
+ .append(",numConnectedDevices=").append(numConnectedDevices)
+ .append(']').toString();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 5f7d6fb..e098fd8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -83,7 +83,7 @@
@Override
public CharSequence getTileLabel() {
- return mContext.getString(R.string.quick_settings_work_mode_label);
+ return mContext.getString(R.string.quick_settings_work_mode_on_label);
}
@Override
@@ -98,16 +98,17 @@
state.value = mProfileController.isWorkModeEnabled();
}
- state.label = mContext.getString(R.string.quick_settings_work_mode_label);
state.icon = mIcon;
if (state.value) {
state.slash.isSlashed = false;
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_work_mode_on);
+ state.label = mContext.getString(R.string.quick_settings_work_mode_on_label);
} else {
state.slash.isSlashed = true;
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_work_mode_off);
+ state.label = mContext.getString(R.string.quick_settings_work_mode_off_label);
}
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java
new file mode 100644
index 0000000..6c553de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+
+import android.annotation.TargetApi;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.RippleDrawable;
+import android.os.Build;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.Prefs;
+import com.android.systemui.R;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+/**
+ * Shows onboarding for the new recents interaction in P (codenamed quickstep).
+ */
+@TargetApi(Build.VERSION_CODES.P)
+public class SwipeUpOnboarding {
+
+ private static final String TAG = "SwipeUpOnboarding";
+ private static final boolean RESET_PREFS_FOR_DEBUG = false;
+ private static final long SHOW_DELAY_MS = 500;
+ private static final long SHOW_HIDE_DURATION_MS = 300;
+ // Don't show the onboarding until the user has launched this number of apps.
+ private static final int SHOW_ON_APP_LAUNCH = 2;
+
+ private final Context mContext;
+ private final WindowManager mWindowManager;
+ private final View mLayout;
+ private final TextView mTextView;
+ private final ImageView mDismissView;
+ private final ColorDrawable mBackgroundDrawable;
+ private final int mDarkBackgroundColor;
+ private final int mLightBackgroundColor;
+ private final int mDarkContentColor;
+ private final int mLightContentColor;
+ private final RippleDrawable mDarkRipple;
+ private final RippleDrawable mLightRipple;
+
+ private boolean mTaskListenerRegistered;
+ private ComponentName mLauncherComponent;
+ private boolean mLayoutAttachedToWindow;
+ private boolean mBackgroundIsLight;
+
+ private final SysUiTaskStackChangeListener mTaskListener = new SysUiTaskStackChangeListener() {
+ @Override
+ public void onTaskStackChanged() {
+ ActivityManager.RunningTaskInfo info = ActivityManagerWrapper.getInstance()
+ .getRunningTask(ACTIVITY_TYPE_UNDEFINED /* ignoreActivityType */);
+ int activityType = info.configuration.windowConfiguration.getActivityType();
+ int numAppsLaunched = Prefs.getInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0);
+ if (activityType == ACTIVITY_TYPE_STANDARD) {
+ numAppsLaunched++;
+ if (numAppsLaunched >= SHOW_ON_APP_LAUNCH) {
+ show();
+ } else {
+ Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, numAppsLaunched);
+ }
+ } else {
+ String runningPackage = info.topActivity.getPackageName();
+ // TODO: use callback from the overview proxy service to handle this case
+ if (runningPackage.equals(mLauncherComponent.getPackageName())
+ && activityType == ACTIVITY_TYPE_RECENTS) {
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, true);
+ onDisconnectedFromLauncher();
+ } else {
+ hide(false);
+ }
+ }
+ }
+ };
+
+ private final View.OnAttachStateChangeListener mOnAttachStateChangeListener
+ = new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ if (view == mLayout) {
+ mLayoutAttachedToWindow = true;
+ }
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ if (view == mLayout) {
+ mLayoutAttachedToWindow = false;
+ }
+ }
+ };
+
+ public SwipeUpOnboarding(Context context) {
+ mContext = context;
+ final Resources res = context.getResources();
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_swipe_up_onboarding, null);
+ mTextView = (TextView) mLayout.findViewById(R.id.onboarding_text);
+ mDismissView = (ImageView) mLayout.findViewById(R.id.dismiss);
+ mDarkBackgroundColor = res.getColor(android.R.color.background_dark);
+ mLightBackgroundColor = res.getColor(android.R.color.background_light);
+ mDarkContentColor = res.getColor(R.color.primary_text_default_material_light);
+ mLightContentColor = res.getColor(R.color.primary_text_default_material_dark);
+ mDarkRipple = new RippleDrawable(res.getColorStateList(R.color.ripple_material_light),
+ null, null);
+ mLightRipple = new RippleDrawable(res.getColorStateList(R.color.ripple_material_dark),
+ null, null);
+ mBackgroundDrawable = new ColorDrawable(mDarkBackgroundColor);
+
+ mLayout.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+ mLayout.setBackground(mBackgroundDrawable);
+ mDismissView.setOnClickListener(v -> hide(true));
+
+ if (RESET_PREFS_FOR_DEBUG) {
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false);
+ Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0);
+ }
+ }
+
+ public void onConnectedToLauncher(ComponentName launcherComponent) {
+ mLauncherComponent = launcherComponent;
+ boolean alreadyLearnedSwipeUpForRecents = Prefs.getBoolean(mContext,
+ Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false);
+ if (!mTaskListenerRegistered && !alreadyLearnedSwipeUpForRecents) {
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
+ mTaskListenerRegistered = true;
+ }
+ }
+
+ public void onDisconnectedFromLauncher() {
+ if (mTaskListenerRegistered) {
+ ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskListener);
+ mTaskListenerRegistered = false;
+ }
+ hide(false);
+ }
+
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ if (newConfiguration.orientation != Configuration.ORIENTATION_PORTRAIT) {
+ hide(false);
+ }
+ }
+
+ public void show() {
+ // Only show in portrait.
+ int orientation = mContext.getResources().getConfiguration().orientation;
+ if (!mLayoutAttachedToWindow && orientation == Configuration.ORIENTATION_PORTRAIT) {
+ mLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ mWindowManager.addView(mLayout, getWindowLayoutParams());
+ int layoutHeight = mLayout.getHeight();
+ if (layoutHeight == 0) {
+ mLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ layoutHeight = mLayout.getMeasuredHeight();
+ }
+ mLayout.setTranslationY(layoutHeight);
+ mLayout.setAlpha(0);
+ mLayout.animate()
+ .translationY(0)
+ .alpha(1f)
+ .withLayer()
+ .setStartDelay(SHOW_DELAY_MS)
+ .setDuration(SHOW_HIDE_DURATION_MS)
+ .setInterpolator(new DecelerateInterpolator())
+ .start();
+ }
+ }
+
+ public void hide(boolean animate) {
+ if (mLayoutAttachedToWindow) {
+ if (animate) {
+ mLayout.animate()
+ .translationY(mLayout.getHeight())
+ .alpha(0f)
+ .withLayer()
+ .setDuration(SHOW_HIDE_DURATION_MS)
+ .setInterpolator(new AccelerateInterpolator())
+ .withEndAction(() -> mWindowManager.removeView(mLayout))
+ .start();
+ } else {
+ mWindowManager.removeView(mLayout);
+ }
+ }
+ }
+
+ public void setContentDarkIntensity(float contentDarkIntensity) {
+ boolean backgroundIsLight = contentDarkIntensity > 0.5f;
+ if (backgroundIsLight != mBackgroundIsLight) {
+ mBackgroundIsLight = backgroundIsLight;
+ mBackgroundDrawable.setColor(mBackgroundIsLight
+ ? mLightBackgroundColor : mDarkBackgroundColor);
+ int contentColor = mBackgroundIsLight ? mDarkContentColor : mLightContentColor;
+ mTextView.setTextColor(contentColor);
+ mTextView.getCompoundDrawables()[3].setColorFilter(contentColor,
+ PorterDuff.Mode.SRC_IN);
+ mDismissView.setColorFilter(contentColor);
+ mDismissView.setBackground(mBackgroundIsLight ? mDarkRipple : mLightRipple);
+ }
+ }
+
+ private WindowManager.LayoutParams getWindowLayoutParams() {
+ int flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG,
+ flags,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.setTitle("SwipeUpOnboarding");
+ lp.gravity = Gravity.BOTTOM;
+ return lp;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 675aa8f..0132fa8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -185,7 +185,7 @@
// The public notification will show similar info but with the actual screenshot omitted
mPublicNotificationBuilder =
- new Notification.Builder(context, NotificationChannels.SCREENSHOTS)
+ new Notification.Builder(context, NotificationChannels.SCREENSHOTS_HEADSUP)
.setContentTitle(r.getString(R.string.screenshot_saving_title))
.setContentText(r.getString(R.string.screenshot_saving_text))
.setSmallIcon(R.drawable.stat_notify_image)
@@ -196,7 +196,8 @@
com.android.internal.R.color.system_notification_accent_color));
SystemUI.overrideNotificationAppName(context, mPublicNotificationBuilder);
- mNotificationBuilder = new Notification.Builder(context, NotificationChannels.SCREENSHOTS)
+ mNotificationBuilder = new Notification.Builder(context,
+ NotificationChannels.SCREENSHOTS_HEADSUP)
.setTicker(r.getString(R.string.screenshot_saving_ticker)
+ (mTickerAddSpace ? " " : ""))
.setContentTitle(r.getString(R.string.screenshot_saving_title))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index c6abcf2..79e9f7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.graphics.Rect;
+import android.hardware.fingerprint.IFingerprintDialogReceiver;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -83,6 +84,12 @@
private static final int MSG_SHOW_SHUTDOWN_UI = 36 << MSG_SHIFT;
private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR = 37 << MSG_SHIFT;
private static final int MSG_ROTATION_PROPOSAL = 38 << MSG_SHIFT;
+ private static final int MSG_FINGERPRINT_SHOW = 39 << MSG_SHIFT;
+ private static final int MSG_FINGERPRINT_AUTHENTICATED = 40 << MSG_SHIFT;
+ private static final int MSG_FINGERPRINT_HELP = 41 << MSG_SHIFT;
+ private static final int MSG_FINGERPRINT_ERROR = 42 << MSG_SHIFT;
+ private static final int MSG_FINGERPRINT_HIDE = 43 << MSG_SHIFT;
+ private static final int MSG_SHOW_CHARGING_ANIMATION = 44 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -144,7 +151,15 @@
default void handleShowGlobalActionsMenu() { }
default void handleShowShutdownUi(boolean isReboot, String reason) { }
+ default void showChargingAnimation(int batteryLevel) { }
+
default void onRotationProposal(int rotation, boolean isValid) { }
+
+ default void showFingerprintDialog(Bundle bundle, IFingerprintDialogReceiver receiver) { }
+ default void onFingerprintAuthenticated() { }
+ default void onFingerprintHelp(String message) { }
+ default void onFingerprintError(String error) { }
+ default void hideFingerprintDialog() { }
}
@VisibleForTesting
@@ -462,6 +477,13 @@
}
@Override
+ public void showChargingAnimation(int batteryLevel) {
+ mHandler.removeMessages(MSG_SHOW_CHARGING_ANIMATION);
+ mHandler.obtainMessage(MSG_SHOW_CHARGING_ANIMATION, batteryLevel, 0)
+ .sendToTarget();
+ }
+
+ @Override
public void onProposedRotationChanged(int rotation, boolean isValid) {
synchronized (mLock) {
mHandler.removeMessages(MSG_ROTATION_PROPOSAL);
@@ -470,6 +492,45 @@
}
}
+ @Override
+ public void showFingerprintDialog(Bundle bundle, IFingerprintDialogReceiver receiver) {
+ synchronized (mLock) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = bundle;
+ args.arg2 = receiver;
+ mHandler.obtainMessage(MSG_FINGERPRINT_SHOW, args)
+ .sendToTarget();
+ }
+ }
+
+ @Override
+ public void onFingerprintAuthenticated() {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_FINGERPRINT_AUTHENTICATED).sendToTarget();
+ }
+ }
+
+ @Override
+ public void onFingerprintHelp(String message) {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_FINGERPRINT_HELP, message).sendToTarget();
+ }
+ }
+
+ @Override
+ public void onFingerprintError(String error) {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_FINGERPRINT_ERROR, error).sendToTarget();
+ }
+ }
+
+ @Override
+ public void hideFingerprintDialog() {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_FINGERPRINT_HIDE).sendToTarget();
+ }
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -671,6 +732,41 @@
mCallbacks.get(i).onRotationProposal(msg.arg1, msg.arg2 != 0);
}
break;
+ case MSG_FINGERPRINT_SHOW:
+ mHandler.removeMessages(MSG_FINGERPRINT_ERROR);
+ mHandler.removeMessages(MSG_FINGERPRINT_HELP);
+ mHandler.removeMessages(MSG_FINGERPRINT_AUTHENTICATED);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).showFingerprintDialog(
+ (Bundle)((SomeArgs)msg.obj).arg1,
+ (IFingerprintDialogReceiver)((SomeArgs)msg.obj).arg2);
+ }
+ break;
+ case MSG_FINGERPRINT_AUTHENTICATED:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onFingerprintAuthenticated();
+ }
+ break;
+ case MSG_FINGERPRINT_HELP:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onFingerprintHelp((String) msg.obj);
+ }
+ break;
+ case MSG_FINGERPRINT_ERROR:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onFingerprintError((String) msg.obj);
+ }
+ break;
+ case MSG_FINGERPRINT_HIDE:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).hideFingerprintDialog();
+ }
+ break;
+ case MSG_SHOW_CHARGING_ANIMATION:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).showChargingAnimation(msg.arg1);
+ }
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 64df92c..a4c17e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
+import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.Log;
@@ -31,6 +32,7 @@
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.NotificationColorUtil;
@@ -42,6 +44,7 @@
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.RemoteInputView;
+import com.android.systemui.statusbar.policy.SmartReplyView;
/**
* A frame layout containing the actual payload of the notification, including the contracted,
@@ -72,6 +75,7 @@
private RemoteInputView mExpandedRemoteInput;
private RemoteInputView mHeadsUpRemoteInput;
+ private SmartReplyView mExpandedSmartReplyView;
private NotificationViewWrapper mContractedWrapper;
private NotificationViewWrapper mExpandedWrapper;
@@ -1125,7 +1129,7 @@
if (mAmbientChild != null) {
mAmbientWrapper.onContentUpdated(entry.row);
}
- applyRemoteInput(entry);
+ applyRemoteInputAndSmartReply(entry);
updateLegacy();
mForceSelectNextLayout = true;
setDark(mDark, false /* animate */, 0 /* delay */);
@@ -1157,20 +1161,34 @@
}
}
- private void applyRemoteInput(final NotificationData.Entry entry) {
+ private void applyRemoteInputAndSmartReply(final NotificationData.Entry entry) {
if (mRemoteInputController == null) {
return;
}
+ boolean enableSmartReplies = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ENABLE_SMART_REPLIES_IN_NOTIFICATIONS, 0) != 0;
+
boolean hasRemoteInput = false;
+ RemoteInput remoteInputWithChoices = null;
+ PendingIntent pendingIntentWithChoices = null;
Notification.Action[] actions = entry.notification.getNotification().actions;
if (actions != null) {
for (Notification.Action a : actions) {
if (a.getRemoteInputs() != null) {
for (RemoteInput ri : a.getRemoteInputs()) {
- if (ri.getAllowFreeFormInput()) {
+ boolean showRemoteInputView = ri.getAllowFreeFormInput();
+ boolean showSmartReplyView = enableSmartReplies && ri.getChoices() != null
+ && ri.getChoices().length > 0;
+ if (showRemoteInputView) {
hasRemoteInput = true;
+ }
+ if (showSmartReplyView) {
+ remoteInputWithChoices = ri;
+ pendingIntentWithChoices = a.actionIntent;
+ }
+ if (showRemoteInputView || showSmartReplyView) {
break;
}
}
@@ -1178,6 +1196,11 @@
}
}
+ applyRemoteInput(entry, hasRemoteInput);
+ applySmartReplyView(remoteInputWithChoices, pendingIntentWithChoices);
+ }
+
+ private void applyRemoteInput(NotificationData.Entry entry, boolean hasRemoteInput) {
View bigContentView = mExpandedChild;
if (bigContentView != null) {
mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasRemoteInput,
@@ -1274,6 +1297,40 @@
return null;
}
+ private void applySmartReplyView(RemoteInput remoteInput, PendingIntent pendingIntent) {
+ mExpandedSmartReplyView = mExpandedChild == null ?
+ null : applySmartReplyView(mExpandedChild, remoteInput, pendingIntent);
+ }
+
+ private SmartReplyView applySmartReplyView(
+ View view, RemoteInput remoteInput, PendingIntent pendingIntent) {
+ View smartReplyContainerCandidate = view.findViewById(
+ com.android.internal.R.id.smart_reply_container);
+ if (!(smartReplyContainerCandidate instanceof LinearLayout)) {
+ return null;
+ }
+ LinearLayout smartReplyContainer = (LinearLayout) smartReplyContainerCandidate;
+ if (remoteInput == null || pendingIntent == null) {
+ smartReplyContainer.setVisibility(View.GONE);
+ return null;
+ }
+ SmartReplyView smartReplyView = null;
+ if (smartReplyContainer.getChildCount() == 0) {
+ smartReplyView = SmartReplyView.inflate(mContext, smartReplyContainer);
+ smartReplyContainer.addView(smartReplyView);
+ } else if (smartReplyContainer.getChildCount() == 1) {
+ View child = smartReplyContainer.getChildAt(0);
+ if (child instanceof SmartReplyView) {
+ smartReplyView = (SmartReplyView) child;
+ }
+ }
+ if (smartReplyView != null) {
+ smartReplyView.setRepliesFromRemoteInput(remoteInput, pendingIntent);
+ smartReplyContainer.setVisibility(View.VISIBLE);
+ }
+ return smartReplyView;
+ }
+
public void closeRemoteInput() {
if (mHeadsUpRemoteInput != null) {
mHeadsUpRemoteInput.close();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 149ec0b..36f9f6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -127,7 +127,7 @@
private final HotspotController.Callback mHotspotCallback = new Callback() {
@Override
- public void onHotspotChanged(boolean enabled) {
+ public void onHotspotChanged(boolean enabled, int numDevices) {
if (mAutoTracker.isAdded(HOTSPOT)) return;
if (enabled) {
mHost.addTile(HOTSPOT);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 368b36b..dc51b1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -60,6 +60,7 @@
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -830,10 +831,13 @@
// window in response to the orientation change.
Handler h = getView().getHandler();
Message msg = Message.obtain(h, () -> {
- // If the screen rotation changes while locked, update lock rotation to flow with
+
+ // If the screen rotation changes while locked, potentially update lock to flow with
// new screen rotation and hide any showing suggestions.
if (mRotationLockController.isRotationLocked()) {
- mRotationLockController.setRotationLockedAtAngle(true, rotation);
+ if (shouldOverrideUserLockPrefs(rotation)) {
+ mRotationLockController.setRotationLockedAtAngle(true, rotation);
+ }
setRotateSuggestionButtonState(false, true);
}
@@ -845,6 +849,12 @@
msg.setAsynchronous(true);
h.sendMessageAtFrontOfQueue(msg);
}
+
+ private boolean shouldOverrideUserLockPrefs(final int rotation) {
+ // Only override user prefs when returning to portrait.
+ // Don't let apps that force landscape or 180 alter user lock.
+ return rotation == Surface.ROTATION_0;
+ }
};
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 059ce92..9bef0ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -57,10 +57,11 @@
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.phone.NavGesture;
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
+import com.android.systemui.recents.SwipeUpOnboarding;
import com.android.systemui.stackdivider.Divider;
-import com.android.systemui.statusbar.policy.TintedKeyButtonDrawable;
import com.android.systemui.statusbar.policy.DeadZone;
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.statusbar.policy.TintedKeyButtonDrawable;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -124,6 +125,7 @@
private NavigationBarInflaterView mNavigationInflaterView;
private RecentsComponent mRecentsComponent;
private Divider mDivider;
+ private SwipeUpOnboarding mSwipeUpOnboarding;
private class NavTransitionListener implements TransitionListener {
private boolean mBackTransitioning;
@@ -206,6 +208,7 @@
private final OverviewProxyListener mOverviewProxyListener = isConnected -> {
setSlippery(!isConnected);
setDisabledFlags(mDisabledFlags, true);
+ setUpSwipeUpOnboarding(isConnected);
};
public NavigationBarView(Context context, AttributeSet attrs) {
@@ -237,6 +240,7 @@
new ButtonDispatcher(R.id.rotate_suggestion));
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
+ mSwipeUpOnboarding = new SwipeUpOnboarding(context);
}
public BarTransitions getBarTransitions() {
@@ -630,6 +634,9 @@
if (mGestureHelper != null) {
mGestureHelper.onDarkIntensityChange(intensity);
}
+ if (mSwipeUpOnboarding != null) {
+ mSwipeUpOnboarding.setContentDarkIntensity(intensity);
+ }
}
@Override
@@ -740,6 +747,7 @@
updateTaskSwitchHelper();
updateIcons(getContext(), mConfiguration, newConfig);
updateRecentsIcon();
+ mSwipeUpOnboarding.onConfigurationChanged(newConfig);
if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi
|| mConfiguration.getLayoutDirection() != newConfig.getLayoutDirection()) {
// If car mode or density changes, we need to reset the icons.
@@ -829,6 +837,7 @@
Dependency.get(PluginManager.class).addPluginListener(this,
NavGesture.class, false /* Only one */);
mOverviewProxyService.addCallback(mOverviewProxyListener);
+ setUpSwipeUpOnboarding(mOverviewProxyService.getProxy() != null);
}
@Override
@@ -839,6 +848,15 @@
mGestureHelper.destroy();
}
mOverviewProxyService.removeCallback(mOverviewProxyListener);
+ setUpSwipeUpOnboarding(false);
+ }
+
+ private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) {
+ if (connectedToOverviewProxy) {
+ mSwipeUpOnboarding.onConnectedToLauncher(mOverviewProxyService.getLauncherComponent());
+ } else {
+ mSwipeUpOnboarding.onDisconnectedFromLauncher();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 6857337..20b5018 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -665,7 +665,7 @@
private final HotspotController.Callback mHotspotCallback = new HotspotController.Callback() {
@Override
- public void onHotspotChanged(boolean enabled) {
+ public void onHotspotChanged(boolean enabled, int numDevices) {
mIconController.setIconVisibility(mSlotHotspot, enabled);
}
};
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 3b783c2..d13ecae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -85,6 +85,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
@@ -138,6 +139,7 @@
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.ActivityStarterDelegate;
import com.android.systemui.AutoReinflateContainer;
+import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.DemoMode;
import com.android.systemui.Dependency;
import com.android.systemui.EventLogTags;
@@ -1419,7 +1421,6 @@
mQSPanel.clickTile(tile);
}
-
private void updateClearAll() {
if (!mClearAllEnabled) {
return;
@@ -2422,6 +2423,18 @@
mask, fullscreenStackBounds, dockedStackBounds, sbModeChanged, mStatusBarMode);
}
+ @Override
+ public void showChargingAnimation(int batteryLevel) {
+ if (mDozing) {
+ // ambient
+ } else if (mKeyguardManager.isKeyguardLocked()) {
+ // lockscreen
+ } else {
+ WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
+ batteryLevel).show();
+ }
+ }
+
void touchAutoHide() {
// update transient bar autohide
if (mStatusBarMode == MODE_SEMI_TRANSPARENT || (mNavigationBar != null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
index 6457209..830b50e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
@@ -26,7 +26,9 @@
void setHotspotEnabled(boolean enabled);
boolean isHotspotSupported();
- public interface Callback {
- void onHotspotChanged(boolean enabled);
+ int getNumConnectedDevices();
+
+ interface Callback {
+ void onHotspotChanged(boolean enabled, int numDevices);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index 1ebb986..8792b4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -23,31 +23,35 @@
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
-import android.os.Handler;
import android.os.UserManager;
import android.util.Log;
+import com.android.systemui.Dependency;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-public class HotspotControllerImpl implements HotspotController {
+public class HotspotControllerImpl implements HotspotController, WifiManager.SoftApCallback {
private static final String TAG = "HotspotController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
- private final Receiver mReceiver = new Receiver();
+ private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+ private final WifiStateReceiver mWifiStateReceiver = new WifiStateReceiver();
private final ConnectivityManager mConnectivityManager;
+ private final WifiManager mWifiManager;
private final Context mContext;
private int mHotspotState;
+ private int mNumConnectedDevices;
private boolean mWaitingForCallback;
public HotspotControllerImpl(Context context) {
mContext = context;
- mConnectivityManager = (ConnectivityManager) context.getSystemService(
- Context.CONNECTIVITY_SERVICE);
+ mConnectivityManager =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
}
@Override
@@ -84,7 +88,8 @@
if (callback == null || mCallbacks.contains(callback)) return;
if (DEBUG) Log.d(TAG, "addCallback " + callback);
mCallbacks.add(callback);
- mReceiver.setListening(!mCallbacks.isEmpty());
+
+ updateWifiStateListeners(!mCallbacks.isEmpty());
}
}
@@ -94,7 +99,26 @@
if (DEBUG) Log.d(TAG, "removeCallback " + callback);
synchronized (mCallbacks) {
mCallbacks.remove(callback);
- mReceiver.setListening(!mCallbacks.isEmpty());
+
+ updateWifiStateListeners(!mCallbacks.isEmpty());
+ }
+ }
+
+ /**
+ * Updates the wifi state receiver to either start or stop listening to get updates to the
+ * hotspot status. Additionally starts listening to wifi manager state to track the number of
+ * connected devices.
+ *
+ * @param shouldListen whether we should start listening to various wifi statuses
+ */
+ private void updateWifiStateListeners(boolean shouldListen) {
+ mWifiStateReceiver.setListening(shouldListen);
+ if (shouldListen) {
+ mWifiManager.registerSoftApCallback(
+ this,
+ Dependency.get(Dependency.MAIN_HANDLER));
+ } else {
+ mWifiManager.unregisterSoftApCallback(this);
}
}
@@ -116,20 +140,55 @@
if (DEBUG) Log.d(TAG, "Starting tethering");
mConnectivityManager.startTethering(
ConnectivityManager.TETHERING_WIFI, false, callback);
- fireCallback(isHotspotEnabled());
+ fireHotspotChangedCallback(isHotspotEnabled());
} else {
mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
}
}
- private void fireCallback(boolean isEnabled) {
+ @Override
+ public int getNumConnectedDevices() {
+ return mNumConnectedDevices;
+ }
+
+ /**
+ * Sends a hotspot changed callback with the new enabled status. Wraps
+ * {@link #fireHotspotChangedCallback(boolean, int)} and assumes that the number of devices has
+ * not changed.
+ *
+ * @param enabled whether the hotspot is enabled
+ */
+ private void fireHotspotChangedCallback(boolean enabled) {
+ fireHotspotChangedCallback(enabled, mNumConnectedDevices);
+ }
+
+ /**
+ * Sends a hotspot changed callback with the new enabled status & the number of devices
+ * connected to the hotspot. Be careful when calling over multiple threads, especially if one of
+ * them is the main thread (as it can be blocked).
+ *
+ * @param enabled whether the hotspot is enabled
+ * @param numConnectedDevices number of devices connected to the hotspot
+ */
+ private void fireHotspotChangedCallback(boolean enabled, int numConnectedDevices) {
synchronized (mCallbacks) {
for (Callback callback : mCallbacks) {
- callback.onHotspotChanged(isEnabled);
+ callback.onHotspotChanged(enabled, numConnectedDevices);
}
}
}
+ @Override
+ public void onStateChanged(int state, int failureReason) {
+ // Do nothing - we don't care about changing anything here.
+ }
+
+ @Override
+ public void onNumClientsChanged(int numConnectedDevices) {
+ mNumConnectedDevices = numConnectedDevices;
+ fireHotspotChangedCallback(isHotspotEnabled(), numConnectedDevices);
+ }
+
private final class OnStartTetheringCallback extends
ConnectivityManager.OnStartTetheringCallback {
@Override
@@ -143,12 +202,15 @@
public void onTetheringFailed() {
if (DEBUG) Log.d(TAG, "onTetheringFailed");
mWaitingForCallback = false;
- fireCallback(isHotspotEnabled());
+ fireHotspotChangedCallback(isHotspotEnabled());
// TODO: Show error.
}
}
- private final class Receiver extends BroadcastReceiver {
+ /**
+ * Class to listen in on wifi state and update the hotspot state
+ */
+ private final class WifiStateReceiver extends BroadcastReceiver {
private boolean mRegistered;
public void setListening(boolean listening) {
@@ -170,8 +232,17 @@
int state = intent.getIntExtra(
WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_FAILED);
if (DEBUG) Log.d(TAG, "onReceive " + state);
+
+ // Update internal hotspot state for tracking before using any enabled/callback methods.
mHotspotState = state;
- fireCallback(mHotspotState == WifiManager.WIFI_AP_STATE_ENABLED);
+
+ if (!isHotspotEnabled()) {
+ // Reset num devices if the hotspot is no longer enabled so we don't get ghost
+ // counters.
+ mNumConnectedDevices = 0;
+ }
+
+ fireHotspotChangedCallback(isHotspotEnabled());
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 4ee4ef4..0b666a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -16,11 +16,12 @@
package com.android.systemui.statusbar.policy;
+import static com.android.settingslib.Utils.updateLocationEnabled;
+
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -28,19 +29,14 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.Settings;
import android.support.annotation.VisibleForTesting;
-
-import com.android.systemui.R;
import com.android.systemui.util.Utils;
-
import java.util.ArrayList;
import java.util.List;
-import static com.android.settingslib.Utils.updateLocationMode;
-
/**
* A controller to manage changes of location related states and update the views accordingly.
*/
@@ -101,32 +97,27 @@
* @return true if attempt to change setting was successful.
*/
public boolean setLocationEnabled(boolean enabled) {
+ // QuickSettings always runs as the owner, so specifically set the settings
+ // for the current foreground user.
int currentUserId = ActivityManager.getCurrentUser();
if (isUserLocationRestricted(currentUserId)) {
return false;
}
- final ContentResolver cr = mContext.getContentResolver();
// When enabling location, a user consent dialog will pop up, and the
// setting won't be fully enabled until the user accepts the agreement.
- int currentMode = Settings.Secure.getIntForUser(cr, Settings.Secure.LOCATION_MODE,
- Settings.Secure.LOCATION_MODE_OFF, currentUserId);
- int mode = enabled
- ? Settings.Secure.LOCATION_MODE_PREVIOUS : Settings.Secure.LOCATION_MODE_OFF;
- // QuickSettings always runs as the owner, so specifically set the settings
- // for the current foreground user.
- return updateLocationMode(mContext, currentMode, mode, currentUserId);
+ updateLocationEnabled(mContext, enabled, currentUserId);
+ return true;
}
/**
- * Returns true if location isn't disabled in settings.
+ * Returns true if location is enabled in settings.
*/
public boolean isLocationEnabled() {
- ContentResolver resolver = mContext.getContentResolver();
// QuickSettings always runs as the owner, so specifically retrieve the settings
// for the current foreground user.
- int mode = Settings.Secure.getIntForUser(resolver, Settings.Secure.LOCATION_MODE,
- Settings.Secure.LOCATION_MODE_OFF, ActivityManager.getCurrentUser());
- return mode != Settings.Secure.LOCATION_MODE_OFF;
+ LocationManager locationManager =
+ (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+ return locationManager.isLocationEnabledForUser(Process.myUserHandle());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index c377feb..b63c1da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -137,6 +137,7 @@
Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
results);
+ RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT);
mEditText.setEnabled(false);
mSendButton.setVisibility(INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 1dcdf63..2d829af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -52,6 +52,7 @@
results.putString(remoteInput.getResultKey(), choice.toString());
Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
RemoteInput.addResultsToIntent(new RemoteInput[]{remoteInput}, intent, results);
+ RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE);
try {
pendingIntent.send(context, 0, intent);
} catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index 87bc0e6..14d5c6f5 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -31,7 +31,8 @@
public class NotificationChannels extends SystemUI {
public static String ALERTS = "ALR";
- public static String SCREENSHOTS = "SCN";
+ public static String SCREENSHOTS_LEGACY = "SCN";
+ public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP";
public static String GENERAL = "GEN";
public static String STORAGE = "DSK";
public static String TVPIP = "TPP";
@@ -56,10 +57,6 @@
context.getString(R.string.notification_channel_alerts),
NotificationManager.IMPORTANCE_HIGH),
new NotificationChannel(
- SCREENSHOTS,
- context.getString(R.string.notification_channel_screenshot),
- NotificationManager.IMPORTANCE_LOW),
- new NotificationChannel(
GENERAL,
context.getString(R.string.notification_channel_general),
NotificationManager.IMPORTANCE_MIN),
@@ -69,9 +66,18 @@
isTv(context)
? NotificationManager.IMPORTANCE_DEFAULT
: NotificationManager.IMPORTANCE_LOW),
+ createScreenshotChannel(
+ context.getString(R.string.notification_channel_screenshot),
+ nm.getNotificationChannel(SCREENSHOTS_LEGACY)),
batteryChannel
));
+ // Delete older SS channel if present.
+ // Screenshots promoted to heads-up in P, this cleans up the lower priority channel from O.
+ // This line can be deleted in Q.
+ nm.deleteNotificationChannel(SCREENSHOTS_LEGACY);
+
+
if (isTv(context)) {
// TV specific notification channel for TV PIP controls.
// Importance should be {@link NotificationManager#IMPORTANCE_MAX} to have the highest
@@ -83,6 +89,40 @@
}
}
+ /**
+ * Set up screenshot channel, respecting any previously committed user settings on legacy
+ * channel.
+ * @return
+ */
+ @VisibleForTesting static NotificationChannel createScreenshotChannel(
+ String name, NotificationChannel legacySS) {
+ NotificationChannel screenshotChannel = new NotificationChannel(SCREENSHOTS_HEADSUP,
+ name, NotificationManager.IMPORTANCE_HIGH); // pop on screen
+
+ screenshotChannel.setSound(Uri.parse(""), // silent
+ new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build());
+
+ if (legacySS != null) {
+ // Respect any user modified fields from the old channel.
+ int userlock = legacySS.getUserLockedFields();
+ if ((userlock & NotificationChannel.USER_LOCKED_IMPORTANCE) != 0) {
+ screenshotChannel.setImportance(legacySS.getImportance());
+ }
+ if ((userlock & NotificationChannel.USER_LOCKED_SOUND) != 0) {
+ screenshotChannel.setSound(legacySS.getSound(), legacySS.getAudioAttributes());
+ }
+ if ((userlock & NotificationChannel.USER_LOCKED_VIBRATION) != 0) {
+ screenshotChannel.setVibrationPattern(legacySS.getVibrationPattern());
+ }
+ if ((userlock & NotificationChannel.USER_LOCKED_LIGHTS) != 0) {
+ screenshotChannel.setLightColor(legacySS.getLightColor());
+ }
+ // skip show_badge, irrelevant for system channel
+ }
+
+ return screenshotChannel;
+ }
+
@Override
public void start() {
createAll(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
index e0af9ba..e3c8503 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
@@ -22,6 +22,7 @@
import static com.android.settingslib.bluetooth.Utils.getBtClassDrawableWithDescription;
+import android.app.Dialog;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
@@ -30,6 +31,8 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.net.wifi.WifiManager;
@@ -43,12 +46,16 @@
import android.telecom.TelecomManager;
import android.util.Log;
import android.util.Pair;
+import android.view.Window;
+import android.view.WindowManager;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.systemui.Dependency;
+import com.android.systemui.HardwareUiLayout;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.statusbar.policy.BluetoothController;
import java.io.IOException;
@@ -58,7 +65,7 @@
import java.util.Comparator;
import java.util.List;
-public class OutputChooserDialog extends SystemUIDialog
+public class OutputChooserDialog extends Dialog
implements DialogInterface.OnDismissListener, OutputChooserLayout.Callback {
private static final String TAG = Util.logTag(OutputChooserDialog.class);
@@ -82,9 +89,11 @@
private Drawable mTvIcon;
private Drawable mSpeakerIcon;
private Drawable mSpeakerGroupIcon;
+ private HardwareUiLayout mHardwareLayout;
+ private final VolumeDialogController mController;
public OutputChooserDialog(Context context, MediaRouterWrapper router) {
- super(context);
+ super(context, com.android.systemui.R.style.qs_theme);
mContext = context;
mBluetoothController = Dependency.get(BluetoothController.class);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
@@ -98,6 +107,22 @@
final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
context.registerReceiver(mReceiver, filter);
+
+ mController = Dependency.get(VolumeDialogController.class);
+
+ // Window initialization
+ Window window = getWindow();
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
+ window.addFlags(
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
}
protected void setIsInCall(boolean inCall) {
@@ -112,6 +137,9 @@
setOnDismissListener(this::onDismiss);
mView = findViewById(R.id.output_chooser);
+ mHardwareLayout = HardwareUiLayout.get(mView);
+ mHardwareLayout.setOutsideTouchListener(view -> dismiss());
+ mHardwareLayout.setSwapOrientation(false);
mView.setCallback(this);
if (mIsInCall) {
@@ -151,6 +179,7 @@
MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
}
mBluetoothController.addCallback(mCallback);
+ mController.addCallback(mControllerCallbackH, mHandler);
isAttached = true;
}
@@ -158,6 +187,7 @@
public void onDetachedFromWindow() {
isAttached = false;
mRouter.removeCallback(mRouterCallback);
+ mController.removeCallback(mControllerCallbackH);
mBluetoothController.removeCallback(mCallback);
super.onDetachedFromWindow();
}
@@ -169,6 +199,38 @@
}
@Override
+ public void show() {
+ super.show();
+ mHardwareLayout.setTranslationX(getAnimTranslation());
+ mHardwareLayout.setAlpha(0);
+ mHardwareLayout.animate()
+ .alpha(1)
+ .translationX(0)
+ .setDuration(300)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .withEndAction(() -> getWindow().getDecorView().requestAccessibilityFocus())
+ .start();
+ }
+
+ @Override
+ public void dismiss() {
+ mHardwareLayout.setTranslationX(0);
+ mHardwareLayout.setAlpha(1);
+ mHardwareLayout.animate()
+ .alpha(0)
+ .translationX(getAnimTranslation())
+ .setDuration(300)
+ .withEndAction(() -> super.dismiss())
+ .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
+ .start();
+ }
+
+ private float getAnimTranslation() {
+ return getContext().getResources().getDimension(
+ com.android.systemui.R.dimen.output_chooser_panel_width) / 2;
+ }
+
+ @Override
public void onDetailItemClick(OutputChooserLayout.Item item) {
if (item == null || item.tag == null) return;
if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
@@ -416,4 +478,41 @@
}
}
};
+
+ private final VolumeDialogController.Callbacks mControllerCallbackH
+ = new VolumeDialogController.Callbacks() {
+ @Override
+ public void onShowRequested(int reason) {
+ dismiss();
+ }
+
+ @Override
+ public void onDismissRequested(int reason) {}
+
+ @Override
+ public void onScreenOff() {
+ dismiss();
+ }
+
+ @Override
+ public void onStateChanged(VolumeDialogController.State state) {}
+
+ @Override
+ public void onLayoutDirectionChanged(int layoutDirection) {}
+
+ @Override
+ public void onConfigurationChanged() {}
+
+ @Override
+ public void onShowVibrateHint() {}
+
+ @Override
+ public void onShowSilentHint() {}
+
+ @Override
+ public void onShowSafetyWarning(int flags) {}
+
+ @Override
+ public void onAccessibilityModeChanged(Boolean showA11yStream) {}
+ };
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index e76bf57..385438c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -368,16 +368,16 @@
mController.setActiveStream(row.stream);
if (row.stream == AudioManager.STREAM_RING) {
final boolean hasVibrator = mController.hasVibrator();
- if (mState.ringerModeExternal == AudioManager.RINGER_MODE_NORMAL) {
+ if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
if (hasVibrator) {
- mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, true);
+ mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
} else {
final boolean wasZero = row.ss.level == 0;
mController.setStreamVolume(stream,
wasZero ? row.lastAudibleLevel : 0);
}
} else {
- mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, true);
+ mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
if (row.ss.level == 0) {
mController.setStreamVolume(stream, 1);
}
@@ -403,15 +403,15 @@
return;
}
final boolean hasVibrator = mController.hasVibrator();
- if (mState.ringerModeExternal == AudioManager.RINGER_MODE_NORMAL) {
+ if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
if (hasVibrator) {
- mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, true);
+ mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
} else {
final boolean wasZero = ss.level == 0;
mController.setStreamVolume(AudioManager.STREAM_RING, wasZero ? 1 : 0);
}
} else {
- mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, true);
+ mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
if (ss.level == 0) {
mController.setStreamVolume(AudioManager.STREAM_RING, 1);
}
@@ -552,7 +552,7 @@
if (ss == null) {
return;
}
- switch (mState.ringerModeExternal) {
+ switch (mState.ringerModeInternal) {
case AudioManager.RINGER_MODE_VIBRATE:
mRingerStatus.setText(R.string.volume_ringer_status_vibrate);
mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate);
@@ -653,9 +653,9 @@
final boolean isAlarmStream = row.stream == AudioManager.STREAM_ALARM;
final boolean isMusicStream = row.stream == AudioManager.STREAM_MUSIC;
final boolean isRingVibrate = isRingStream
- && mState.ringerModeExternal == AudioManager.RINGER_MODE_VIBRATE;
+ && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
final boolean isRingSilent = isRingStream
- && mState.ringerModeExternal == AudioManager.RINGER_MODE_SILENT;
+ && mState.ringerModeInternal == AudioManager.RINGER_MODE_SILENT;
final boolean isZenAlarms = mState.zenMode == Global.ZEN_MODE_ALARMS;
final boolean isZenNone = mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
final boolean zenMuted = isZenAlarms ? (isRingStream || isSystemStream)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java
index 1c9cbc1..368194e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
+import android.util.Slog;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
@@ -127,17 +128,26 @@
rotate(mChild, from, to, true);
ViewGroup rows = mChild.findViewById(R.id.volume_dialog_rows);
rotate(rows, from, to, true);
+ swapOrientation((LinearLayout) rows);
int rowCount = rows.getChildCount();
for (int i = 0; i < rowCount; i++) {
- View child = rows.getChildAt(i);
+ View row = rows.getChildAt(i);
if (to == ROTATION_SEASCAPE) {
- rotateSeekBars(to, 0);
+ rotateSeekBars(row, to, 180);
} else if (to == ROTATION_LANDSCAPE) {
- rotateSeekBars(to, 180);
+ rotateSeekBars(row, to, 0);
} else {
- rotateSeekBars(to, 270);
+ rotateSeekBars(row, to, 270);
}
- rotate(child, from, to, true);
+ rotate(row, from, to, true);
+ }
+ }
+
+ private void swapOrientation(LinearLayout layout) {
+ if(layout.getOrientation() == LinearLayout.HORIZONTAL) {
+ layout.setOrientation(LinearLayout.VERTICAL);
+ } else {
+ layout.setOrientation(LinearLayout.HORIZONTAL);
}
}
@@ -152,13 +162,13 @@
v.setLayoutParams(params);
}
- private void rotateSeekBars(int to, int rotation) {
- SeekBar seekbar = mChild.findViewById(R.id.volume_row_slider);
+ private void rotateSeekBars(View row, int to, int rotation) {
+ SeekBar seekbar = row.findViewById(R.id.volume_row_slider);
if (seekbar != null) {
seekbar.setRotation((float) rotation);
}
- View parent = mChild.findViewById(R.id.volume_row_slider_frame);
+ View parent = row.findViewById(R.id.volume_row_slider_frame);
swapDimens(parent);
ViewGroup.LayoutParams params = seekbar.getLayoutParams();
ViewGroup.LayoutParams parentParams = parent.getLayoutParams();
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 859dc2f..f5e079c 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -47,6 +47,7 @@
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
<uses-permission android:name="android.permission.REAL_GET_TASKS" />
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
+ <uses-permission android:name="android.permission.NETWORK_SETTINGS" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java
new file mode 100644
index 0000000..8e0426a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.chooser;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.Binder;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import com.android.systemui.chooser.ChooserHelper;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ChooserHelperTest extends SysuiTestCase {
+
+ @Test
+ public void testOnChoose_CallsStartActivityAsCallerWithToken() {
+ final Intent intent = new Intent();
+ final Binder token = new Binder();
+ intent.putExtra(ActivityManager.EXTRA_PERMISSION_TOKEN, token);
+
+ final Activity mockActivity = mock(Activity.class);
+ when(mockActivity.getIntent()).thenReturn(intent);
+
+ ChooserHelper.onChoose(mockActivity);
+ verify(mockActivity, times(1)).startActivityAsCaller(
+ any(), any(), eq(token), anyBoolean(), anyInt());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java
new file mode 100644
index 0000000..76a3c95
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/** A simple receiver to wait for broadcast intents in tests. */
+public class BlockingQueueIntentReceiver extends BroadcastReceiver {
+ private final BlockingQueue<Intent> mQueue = new ArrayBlockingQueue<Intent>(1);
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mQueue.add(intent);
+ }
+
+ public Intent waitForIntent() throws InterruptedException {
+ return mQueue.poll(10, TimeUnit.SECONDS);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
new file mode 100644
index 0000000..63920a4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ShortcutManager;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.widget.EditText;
+import android.widget.ImageButton;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationTestHelper;
+import com.android.systemui.statusbar.RemoteInputController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class RemoteInputViewTest extends SysuiTestCase {
+
+ private static final String TEST_RESULT_KEY = "test_result_key";
+ private static final String TEST_REPLY = "hello";
+ private static final String TEST_ACTION = "com.android.ACTION";
+
+ @Mock private RemoteInputController mController;
+ @Mock private ShortcutManager mShortcutManager;
+ private BlockingQueueIntentReceiver mReceiver;
+ private RemoteInputView mView;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mReceiver = new BlockingQueueIntentReceiver();
+ mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION));
+
+ // Avoid SecurityException RemoteInputView#sendRemoteInput().
+ mContext.addMockSystemService(ShortcutManager.class, mShortcutManager);
+
+ ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow();
+ mView = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+ }
+
+ @Test
+ public void testSendRemoteInput_intentContainsResultsAndSource() throws InterruptedException {
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
+ new Intent(TEST_ACTION), 0);
+ RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).build();
+
+ mView.setPendingIntent(pendingIntent);
+ mView.setRemoteInput(new RemoteInput[]{input}, input);
+ mView.focus();
+
+ EditText editText = mView.findViewById(R.id.remote_input_text);
+ editText.setText(TEST_REPLY);
+ ImageButton sendButton = mView.findViewById(R.id.remote_input_send);
+ sendButton.performClick();
+
+ Intent resultIntent = mReceiver.waitForIntent();
+ assertEquals(TEST_REPLY,
+ RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
+ assertEquals(RemoteInput.SOURCE_FREE_FORM_INPUT,
+ RemoteInput.getResultsSource(resultIntent));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
new file mode 100644
index 0000000..0c3637d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class SmartReplyViewTest extends SysuiTestCase {
+
+ private static final String TEST_RESULT_KEY = "test_result_key";
+ private static final String TEST_ACTION = "com.android.ACTION";
+ private static final String[] TEST_CHOICES = new String[]{"Hello", "What's up?", "I'm here"};
+
+ private BlockingQueueIntentReceiver mReceiver;
+ private SmartReplyView mView;
+
+ @Before
+ public void setUp() {
+ mReceiver = new BlockingQueueIntentReceiver();
+ mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION));
+
+ mView = SmartReplyView.inflate(mContext, null);
+ }
+
+ @Test
+ public void testSendSmartReply_intentContainsResultsAndSource() throws InterruptedException {
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
+ new Intent(TEST_ACTION), 0);
+ RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).setChoices(
+ TEST_CHOICES).build();
+
+ mView.setRepliesFromRemoteInput(input, pendingIntent);
+
+ mView.getChildAt(2).performClick();
+
+ Intent resultIntent = mReceiver.waitForIntent();
+ assertEquals(TEST_CHOICES[2],
+ RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
+ assertEquals(RemoteInput.SOURCE_CHOICE, RemoteInput.getResultsSource(resultIntent));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java
index 04bdc04..80dc2c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java
@@ -26,11 +26,14 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
+import android.net.Uri;
+import android.provider.Settings;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArraySet;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.NotificationChannels;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,7 +57,7 @@
public void testChannelSetup() {
Set<String> ALL_CHANNELS = new ArraySet<>(Arrays.asList(
NotificationChannels.ALERTS,
- NotificationChannels.SCREENSHOTS,
+ NotificationChannels.SCREENSHOTS_HEADSUP,
NotificationChannels.STORAGE,
NotificationChannels.GENERAL,
NotificationChannels.BATTERY
@@ -66,4 +69,52 @@
assertEquals(ALL_CHANNELS.size(), list.size());
list.forEach((chan) -> assertTrue(ALL_CHANNELS.contains(chan.getId())));
}
+
+ @Test
+ public void testChannelSetup_noLegacyScreenshot() {
+ // Assert old channel cleaned up.
+ // TODO: remove that code + this test after P.
+ NotificationChannels.createAll(mContext);
+ ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
+ verify(mMockNotificationManager).deleteNotificationChannel(
+ NotificationChannels.SCREENSHOTS_LEGACY);
+ }
+
+ @Test
+ public void testInheritFromLegacy_keepsUserLockedLegacySettings() {
+ NotificationChannel legacyChannel = new NotificationChannel("id", "oldName",
+ NotificationManager.IMPORTANCE_MIN);
+ legacyChannel.setImportance(NotificationManager.IMPORTANCE_NONE);;
+ legacyChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
+ legacyChannel.getAudioAttributes());
+ legacyChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE |
+ NotificationChannel.USER_LOCKED_SOUND);
+ NotificationChannel newChannel =
+ NotificationChannels.createScreenshotChannel("newName", legacyChannel);
+ // NONE importance user locked, so don't use HIGH for new channel.
+ assertEquals(NotificationManager.IMPORTANCE_NONE, newChannel.getImportance());
+ assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, newChannel.getSound());
+ }
+
+ @Test
+ public void testInheritFromLegacy_dropsUnlockedLegacySettings() {
+ NotificationChannel legacyChannel = new NotificationChannel("id", "oldName",
+ NotificationManager.IMPORTANCE_MIN);
+ NotificationChannel newChannel =
+ NotificationChannels.createScreenshotChannel("newName", legacyChannel);
+ assertEquals(Uri.EMPTY, newChannel.getSound());
+ assertEquals("newName", newChannel.getName());
+ // MIN importance not user locked, so HIGH wins out.
+ assertEquals(NotificationManager.IMPORTANCE_HIGH, newChannel.getImportance());
+ }
+
+ @Test
+ public void testInheritFromLegacy_noLegacyExists() {
+ NotificationChannel newChannel =
+ NotificationChannels.createScreenshotChannel("newName", null);
+ assertEquals(Uri.EMPTY, newChannel.getSound());
+ assertEquals("newName", newChannel.getName());
+ assertEquals(NotificationManager.IMPORTANCE_HIGH, newChannel.getImportance());
+ }
+
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java
index 5491147..016160a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java
@@ -44,4 +44,9 @@
public boolean isHotspotSupported() {
return false;
}
+
+ @Override
+ public int getNumConnectedDevices() {
+ return 0;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
index 537d606..de99d71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
@@ -38,6 +38,7 @@
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.statusbar.policy.BluetoothController;
import org.junit.After;
@@ -54,6 +55,8 @@
OutputChooserDialog mDialog;
@Mock
+ private VolumeDialogController mVolumeController;
+ @Mock
private BluetoothController mController;
@Mock
private WifiManager mWifiManager;
@@ -69,6 +72,7 @@
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
+ mVolumeController = mDependency.injectMockDependency(VolumeDialogController.class);
mController = mDependency.injectMockDependency(BluetoothController.class);
when(mWifiManager.isWifiEnabled()).thenReturn(true);
@@ -78,20 +82,10 @@
mDialog = new OutputChooserDialog(getContext(), mRouter);
}
- @After
- @UiThreadTest
- public void tearDown() throws Exception {
- mDialog.dismiss();
- }
-
- private void showDialog() {
- mDialog.show();
- }
-
@Test
@UiThreadTest
public void testClickMediaRouterItemConnectsMedia() {
- showDialog();
+ mDialog.show();
OutputChooserLayout.Item item = new OutputChooserLayout.Item();
item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER;
@@ -102,12 +96,13 @@
mDialog.onDetailItemClick(item);
verify(info, times(1)).select();
verify(mController, never()).connect(any());
+ mDialog.dismiss();
}
@Test
@UiThreadTest
public void testClickBtItemConnectsBt() {
- showDialog();
+ mDialog.show();
OutputChooserLayout.Item item = new OutputChooserLayout.Item();
item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_BT;
@@ -117,25 +112,28 @@
mDialog.onDetailItemClick(item);
verify(mController, times(1)).connect(any());
+ mDialog.dismiss();
}
@Test
@UiThreadTest
public void testTitleNotInCall() {
- showDialog();
+ mDialog.show();
assertTrue(((TextView) mDialog.findViewById(R.id.title))
.getText().toString().contains("Media"));
+ mDialog.dismiss();
}
@Test
@UiThreadTest
public void testTitleInCall() {
mDialog.setIsInCall(true);
- showDialog();
+ mDialog.show();
assertTrue(((TextView) mDialog.findViewById(R.id.title))
.getText().toString().contains("Phone"));
+ mDialog.dismiss();
}
@Test
@@ -155,4 +153,26 @@
verify(mRouter, times(1)).addCallback(any(), any(), anyInt());
}
+
+ @Test
+ @UiThreadTest
+ public void testRegisterCallbacks() {
+ mDialog.setIsInCall(false);
+ mDialog.onAttachedToWindow();
+
+ verify(mRouter, times(1)).addCallback(any(), any(), anyInt());
+ verify(mController, times(1)).addCallback(any());
+ verify(mVolumeController, times(1)).addCallback(any(), any());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testUnregisterCallbacks() {
+ mDialog.setIsInCall(false);
+ mDialog.onDetachedFromWindow();
+
+ verify(mRouter, times(1)).removeCallback(any());
+ verify(mController, times(1)).removeCallback(any());
+ verify(mVolumeController, times(1)).removeCallback(any());
+ }
}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 4144bbd..bfec88c 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4685,7 +4685,8 @@
// OS: O MR
AUTOFILL_SERVICE_DISABLED_SELF = 1135;
- // Counter showing how long it took (in ms) to show the autofill UI after a field was focused
+ // Reports how long it took to show the autofill UI after a field was focused
+ // Tag FIELD_AUTOFILL_DURATION: Duration in ms
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
// Package: Package of the autofill service
// OS: O MR
@@ -4724,6 +4725,9 @@
// logged when we cancel an app transition.
APP_TRANSITION_CANCELLED = 1144;
+ // Tag of a field representing a duration on autofill-related metrics.
+ FIELD_AUTOFILL_DURATION = 1145;
+
// ---- End O-MR1 Constants, all O-MR1 constants go above this line ----
// OPEN: Settings > Network & Internet > Mobile network
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 0a03b7f..0bc95f4 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -37,7 +37,9 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
+import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
import android.util.TypedValue;
@@ -154,6 +156,12 @@
MagnificationController magnificationController,
boolean detectTripleTap,
boolean detectShortcutTrigger) {
+ if (DEBUG_ALL) {
+ Log.i(LOG_TAG,
+ "MagnificationGestureHandler(detectTripleTap = " + detectTripleTap
+ + ", detectShortcutTrigger = " + detectShortcutTrigger + ")");
+ }
+
mMagnificationController = magnificationController;
mDelegatingState = new DelegatingState();
@@ -581,7 +589,7 @@
@VisibleForTesting boolean mShortcutTriggered;
- Handler mHandler = new Handler(this);
+ @VisibleForTesting Handler mHandler = new Handler(this);
public DetectingState(Context context) {
mLongTapMinDelay = ViewConfiguration.getLongPressTimeout();
@@ -756,11 +764,14 @@
@Override
public void clear() {
setShortcutTriggered(false);
- mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
- mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+ removePendingDelayedMessages();
clearDelayedMotionEvents();
}
+ private void removePendingDelayedMessages() {
+ mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
+ mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+ }
private void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent,
int policyFlags) {
@@ -811,7 +822,7 @@
void transitionToDelegatingStateAndClear() {
transitionTo(mDelegatingState);
sendDelayedMotionEvents();
- clear();
+ removePendingDelayedMessages();
}
private void onTripleTap(MotionEvent up) {
@@ -860,6 +871,7 @@
if (mShortcutTriggered == state) {
return;
}
+ if (DEBUG_DETECTING) Slog.i(LOG_TAG, "setShortcutTriggered(" + state + ")");
mShortcutTriggered = state;
mMagnificationController.setForceShowMagnifiableBounds(state);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 978ed25..0e2ca14 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -119,6 +119,7 @@
private final LocalLog mRequestsHistory = new LocalLog(20);
private final LocalLog mUiLatencyHistory = new LocalLog(20);
+ private final LocalLog mWtfHistory = new LocalLog(50);
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -310,7 +311,8 @@
AutofillManagerServiceImpl service = mServicesCache.get(resolvedUserId);
if (service == null) {
service = new AutofillManagerServiceImpl(mContext, mLock, mRequestsHistory,
- mUiLatencyHistory, resolvedUserId, mUi, mDisabledUsers.get(resolvedUserId));
+ mUiLatencyHistory, mWtfHistory, resolvedUserId, mUi,
+ mDisabledUsers.get(resolvedUserId));
mServicesCache.put(userId, service);
}
return service;
@@ -878,10 +880,12 @@
mUi.dump(pw);
}
if (showHistory) {
- pw.println("Requests history:");
+ pw.println(); pw.println("Requests history:"); pw.println();
mRequestsHistory.reverseDump(fd, pw, args);
- pw.println("UI latency history:");
+ pw.println(); pw.println("UI latency history:"); pw.println();
mUiLatencyHistory.reverseDump(fd, pw, args);
+ pw.println(); pw.println("WTF history:"); pw.println();
+ mWtfHistory.reverseDump(fd, pw, args);
}
} finally {
setDebugLocked(oldDebug);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 6bcfc4b..07b0b77 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -43,7 +43,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
-import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -116,6 +115,7 @@
private final LocalLog mRequestsHistory;
private final LocalLog mUiLatencyHistory;
+ private final LocalLog mWtfHistory;
private final FieldClassificationStrategy mFieldClassificationStrategy;
/**
@@ -179,11 +179,13 @@
private long mLastPrune = 0;
AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory,
- LocalLog uiLatencyHistory, int userId, AutoFillUI ui, boolean disabled) {
+ LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui,
+ boolean disabled) {
mContext = context;
mLock = lock;
mRequestsHistory = requestsHistory;
mUiLatencyHistory = uiLatencyHistory;
+ mWtfHistory = wtfHistory;
mUserId = userId;
mUi = ui;
mFieldClassificationStrategy = new FieldClassificationStrategy(context, userId);
@@ -484,8 +486,8 @@
assertCallerLocked(componentName);
final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
- sessionId, uid, activityToken, appCallbackToken, hasCallback,
- mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), componentName, flags);
+ sessionId, uid, activityToken, appCallbackToken, hasCallback, mUiLatencyHistory,
+ mWtfHistory, mInfo.getServiceInfo().getComponentName(), componentName, flags);
mSessions.put(newSession.id, newSession);
return newSession;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 63f8384..6b44fa5 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -210,6 +210,9 @@
@GuardedBy("mLock")
private final LocalLog mUiLatencyHistory;
+ @GuardedBy("mLock")
+ private final LocalLog mWtfHistory;
+
/**
* Receiver of assist data from the app's {@link Activity}.
*/
@@ -241,7 +244,13 @@
// ONE_WAY warning because system_service could block on app calls. We need to
// change AssistStructure so it provides a "one-way" writeToParcel() method that
// sends all the data
- structure.ensureData();
+ try {
+ structure.ensureData();
+ } catch (RuntimeException e) {
+ wtf(e, "Exception lazy loading assist structure for %s: %s",
+ structure.getActivityComponent(), e);
+ return;
+ }
// Sanitize structure before it's sent to service.
final ComponentName componentNameFromApp = structure.getActivityComponent();
@@ -447,6 +456,7 @@
@NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId,
@NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken,
@NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory,
+ @NonNull LocalLog wtfHistory,
@NonNull ComponentName serviceComponentName, @NonNull ComponentName componentName,
int flags) {
id = sessionId;
@@ -461,6 +471,7 @@
mActivityToken = activityToken;
mHasCallback = hasCallback;
mUiLatencyHistory = uiLatencyHistory;
+ mWtfHistory = wtfHistory;
mComponentName = componentName;
mClient = IAutoFillManagerClient.Stub.asInterface(client);
@@ -1102,8 +1113,7 @@
if (userData != null && fcStrategy != null) {
logFieldClassificationScoreLocked(fcStrategy, ignoredDatasets, changedFieldIds,
changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds,
- manuallyFilledIds, userData,
- mViewStates.values());
+ userData, mViewStates.values());
} else {
mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
ignoredDatasets, changedFieldIds, changedDatasetIds,
@@ -1123,7 +1133,6 @@
@NonNull ArrayList<String> changedDatasetIds,
@NonNull ArrayList<AutofillId> manuallyFilledFieldIds,
@NonNull ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
- @NonNull ArrayMap<AutofillId, ArraySet<String>> manuallyFilledIds,
@NonNull UserData userData, @NonNull Collection<ViewState> viewStates) {
final String[] userValues = userData.getValues();
@@ -1201,8 +1210,7 @@
}
}
} catch (ArrayIndexOutOfBoundsException e) {
- Slog.wtf(TAG, "Error accessing FC score at " + i + " x " + j + ": "
- + Arrays.toString(scores.scores), e);
+ wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e);
return;
}
@@ -1852,7 +1860,7 @@
mUiLatencyHistory.log(historyLog.toString());
final LogMaker metricsLog = newLogMaker(MetricsEvent.AUTOFILL_UI_LATENCY)
- .setCounterValue((int) duration);
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, duration);
mMetricsLogger.write(metricsLog);
}
}
@@ -2151,9 +2159,10 @@
final Intent fillInIntent = new Intent();
final FillContext context = getFillContextByRequestIdLocked(requestId);
+
if (context == null) {
- Slog.wtf(TAG, "createAuthFillInIntentLocked(): no FillContext. requestId=" + requestId
- + "; mContexts= " + mContexts);
+ wtf(null, "createAuthFillInIntentLocked(): no FillContext. requestId=%d; mContexts=%s",
+ requestId, mContexts);
return null;
}
fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure());
@@ -2418,4 +2427,15 @@
private void writeLog(int category) {
mMetricsLogger.write(newLogMaker(category));
}
+
+ private void wtf(@Nullable Exception e, String fmt, Object...args) {
+ final String message = String.format(fmt, args);
+ mWtfHistory.log(message);
+
+ if (e != null) {
+ Slog.wtf(TAG, message, e);
+ } else {
+ Slog.wtf(TAG, message);
+ }
+ }
}
diff --git a/services/backup/java/com/android/server/backup/internal/BackupState.java b/services/backup/java/com/android/server/backup/internal/BackupState.java
index 4d42c24..937b167 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupState.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupState.java
@@ -5,6 +5,7 @@
*/
enum BackupState {
INITIAL,
+ BACKUP_PM,
RUNNING_QUEUE,
FINAL
}
diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
index 289dd14..99ffa12 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
@@ -224,6 +224,10 @@
beginBackup();
break;
+ case BACKUP_PM:
+ backupPm();
+ break;
+
case RUNNING_QUEUE:
invokeNextAgent();
break;
@@ -239,8 +243,7 @@
}
}
- // We're starting a backup pass. Initialize the transport and send
- // the PM metadata blob if we haven't already.
+ // We're starting a backup pass. Initialize the transport if we haven't already.
private void beginBackup() {
if (DEBUG_BACKUP_TRACE) {
backupManagerService.clearBackupTrace();
@@ -320,40 +323,21 @@
Slog.d(TAG, "Skipping backup of package metadata.");
executeNextState(BackupState.RUNNING_QUEUE);
} else {
- // The package manager doesn't have a proper <application> etc, but since
- // it's running here in the system process we can just set up its agent
- // directly and use a synthetic BackupRequest. We always run this pass
- // because it's cheap and this way we guarantee that we don't get out of
- // step even if we're selecting among various transports at run time.
+ // As the package manager is running here in the system process we can just set up
+ // its agent directly. Thus we always run this pass because it's cheap and this way
+ // we guarantee that we don't get out of step even if we're selecting among various
+ // transports at run time.
if (mStatus == BackupTransport.TRANSPORT_OK) {
- PackageManagerBackupAgent pmAgent = backupManagerService.makeMetadataAgent();
- mStatus = invokeAgentForBackup(
- PACKAGE_MANAGER_SENTINEL,
- IBackupAgent.Stub.asInterface(pmAgent.onBind()));
- backupManagerService.addBackupTrace("PMBA invoke: " + mStatus);
-
- // Because the PMBA is a local instance, it has already executed its
- // backup callback and returned. Blow away the lingering (spurious)
- // pending timeout message for it.
- backupManagerService.getBackupHandler().removeMessages(
- MSG_BACKUP_OPERATION_TIMEOUT);
+ executeNextState(BackupState.BACKUP_PM);
}
}
-
- if (mStatus == BackupTransport.TRANSPORT_NOT_INITIALIZED) {
- // The backend reports that our dataset has been wiped. Note this in
- // the event log; the no-success code below will reset the backup
- // state as well.
- EventLog.writeEvent(EventLogTags.BACKUP_RESET, transportName);
- }
} catch (Exception e) {
- Slog.e(TAG, "Error in backup thread", e);
- backupManagerService.addBackupTrace("Exception in backup thread: " + e);
+ Slog.e(TAG, "Error in backup thread during init", e);
+ backupManagerService.addBackupTrace("Exception in backup thread during init: " + e);
mStatus = BackupTransport.TRANSPORT_ERROR;
} finally {
- // If we've succeeded so far, invokeAgentForBackup() will have run the PM
- // metadata and its completion/timeout callback will continue the state
- // machine chain. If it failed that won't happen; we handle that now.
+ // If we've succeeded so far, we will move to the BACKUP_PM state. If something has gone
+ // wrong then that won't have happen so cleanup.
backupManagerService.addBackupTrace("exiting prelim: " + mStatus);
if (mStatus != BackupTransport.TRANSPORT_OK) {
// if things went wrong at this point, we need to
@@ -367,6 +351,49 @@
}
}
+ private void backupPm() {
+ try {
+ // The package manager doesn't have a proper <application> etc, but since it's running
+ // here in the system process we can just set up its agent directly and use a synthetic
+ // BackupRequest.
+ PackageManagerBackupAgent pmAgent = backupManagerService.makeMetadataAgent();
+ mStatus = invokeAgentForBackup(
+ PACKAGE_MANAGER_SENTINEL,
+ IBackupAgent.Stub.asInterface(pmAgent.onBind()));
+ backupManagerService.addBackupTrace("PMBA invoke: " + mStatus);
+
+ // Because the PMBA is a local instance, it has already executed its backup callback and
+ // returned. Blow away the lingering (spurious) pending timeout message for it.
+ backupManagerService.getBackupHandler().removeMessages(
+ MSG_BACKUP_OPERATION_TIMEOUT);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error in backup thread during pm", e);
+ backupManagerService.addBackupTrace("Exception in backup thread during pm: " + e);
+ mStatus = BackupTransport.TRANSPORT_ERROR;
+ } finally {
+ // If we've succeeded so far, invokeAgentForBackup() will have run the PM
+ // metadata and its completion/timeout callback will continue the state
+ // machine chain. If it failed that won't happen; we handle that now.
+ backupManagerService.addBackupTrace("exiting backupPm: " + mStatus);
+ if (mStatus != BackupTransport.TRANSPORT_OK) {
+ // if things went wrong at this point, we need to
+ // restage everything and try again later.
+ backupManagerService.resetBackupState(mStateDir); // Just to make sure.
+ BackupObserverUtils.sendBackupFinished(mObserver,
+ invokeAgentToObserverError(mStatus));
+ executeNextState(BackupState.FINAL);
+ }
+ }
+ }
+
+ private int invokeAgentToObserverError(int error) {
+ if (error == BackupTransport.AGENT_ERROR) {
+ return BackupManager.ERROR_AGENT_FAILURE;
+ } else {
+ return BackupManager.ERROR_TRANSPORT_ABORTED;
+ }
+ }
+
// Transport has been initialized and the PM metadata submitted successfully
// if that was warranted. Now we process the single next thing in the queue.
private void invokeNextAgent() {
@@ -903,6 +930,7 @@
TransportUtils.checkTransportNotNull(transport);
size = mBackupDataName.length();
if (size > 0) {
+ boolean isNonIncremental = mSavedStateName.length() == 0;
if (mStatus == BackupTransport.TRANSPORT_OK) {
backupData = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_ONLY);
@@ -911,7 +939,7 @@
int userInitiatedFlag =
mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
int incrementalFlag =
- mSavedStateName.length() == 0
+ isNonIncremental
? BackupTransport.FLAG_NON_INCREMENTAL
: BackupTransport.FLAG_INCREMENTAL;
int flags = userInitiatedFlag | incrementalFlag;
@@ -919,6 +947,19 @@
mStatus = transport.performBackup(mCurrentPackage, backupData, flags);
}
+ if (isNonIncremental
+ && mStatus == BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED) {
+ // TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED is only valid if the backup was
+ // incremental, as if the backup is non-incremental there is no state to
+ // clear. This avoids us ending up in a retry loop if the transport always
+ // returns this code.
+ Slog.w(TAG,
+ "Transport requested non-incremental but already the case, error");
+ backupManagerService.addBackupTrace(
+ "Transport requested non-incremental but already the case, error");
+ mStatus = BackupTransport.TRANSPORT_ERROR;
+ }
+
// TODO - We call finishBackup() for each application backed up, because
// we need to know now whether it succeeded or failed. Instead, we should
// hold off on finishBackup() until the end, which implies holding off on
@@ -966,6 +1007,31 @@
BackupObserverUtils.sendBackupOnPackageResult(mObserver, pkgName,
BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED);
EventLog.writeEvent(EventLogTags.BACKUP_QUOTA_EXCEEDED, pkgName);
+
+ } else if (mStatus == BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED) {
+ Slog.i(TAG, "Transport lost data, retrying package");
+ backupManagerService.addBackupTrace(
+ "Transport lost data, retrying package:" + pkgName);
+ BackupManagerMonitorUtils.monitorEvent(
+ mMonitor,
+ BackupManagerMonitor
+ .LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
+ /*extras=*/ null);
+
+ mBackupDataName.delete();
+ mSavedStateName.delete();
+ mNewStateName.delete();
+
+ // Immediately retry the package by adding it back to the front of the queue.
+ // We cannot add @pm@ to the queue because we back it up separately at the start
+ // of the backup pass in state BACKUP_PM. Instead we retry this state (see
+ // below).
+ if (!PACKAGE_MANAGER_SENTINEL.equals(pkgName)) {
+ mQueue.add(0, new BackupRequest(pkgName));
+ }
+
} else {
// Actual transport-level failure to communicate the data to the backend
BackupObserverUtils.sendBackupOnPackageResult(mObserver, pkgName,
@@ -991,6 +1057,17 @@
// Success or single-package rejection. Proceed with the next app if any,
// otherwise we're done.
nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
+
+ } else if (mStatus == BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED) {
+ // We want to immediately retry the current package.
+ if (PACKAGE_MANAGER_SENTINEL.equals(pkgName)) {
+ nextState = BackupState.BACKUP_PM;
+ } else {
+ // This is an ordinary package so we will have added it back into the queue
+ // above. Thus, we proceed processing the queue.
+ nextState = BackupState.RUNNING_QUEUE;
+ }
+
} else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
if (MORE_DEBUG) {
Slog.d(TAG, "Package " + mCurrentPackage.packageName +
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 5030dce..145b307 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -30,6 +30,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
@@ -106,6 +107,7 @@
import android.security.KeyStore;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.LocalLog;
import android.util.LocalLog.ReadOnlyLocalLog;
import android.util.Log;
@@ -176,6 +178,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -455,8 +458,8 @@
private LingerMonitor mLingerMonitor;
// sequence number for Networks; keep in sync with system/netd/NetworkController.cpp
- private final static int MIN_NET_ID = 100; // some reserved marks
- private final static int MAX_NET_ID = 65535;
+ private static final int MIN_NET_ID = 100; // some reserved marks
+ private static final int MAX_NET_ID = 65535 - 0x0400; // Top 1024 bits reserved by IpSecService
private int mNextNetId = MIN_NET_ID;
// sequence number of NetworkRequests
@@ -733,12 +736,12 @@
mSystemProperties = getSystemProperties();
mMetricsLog = logger;
- mDefaultRequest = createInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST);
+ mDefaultRequest = createDefaultInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST);
NetworkRequestInfo defaultNRI = new NetworkRequestInfo(null, mDefaultRequest, new Binder());
mNetworkRequests.put(mDefaultRequest, defaultNRI);
mNetworkRequestInfoLogs.log("REGISTER " + defaultNRI);
- mDefaultMobileDataRequest = createInternetRequestForTransport(
+ mDefaultMobileDataRequest = createDefaultInternetRequestForTransport(
NetworkCapabilities.TRANSPORT_CELLULAR, NetworkRequest.Type.BACKGROUND_REQUEST);
mHandlerThread = new HandlerThread("ConnectivityServiceThread");
@@ -903,7 +906,7 @@
deps);
}
- private NetworkRequest createInternetRequestForTransport(
+ private NetworkRequest createDefaultInternetRequestForTransport(
int transportType, NetworkRequest.Type type) {
NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
@@ -1281,7 +1284,11 @@
for (Network network : networks) {
nai = getNetworkAgentInfoForNetwork(network);
nc = getNetworkCapabilitiesInternal(nai);
+ // nc is a copy of the capabilities in nai, so it's fine to mutate it
+ // TODO : don't remove the UIDs when communicating with processes
+ // that have the NETWORK_SETTINGS permission.
if (nc != null) {
+ nc.setSingleUid(userId);
result.put(network, nc);
}
}
@@ -2079,24 +2086,6 @@
if (score != null) updateNetworkScore(nai, score.intValue());
break;
}
- case NetworkAgent.EVENT_UID_RANGES_ADDED: {
- try {
- mNetd.addVpnUidRanges(nai.network.netId, (UidRange[])msg.obj);
- } catch (Exception e) {
- // Never crash!
- loge("Exception in addVpnUidRanges: " + e);
- }
- break;
- }
- case NetworkAgent.EVENT_UID_RANGES_REMOVED: {
- try {
- mNetd.removeVpnUidRanges(nai.network.netId, (UidRange[])msg.obj);
- } catch (Exception e) {
- // Never crash!
- loge("Exception in removeVpnUidRanges: " + e);
- }
- break;
- }
case NetworkAgent.EVENT_SET_EXPLICITLY_SELECTED: {
if (nai.everConnected && !nai.networkMisc.explicitlySelected) {
loge("ERROR: already-connected network explicitly selected.");
@@ -4235,6 +4224,7 @@
// the system default network.
if (type == NetworkRequest.Type.TRACK_DEFAULT) {
networkCapabilities = new NetworkCapabilities(mDefaultRequest.networkCapabilities);
+ networkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN);
enforceAccessPermission();
} else {
networkCapabilities = new NetworkCapabilities(networkCapabilities);
@@ -4245,6 +4235,13 @@
enforceMeteredApnPolicy(networkCapabilities);
}
ensureRequestableCapabilities(networkCapabilities);
+ // Set the UID range for this request to the single UID of the requester.
+ // This will overwrite any allowed UIDs in the requested capabilities. Though there
+ // are no visible methods to set the UIDs, an app could use reflection to try and get
+ // networks for other apps so it's essential that the UIDs are overwritten.
+ // TODO : don't forcefully set the UID when communicating with processes
+ // that have the NETWORK_SETTINGS permission.
+ networkCapabilities.setSingleUid(Binder.getCallingUid());
if (timeoutMs < 0) {
throw new IllegalArgumentException("Bad timeout specified");
@@ -4318,6 +4315,9 @@
enforceMeteredApnPolicy(networkCapabilities);
ensureRequestableCapabilities(networkCapabilities);
ensureValidNetworkSpecifier(networkCapabilities);
+ // TODO : don't forcefully set the UID when communicating with processes
+ // that have the NETWORK_SETTINGS permission.
+ networkCapabilities.setSingleUid(Binder.getCallingUid());
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
@@ -4371,6 +4371,9 @@
}
NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
+ // TODO : don't forcefully set the UIDs when communicating with processes
+ // that have the NETWORK_SETTINGS permission.
+ nc.setSingleUid(Binder.getCallingUid());
if (!ConnectivityManager.checkChangePermission(mContext)) {
// Apps without the CHANGE_NETWORK_STATE permission can't use background networks, so
// make all their listens include NET_CAPABILITY_FOREGROUND. That way, they will get
@@ -4399,8 +4402,12 @@
}
ensureValidNetworkSpecifier(networkCapabilities);
- NetworkRequest networkRequest = new NetworkRequest(
- new NetworkCapabilities(networkCapabilities), TYPE_NONE, nextNetworkRequestId(),
+ final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
+ // TODO : don't forcefully set the UIDs when communicating with processes
+ // that have the NETWORK_SETTINGS permission.
+ nc.setSingleUid(Binder.getCallingUid());
+
+ NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
NetworkRequest.Type.LISTEN);
NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation);
if (VDBG) log("pendingListenForNetwork for " + nri);
@@ -4543,6 +4550,7 @@
NetworkInfo networkInfo = na.networkInfo;
na.networkInfo = null;
updateNetworkInfo(na, networkInfo);
+ updateUids(na, null, na.networkCapabilities);
}
private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties oldLp) {
@@ -4791,6 +4799,8 @@
nai.networkCapabilities = newNc;
}
+ updateUids(nai, prevNc, newNc);
+
if (nai.getCurrentScore() == oldScore && newNc.equalRequestableCapabilities(prevNc)) {
// If the requestable capabilities haven't changed, and the score hasn't changed, then
// the change we're processing can't affect any requests, it can only affect the listens
@@ -4827,6 +4837,34 @@
}
}
+ private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc,
+ NetworkCapabilities newNc) {
+ Set<UidRange> prevRanges = null == prevNc ? null : prevNc.getUids();
+ Set<UidRange> newRanges = null == newNc ? null : newNc.getUids();
+ if (null == prevRanges) prevRanges = new ArraySet<>();
+ if (null == newRanges) newRanges = new ArraySet<>();
+ final Set<UidRange> prevRangesCopy = new ArraySet<>(prevRanges);
+
+ prevRanges.removeAll(newRanges);
+ newRanges.removeAll(prevRangesCopy);
+
+ try {
+ if (!newRanges.isEmpty()) {
+ final UidRange[] addedRangesArray = new UidRange[newRanges.size()];
+ newRanges.toArray(addedRangesArray);
+ mNetd.addVpnUidRanges(nai.network.netId, addedRangesArray);
+ }
+ if (!prevRanges.isEmpty()) {
+ final UidRange[] removedRangesArray = new UidRange[prevRanges.size()];
+ prevRanges.toArray(removedRangesArray);
+ mNetd.removeVpnUidRanges(nai.network.netId, removedRangesArray);
+ }
+ } catch (Exception e) {
+ // Never crash!
+ loge("Exception in updateUids: " + e);
+ }
+ }
+
public void handleUpdateLinkProperties(NetworkAgentInfo nai, LinkProperties newLp) {
if (mNetworkForNetId.get(nai.network.netId) != nai) {
// Ignore updates for disconnected networks
@@ -4918,7 +4956,12 @@
break;
}
case ConnectivityManager.CALLBACK_CAP_CHANGED: {
- putParcelable(bundle, new NetworkCapabilities(networkAgent.networkCapabilities));
+ final NetworkCapabilities nc =
+ new NetworkCapabilities(networkAgent.networkCapabilities);
+ // TODO : don't remove the UIDs when communicating with processes
+ // that have the NETWORK_SETTINGS permission.
+ nc.setSingleUid(nri.mUid);
+ putParcelable(bundle, nc);
break;
}
case ConnectivityManager.CALLBACK_IP_CHANGED: {
@@ -5442,6 +5485,7 @@
}
}
}
+ updateUids(networkAgent, networkAgent.networkCapabilities, null);
}
} else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) ||
state == NetworkInfo.State.SUSPENDED) {
diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java
index de113a6..792fdfe 100644
--- a/services/core/java/com/android/server/ForceAppStandbyTracker.java
+++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java
@@ -17,6 +17,7 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.AppOpsManager.PackageOps;
import android.app.IActivityManager;
@@ -504,7 +505,7 @@
*/
void uidToForeground(int uid) {
synchronized (mLock) {
- if (!UserHandle.isApp(uid)) {
+ if (UserHandle.isCore(uid)) {
return;
}
// TODO This can be optimized by calling indexOfKey and sharing the index for get and
@@ -522,7 +523,7 @@
*/
void uidToBackground(int uid, boolean remove) {
synchronized (mLock) {
- if (!UserHandle.isApp(uid)) {
+ if (UserHandle.isCore(uid)) {
return;
}
// TODO This can be optimized by calling indexOfKey and sharing the index for get and
@@ -825,9 +826,10 @@
/**
* @return whether jobs should be restricted for a UID package-name.
*/
- public boolean areJobsRestricted(int uid, @NonNull String packageName) {
+ public boolean areJobsRestricted(int uid, @NonNull String packageName,
+ boolean hasForegroundExemption) {
return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ true,
- /* exemptOnBatterySaver =*/ false);
+ hasForegroundExemption);
}
/**
@@ -861,10 +863,12 @@
/**
* @return whether a UID is in the foreground or not.
*
- * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ * Note this information is based on the UID proc state callback, meaning it's updated
+ * asynchronously and may subtly be stale. If the fresh data is needed, use
+ * {@link ActivityManagerInternal#getUidProcessState} instead.
*/
public boolean isInForeground(int uid) {
- if (!UserHandle.isApp(uid)) {
+ if (UserHandle.isCore(uid)) {
return true;
}
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 24d493e..fe4ac6d7 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -34,7 +34,9 @@
import android.net.IpSecSpiResponse;
import android.net.IpSecTransform;
import android.net.IpSecTransformResponse;
+import android.net.IpSecTunnelInterfaceResponse;
import android.net.IpSecUdpEncapResponse;
+import android.net.Network;
import android.net.NetworkUtils;
import android.net.TrafficStats;
import android.net.util.NetdService;
@@ -50,6 +52,7 @@
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -99,6 +102,7 @@
static final int FREE_PORT_MIN = 1024; // ports 1-1023 are reserved
static final int PORT_MAX = 0xFFFF; // ports are an unsigned 16-bit integer
+ static final String TUNNEL_INTERFACE_PREFIX = "ipsec";
/* Binder context for this service */
private final Context mContext;
@@ -347,6 +351,7 @@
@VisibleForTesting
static final class UserRecord {
/* Maximum number of each type of resource that a single UID may possess */
+ public static final int MAX_NUM_TUNNEL_INTERFACES = 2;
public static final int MAX_NUM_ENCAP_SOCKETS = 2;
public static final int MAX_NUM_TRANSFORMS = 4;
public static final int MAX_NUM_SPIS = 8;
@@ -366,6 +371,8 @@
new RefcountedResourceArray<>(TransformRecord.class.getSimpleName());
final RefcountedResourceArray<EncapSocketRecord> mEncapSocketRecords =
new RefcountedResourceArray<>(EncapSocketRecord.class.getSimpleName());
+ final RefcountedResourceArray<TunnelInterfaceRecord> mTunnelInterfaceRecords =
+ new RefcountedResourceArray<>(TunnelInterfaceRecord.class.getSimpleName());
/**
* Trackers for quotas for each of the OwnedResource types.
@@ -379,6 +386,7 @@
final ResourceTracker mSpiQuotaTracker = new ResourceTracker(MAX_NUM_SPIS);
final ResourceTracker mTransformQuotaTracker = new ResourceTracker(MAX_NUM_TRANSFORMS);
final ResourceTracker mSocketQuotaTracker = new ResourceTracker(MAX_NUM_ENCAP_SOCKETS);
+ final ResourceTracker mTunnelQuotaTracker = new ResourceTracker(MAX_NUM_TUNNEL_INTERFACES);
void removeSpiRecord(int resourceId) {
mSpiRecords.remove(resourceId);
@@ -388,6 +396,10 @@
mTransformRecords.remove(resourceId);
}
+ void removeTunnelInterfaceRecord(int resourceId) {
+ mTunnelInterfaceRecords.remove(resourceId);
+ }
+
void removeEncapSocketRecord(int resourceId) {
mEncapSocketRecords.remove(resourceId);
}
@@ -583,6 +595,10 @@
return mSpi;
}
+ public EncapSocketRecord getSocketRecord() {
+ return mSocket;
+ }
+
/** always guarded by IpSecService#this */
@Override
public void freeUnderlyingResources() {
@@ -594,7 +610,9 @@
mResourceId,
mConfig.getSourceAddress(),
mConfig.getDestinationAddress(),
- spi);
+ spi,
+ mConfig.getMarkValue(),
+ mConfig.getMarkMask());
} catch (ServiceSpecificException e) {
// FIXME: get the error code and throw is at an IOException from Errno Exception
} catch (RemoteException e) {
@@ -657,7 +675,7 @@
mSrvConfig
.getNetdInstance()
.ipSecDeleteSecurityAssociation(
- mResourceId, mSourceAddress, mDestinationAddress, mSpi);
+ mResourceId, mSourceAddress, mDestinationAddress, mSpi, 0, 0);
} catch (ServiceSpecificException e) {
// FIXME: get the error code and throw is at an IOException from Errno Exception
} catch (RemoteException e) {
@@ -719,6 +737,165 @@
}
}
+ // These values have been reserved in ConnectivityService
+ @VisibleForTesting static final int TUN_INTF_NETID_START = 0xFC00;
+
+ @VisibleForTesting static final int TUN_INTF_NETID_RANGE = 0x0400;
+
+ private final SparseBooleanArray mTunnelNetIds = new SparseBooleanArray();
+ private int mNextTunnelNetIdIndex = 0;
+
+ /**
+ * Reserves a netId within the range of netIds allocated for IPsec tunnel interfaces
+ *
+ * <p>This method should only be called from Binder threads. Do not call this from within the
+ * system server as it will crash the system on failure.
+ *
+ * @return an integer key within the netId range, if successful
+ * @throws IllegalStateException if unsuccessful (all netId are currently reserved)
+ */
+ @VisibleForTesting
+ int reserveNetId() {
+ synchronized (mTunnelNetIds) {
+ for (int i = 0; i < TUN_INTF_NETID_RANGE; i++) {
+ int index = mNextTunnelNetIdIndex;
+ int netId = index + TUN_INTF_NETID_START;
+ if (++mNextTunnelNetIdIndex >= TUN_INTF_NETID_RANGE) mNextTunnelNetIdIndex = 0;
+ if (!mTunnelNetIds.get(netId)) {
+ mTunnelNetIds.put(netId, true);
+ return netId;
+ }
+ }
+ }
+ throw new IllegalStateException("No free netIds to allocate");
+ }
+
+ @VisibleForTesting
+ void releaseNetId(int netId) {
+ synchronized (mTunnelNetIds) {
+ mTunnelNetIds.delete(netId);
+ }
+ }
+
+ private final class TunnelInterfaceRecord extends OwnedResourceRecord {
+ private final String mInterfaceName;
+ private final Network mUnderlyingNetwork;
+
+ // outer addresses
+ private final String mLocalAddress;
+ private final String mRemoteAddress;
+
+ private final int mIkey;
+ private final int mOkey;
+
+ TunnelInterfaceRecord(
+ int resourceId,
+ String interfaceName,
+ Network underlyingNetwork,
+ String localAddr,
+ String remoteAddr,
+ int ikey,
+ int okey) {
+ super(resourceId);
+
+ mInterfaceName = interfaceName;
+ mUnderlyingNetwork = underlyingNetwork;
+ mLocalAddress = localAddr;
+ mRemoteAddress = remoteAddr;
+ mIkey = ikey;
+ mOkey = okey;
+ }
+
+ /** always guarded by IpSecService#this */
+ @Override
+ public void freeUnderlyingResources() {
+ // Calls to netd
+ // Teardown VTI
+ // Delete global policies
+ try {
+ mSrvConfig.getNetdInstance().removeVirtualTunnelInterface(mInterfaceName);
+
+ for (int direction : DIRECTIONS) {
+ int mark = (direction == IpSecManager.DIRECTION_IN) ? mIkey : mOkey;
+ mSrvConfig
+ .getNetdInstance()
+ .ipSecDeleteSecurityPolicy(
+ 0, direction, mLocalAddress, mRemoteAddress, mark, 0xffffffff);
+ }
+ } 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 VTI with interface name: "
+ + mInterfaceName
+ + " and id: "
+ + mResourceId);
+ }
+
+ getResourceTracker().give();
+ releaseNetId(mIkey);
+ releaseNetId(mOkey);
+ }
+
+ public String getInterfaceName() {
+ return mInterfaceName;
+ }
+
+ public Network getUnderlyingNetwork() {
+ return mUnderlyingNetwork;
+ }
+
+ /** Returns the local, outer address for the tunnelInterface */
+ public String getLocalAddress() {
+ return mLocalAddress;
+ }
+
+ /** Returns the remote, outer address for the tunnelInterface */
+ public String getRemoteAddress() {
+ return mRemoteAddress;
+ }
+
+ public int getIkey() {
+ return mIkey;
+ }
+
+ public int getOkey() {
+ return mOkey;
+ }
+
+ @Override
+ protected ResourceTracker getResourceTracker() {
+ return getUserRecord().mTunnelQuotaTracker;
+ }
+
+ @Override
+ public void invalidate() {
+ getUserRecord().removeTunnelInterfaceRecord(mResourceId);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("{super=")
+ .append(super.toString())
+ .append(", mInterfaceName=")
+ .append(mInterfaceName)
+ .append(", mUnderlyingNetwork=")
+ .append(mUnderlyingNetwork)
+ .append(", mLocalAddress=")
+ .append(mLocalAddress)
+ .append(", mRemoteAddress=")
+ .append(mRemoteAddress)
+ .append(", mIkey=")
+ .append(mIkey)
+ .append(", mOkey=")
+ .append(mOkey)
+ .append("}")
+ .toString();
+ }
+ }
+
/**
* Tracks a UDP encap socket, and manages cleanup paths
*
@@ -1049,6 +1226,130 @@
releaseResource(userRecord.mEncapSocketRecords, resourceId);
}
+ /**
+ * Create a tunnel interface for use in IPSec tunnel mode. The system server will cache the
+ * tunnel interface and a record of its owner so that it can and must be freed when no longer
+ * needed.
+ */
+ @Override
+ public synchronized IpSecTunnelInterfaceResponse createTunnelInterface(
+ String localAddr, String remoteAddr, Network underlyingNetwork, IBinder binder) {
+ checkNotNull(binder, "Null Binder passed to createTunnelInterface");
+ checkNotNull(underlyingNetwork, "No underlying network was specified");
+ checkInetAddress(localAddr);
+ checkInetAddress(remoteAddr);
+
+ // TODO: Check that underlying network exists, and IP addresses not assigned to a different
+ // network (b/72316676).
+
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+ if (!userRecord.mTunnelQuotaTracker.isAvailable()) {
+ return new IpSecTunnelInterfaceResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
+ }
+
+ final int resourceId = mNextResourceId++;
+ final int ikey = reserveNetId();
+ final int okey = reserveNetId();
+ String intfName = String.format("%s%d", TUNNEL_INTERFACE_PREFIX, resourceId);
+
+ try {
+ // Calls to netd:
+ // Create VTI
+ // Add inbound/outbound global policies
+ // (use reqid = 0)
+ mSrvConfig
+ .getNetdInstance()
+ .addVirtualTunnelInterface(intfName, localAddr, remoteAddr, ikey, okey);
+
+ for (int direction : DIRECTIONS) {
+ int mark = (direction == IpSecManager.DIRECTION_OUT) ? okey : ikey;
+
+ mSrvConfig
+ .getNetdInstance()
+ .ipSecAddSecurityPolicy(
+ 0, // Use 0 for reqId
+ direction,
+ "",
+ "",
+ 0,
+ mark,
+ 0xffffffff);
+ }
+
+ userRecord.mTunnelInterfaceRecords.put(
+ resourceId,
+ new RefcountedResource<TunnelInterfaceRecord>(
+ new TunnelInterfaceRecord(
+ resourceId,
+ intfName,
+ underlyingNetwork,
+ localAddr,
+ remoteAddr,
+ ikey,
+ okey),
+ binder));
+ return new IpSecTunnelInterfaceResponse(IpSecManager.Status.OK, resourceId, intfName);
+ } catch (RemoteException e) {
+ // Release keys if we got an error.
+ releaseNetId(ikey);
+ releaseNetId(okey);
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ // FIXME: get the error code and throw is at an IOException from Errno Exception
+ }
+
+ // If we make it to here, then something has gone wrong and we couldn't create a VTI.
+ // Release the keys that we reserved, and return an error status.
+ releaseNetId(ikey);
+ releaseNetId(okey);
+ return new IpSecTunnelInterfaceResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
+ }
+
+ /**
+ * Adds a new local address to the tunnel interface. This allows packets to be sent and received
+ * from multiple local IP addresses over the same tunnel.
+ */
+ @Override
+ public synchronized void addAddressToTunnelInterface(int tunnelResourceId, String localAddr) {
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+
+ // Get tunnelInterface record; if no such interface is found, will throw
+ // IllegalArgumentException
+ TunnelInterfaceRecord tunnelInterfaceInfo =
+ userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId);
+
+ // TODO: Add calls to netd:
+ // Add address to TunnelInterface
+ }
+
+ /**
+ * Remove a new local address from the tunnel interface. After removal, the address will no
+ * longer be available to send from, or receive on.
+ */
+ @Override
+ public synchronized void removeAddressFromTunnelInterface(
+ int tunnelResourceId, String localAddr) {
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+
+ // Get tunnelInterface record; if no such interface is found, will throw
+ // IllegalArgumentException
+ TunnelInterfaceRecord tunnelInterfaceInfo =
+ userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId);
+
+ // TODO: Add calls to netd:
+ // Remove address from TunnelInterface
+ }
+
+ /**
+ * Delete a TunnelInterface that has been been allocated by and registered with the system
+ * server
+ */
+ @Override
+ public synchronized void deleteTunnelInterface(int resourceId) throws RemoteException {
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+ releaseResource(userRecord.mTunnelInterfaceRecords, resourceId);
+ }
+
@VisibleForTesting
void validateAlgorithms(IpSecConfig config) throws IllegalArgumentException {
IpSecAlgorithm auth = config.getAuthentication();
@@ -1136,12 +1437,50 @@
}
}
+ private void createOrUpdateTransform(
+ IpSecConfig c, int resourceId, SpiRecord spiRecord, EncapSocketRecord socketRecord)
+ throws RemoteException {
+
+ int encapType = c.getEncapType(), encapLocalPort = 0, encapRemotePort = 0;
+ if (encapType != IpSecTransform.ENCAP_NONE) {
+ encapLocalPort = socketRecord.getPort();
+ encapRemotePort = c.getEncapRemotePort();
+ }
+
+ IpSecAlgorithm auth = c.getAuthentication();
+ IpSecAlgorithm crypt = c.getEncryption();
+ IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption();
+
+ mSrvConfig
+ .getNetdInstance()
+ .ipSecAddSecurityAssociation(
+ resourceId,
+ c.getMode(),
+ c.getSourceAddress(),
+ c.getDestinationAddress(),
+ (c.getNetwork() != null) ? c.getNetwork().netId : 0,
+ spiRecord.getSpi(),
+ c.getMarkValue(),
+ c.getMarkMask(),
+ (auth != null) ? auth.getName() : "",
+ (auth != null) ? auth.getKey() : new byte[] {},
+ (auth != null) ? auth.getTruncationLengthBits() : 0,
+ (crypt != null) ? crypt.getName() : "",
+ (crypt != null) ? crypt.getKey() : new byte[] {},
+ (crypt != null) ? crypt.getTruncationLengthBits() : 0,
+ (authCrypt != null) ? authCrypt.getName() : "",
+ (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
+ (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
+ encapType,
+ encapLocalPort,
+ encapRemotePort);
+ }
+
/**
- * 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.
+ * Create a IPsec transform, which represents a single security association 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 synchronized IpSecTransformResponse createTransform(IpSecConfig c, IBinder binder)
@@ -1157,56 +1496,28 @@
return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
}
- int encapType, encapLocalPort = 0, encapRemotePort = 0;
EncapSocketRecord socketRecord = null;
- encapType = c.getEncapType();
- if (encapType != IpSecTransform.ENCAP_NONE) {
+ if (c.getEncapType() != IpSecTransform.ENCAP_NONE) {
RefcountedResource<EncapSocketRecord> refcountedSocketRecord =
userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(
c.getEncapSocketResourceId());
dependencies.add(refcountedSocketRecord);
-
socketRecord = refcountedSocketRecord.getResource();
- encapLocalPort = socketRecord.getPort();
- encapRemotePort = c.getEncapRemotePort();
}
- IpSecAlgorithm auth = c.getAuthentication();
- IpSecAlgorithm crypt = c.getEncryption();
- IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption();
-
RefcountedResource<SpiRecord> refcountedSpiRecord =
userRecord.mSpiRecords.getRefcountedResourceOrThrow(c.getSpiResourceId());
dependencies.add(refcountedSpiRecord);
SpiRecord spiRecord = refcountedSpiRecord.getResource();
try {
- mSrvConfig
- .getNetdInstance()
- .ipSecAddSecurityAssociation(
- resourceId,
- c.getMode(),
- c.getSourceAddress(),
- c.getDestinationAddress(),
- (c.getNetwork() != null) ? c.getNetwork().netId : 0,
- spiRecord.getSpi(),
- (auth != null) ? auth.getName() : "",
- (auth != null) ? auth.getKey() : new byte[] {},
- (auth != null) ? auth.getTruncationLengthBits() : 0,
- (crypt != null) ? crypt.getName() : "",
- (crypt != null) ? crypt.getKey() : new byte[] {},
- (crypt != null) ? crypt.getTruncationLengthBits() : 0,
- (authCrypt != null) ? authCrypt.getName() : "",
- (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
- (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
- encapType,
- encapLocalPort,
- encapRemotePort);
+ createOrUpdateTransform(c, resourceId, spiRecord, socketRecord);
} catch (ServiceSpecificException e) {
// FIXME: get the error code and throw is at an IOException from Errno Exception
return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
}
- // Both SAs were created successfully, time to construct a record and lock it away
+
+ // SA was created successfully, time to construct a record and lock it away
userRecord.mTransformRecords.put(
resourceId,
new RefcountedResource<TransformRecord>(
@@ -1245,7 +1556,12 @@
throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
}
+ // Get config and check that to-be-applied transform has the correct mode
IpSecConfig c = info.getConfig();
+ Preconditions.checkArgument(
+ c.getMode() == IpSecTransform.MODE_TRANSPORT,
+ "Transform mode was not Transport mode; cannot be applied to a socket");
+
try {
mSrvConfig
.getNetdInstance()
@@ -1283,6 +1599,76 @@
}
}
+ /**
+ * Apply an active tunnel mode transform to a TunnelInterface, which will apply the IPsec
+ * security association as a correspondent policy to the provided interface
+ */
+ @Override
+ public synchronized void applyTunnelModeTransform(
+ int tunnelResourceId, int direction, int transformResourceId) throws RemoteException {
+ checkDirection(direction);
+
+ UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+
+ // Get transform record; if no transform is found, will throw IllegalArgumentException
+ TransformRecord transformInfo =
+ userRecord.mTransformRecords.getResourceOrThrow(transformResourceId);
+
+ // Get tunnelInterface record; if no such interface is found, will throw
+ // IllegalArgumentException
+ TunnelInterfaceRecord tunnelInterfaceInfo =
+ userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId);
+
+ // Get config and check that to-be-applied transform has the correct mode
+ IpSecConfig c = transformInfo.getConfig();
+ Preconditions.checkArgument(
+ c.getMode() == IpSecTransform.MODE_TUNNEL,
+ "Transform mode was not Tunnel mode; cannot be applied to a tunnel interface");
+
+ EncapSocketRecord socketRecord = null;
+ if (c.getEncapType() != IpSecTransform.ENCAP_NONE) {
+ socketRecord =
+ userRecord.mEncapSocketRecords.getResourceOrThrow(c.getEncapSocketResourceId());
+ }
+ SpiRecord spiRecord = userRecord.mSpiRecords.getResourceOrThrow(c.getSpiResourceId());
+
+ int mark =
+ (direction == IpSecManager.DIRECTION_IN)
+ ? tunnelInterfaceInfo.getIkey()
+ : tunnelInterfaceInfo.getOkey();
+
+ try {
+ c.setMarkValue(mark);
+ c.setMarkMask(0xffffffff);
+
+ if (direction == IpSecManager.DIRECTION_OUT) {
+ // Set output mark via underlying network (output only)
+ c.setNetwork(tunnelInterfaceInfo.getUnderlyingNetwork());
+
+ // If outbound, also add SPI to the policy.
+ mSrvConfig
+ .getNetdInstance()
+ .ipSecUpdateSecurityPolicy(
+ 0, // Use 0 for reqId
+ direction,
+ "",
+ "",
+ transformInfo.getSpiRecord().getSpi(),
+ mark,
+ 0xffffffff);
+ }
+
+ // Update SA with tunnel mark (ikey or okey based on direction)
+ createOrUpdateTransform(c, transformResourceId, spiRecord, socketRecord);
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode == EINVAL) {
+ throw new IllegalArgumentException(e.toString());
+ } else {
+ throw e;
+ }
+ }
+ }
+
@Override
protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(DUMP, TAG);
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 6c63f43..9aa588f 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -16,11 +16,64 @@
package com.android.server;
-import android.app.ActivityManager;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ResolveInfo;
+import android.content.pm.Signature;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.hardware.location.ActivityRecognitionHardware;
+import android.location.Address;
+import android.location.Criteria;
+import android.location.GeocoderParams;
+import android.location.Geofence;
+import android.location.IBatchedLocationCallback;
+import android.location.IGnssMeasurementsListener;
+import android.location.IGnssNavigationMessageListener;
+import android.location.IGnssStatusListener;
+import android.location.IGnssStatusProvider;
+import android.location.IGpsGeofenceHardware;
+import android.location.ILocationListener;
+import android.location.ILocationManager;
+import android.location.INetInitiatedListener;
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.location.LocationRequest;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
-
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
import com.android.internal.content.PackageMonitor;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
@@ -45,60 +98,6 @@
import com.android.server.location.LocationRequestStatistics.PackageStatistics;
import com.android.server.location.MockProvider;
import com.android.server.location.PassiveProvider;
-
-import android.app.AppOpsManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.pm.Signature;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.hardware.location.ActivityRecognitionHardware;
-import android.location.Address;
-import android.location.Criteria;
-import android.location.GeocoderParams;
-import android.location.Geofence;
-import android.location.IBatchedLocationCallback;
-import android.location.IGnssMeasurementsListener;
-import android.location.IGnssStatusListener;
-import android.location.IGnssStatusProvider;
-import android.location.IGpsGeofenceHardware;
-import android.location.IGnssNavigationMessageListener;
-import android.location.ILocationListener;
-import android.location.ILocationManager;
-import android.location.INetInitiatedListener;
-import android.location.Location;
-import android.location.LocationManager;
-import android.location.LocationProvider;
-import android.location.LocationRequest;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.os.WorkSource;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.Log;
-import android.util.Slog;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -1378,10 +1377,7 @@
if (mDisabledProviders.contains(provider)) {
return false;
}
- // Use system settings
- ContentResolver resolver = mContext.getContentResolver();
-
- return Settings.Secure.isLocationProviderEnabledForUser(resolver, provider, mCurrentUserId);
+ return isLocationProviderEnabledForUser(provider, mCurrentUserId);
}
/**
@@ -1400,6 +1396,23 @@
}
/**
+ * Returns "true" if access to the specified location provider is allowed by the specified
+ * user's settings. Access to all location providers is forbidden to non-location-provider
+ * processes belonging to background users.
+ *
+ * @param provider the name of the location provider
+ * @param uid the requestor's UID
+ * @param userId the user id to query
+ */
+ private boolean isAllowedByUserSettingsLockedForUser(
+ String provider, int uid, int userId) {
+ if (!isCurrentProfile(UserHandle.getUserId(uid)) && !isUidALocationProvider(uid)) {
+ return false;
+ }
+ return isLocationProviderEnabledForUser(provider, userId);
+ }
+
+ /**
* Returns the permission string associated with the specified resolution level.
*
* @param resolutionLevel the resolution level
@@ -1425,10 +1438,10 @@
*/
private int getAllowedResolutionLevel(int pid, int uid) {
if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
- pid, uid) == PackageManager.PERMISSION_GRANTED) {
+ pid, uid) == PERMISSION_GRANTED) {
return RESOLUTION_LEVEL_FINE;
} else if (mContext.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
- pid, uid) == PackageManager.PERMISSION_GRANTED) {
+ pid, uid) == PERMISSION_GRANTED) {
return RESOLUTION_LEVEL_COARSE;
} else {
return RESOLUTION_LEVEL_NONE;
@@ -2053,7 +2066,7 @@
}
boolean callerHasLocationHardwarePermission =
mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
- == PackageManager.PERMISSION_GRANTED;
+ == PERMISSION_GRANTED;
LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel,
callerHasLocationHardwarePermission);
@@ -2326,7 +2339,7 @@
// Require that caller can manage given document
boolean callerHasLocationHardwarePermission =
mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
- == PackageManager.PERMISSION_GRANTED;
+ == PERMISSION_GRANTED;
LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel,
callerHasLocationHardwarePermission);
@@ -2476,7 +2489,7 @@
// and check for ACCESS_LOCATION_EXTRA_COMMANDS
if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS)
- != PackageManager.PERMISSION_GRANTED)) {
+ != PERMISSION_GRANTED)) {
throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission");
}
@@ -2546,8 +2559,64 @@
return null;
}
+ /**
+ * Method for enabling or disabling location.
+ *
+ * @param enabled true to enable location. false to disable location
+ * @param userId the user id to set
+ */
+ @Override
+ public void setLocationEnabledForUser(boolean enabled, int userId) {
+ // Check INTERACT_ACROSS_USERS permission if userId is not current user id.
+ checkInteractAcrossUsersPermission(userId);
+
+ // Enable or disable all location providers
+ synchronized (mLock) {
+ for(String provider : getAllProviders()) {
+ setProviderEnabledForUser(provider, enabled, userId);
+ }
+ }
+ }
+
+ /**
+ * Returns the current enabled/disabled status of location
+ *
+ * @param userId the user id to query
+ * @return true if location is enabled. false if location is disabled.
+ */
+ @Override
+ public boolean isLocationEnabledForUser(int userId) {
+ // Check INTERACT_ACROSS_USERS permission if userId is not current user id.
+ checkInteractAcrossUsersPermission(userId);
+
+ // If at least one location provider is enabled, return true
+ synchronized (mLock) {
+ for (String provider : getAllProviders()) {
+ if (isProviderEnabledForUser(provider, userId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
@Override
public boolean isProviderEnabled(String provider) {
+ return isProviderEnabledForUser(provider, UserHandle.getCallingUserId());
+ }
+
+ /**
+ * Method for determining if a location provider is enabled.
+ *
+ * @param provider the location provider to query
+ * @param userId the user id to query
+ * @return true if the provider is enabled
+ */
+ @Override
+ public boolean isProviderEnabledForUser(String provider, int userId) {
+ // Check INTERACT_ACROSS_USERS permission if userId is not current user id.
+ checkInteractAcrossUsersPermission(userId);
+
// Fused provider is accessed indirectly via criteria rather than the provider-based APIs,
// so we discourage its use
if (LocationManager.FUSED_PROVIDER.equals(provider)) return false;
@@ -2557,7 +2626,8 @@
try {
synchronized (mLock) {
LocationProviderInterface p = mProvidersByName.get(provider);
- return p != null && isAllowedByUserSettingsLocked(provider, uid);
+ return p != null
+ && isAllowedByUserSettingsLockedForUser(provider, uid, userId);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -2565,6 +2635,83 @@
}
/**
+ * Method for enabling or disabling a single location provider.
+ *
+ * @param provider the name of the provider
+ * @param enabled true to enable the provider. false to disable the provider
+ * @param userId the user id to set
+ * @return true if the value was set successfully. false on failure.
+ */
+ @Override
+ public boolean setProviderEnabledForUser(
+ String provider, boolean enabled, int userId) {
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.WRITE_SECURE_SETTINGS,
+ "Requires WRITE_SECURE_SETTINGS permission");
+
+ // Check INTERACT_ACROSS_USERS permission if userId is not current user id.
+ checkInteractAcrossUsersPermission(userId);
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ // to ensure thread safety, we write the provider name with a '+' or '-'
+ // and let the SettingsProvider handle it rather than reading and modifying
+ // the list of enabled providers.
+ if (enabled) {
+ provider = "+" + provider;
+ } else {
+ provider = "-" + provider;
+ }
+ return Settings.Secure.putStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ provider,
+ userId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Read location provider status from Settings.Secure
+ *
+ * @param provider the location provider to query
+ * @param userId the user id to query
+ * @return true if the provider is enabled
+ */
+ private boolean isLocationProviderEnabledForUser(String provider, int userId) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ // Use system settings
+ ContentResolver cr = mContext.getContentResolver();
+ String allowedProviders = Settings.Secure.getStringForUser(
+ cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, userId);
+ return TextUtils.delimitedStringContains(allowedProviders, ',', provider);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Method for checking INTERACT_ACROSS_USERS permission if specified user id is not the same as
+ * current user id
+ *
+ * @param userId the user id to get or set value
+ */
+ private void checkInteractAcrossUsersPermission(int userId) {
+ int uid = Binder.getCallingUid();
+ if (UserHandle.getUserId(uid) != userId) {
+ if (ActivityManager.checkComponentPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS, uid, -1, true)
+ != PERMISSION_GRANTED) {
+ throw new SecurityException("Requires INTERACT_ACROSS_USERS permission");
+ }
+ }
+ }
+
+ /**
* Returns "true" if the UID belongs to a bound location provider.
*
* @param uid the uid
@@ -2585,7 +2732,7 @@
private void checkCallerIsProvider() {
if (mContext.checkCallingOrSelfPermission(INSTALL_LOCATION_PROVIDER)
- == PackageManager.PERMISSION_GRANTED) {
+ == PERMISSION_GRANTED) {
return;
}
diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
index 833def3..1e9a007 100644
--- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
@@ -24,7 +24,11 @@
/** Stores the handle to a lockscreen credential to be used for Factory Reset Protection. */
void setFrpCredentialHandle(byte[] handle);
- /** Retrieves handle to a lockscreen credential to be used for Factory Reset Protection. */
+ /**
+ * Retrieves handle to a lockscreen credential to be used for Factory Reset Protection.
+ *
+ * @throws IllegalStateException if the underlying storage is corrupt or inaccessible.
+ */
byte[] getFrpCredentialHandle();
/** Update the OEM unlock enabled bit, bypassing user restriction checks. */
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 4298140..21093b9 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -28,6 +28,7 @@
import android.os.UserManager;
import android.service.persistentdata.IPersistentDataBlockService;
import android.service.persistentdata.PersistentDataBlockManager;
+import android.util.Log;
import android.util.Slog;
import com.android.internal.R;
@@ -582,7 +583,12 @@
@Override
public boolean hasFrpCredentialHandle() {
enforcePersistentDataBlockAccess();
- return mInternalService.getFrpCredentialHandle() != null;
+ try {
+ return mInternalService.getFrpCredentialHandle() != null;
+ } catch (IllegalStateException e) {
+ Slog.e(TAG, "error reading frp handle", e);
+ throw new UnsupportedOperationException("cannot read frp credential");
+ }
}
};
@@ -638,7 +644,7 @@
@Override
public byte[] getFrpCredentialHandle() {
if (!enforceChecksumValidity()) {
- return null;
+ throw new IllegalStateException("invalid checksum");
}
DataInputStream inputStream;
@@ -646,8 +652,7 @@
inputStream = new DataInputStream(
new FileInputStream(new File(mDataBlockFile)));
} catch (FileNotFoundException e) {
- Slog.e(TAG, "partition not available");
- return null;
+ throw new IllegalStateException("frp partition not available");
}
try {
@@ -662,8 +667,7 @@
return bytes;
}
} catch (IOException e) {
- Slog.e(TAG, "unable to access persistent partition", e);
- return null;
+ throw new IllegalStateException("frp handle not readable", e);
} finally {
IoUtils.closeQuietly(inputStream);
}
diff --git a/services/core/java/com/android/server/am/ActiveInstrumentation.java b/services/core/java/com/android/server/am/ActiveInstrumentation.java
index 84e4ea9..4a65733 100644
--- a/services/core/java/com/android/server/am/ActiveInstrumentation.java
+++ b/services/core/java/com/android/server/am/ActiveInstrumentation.java
@@ -22,6 +22,9 @@
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.util.PrintWriterPrinter;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.ActiveInstrumentationProto;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -119,4 +122,26 @@
pw.print(prefix); pw.print("mArguments=");
pw.println(mArguments);
}
+
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ mClass.writeToProto(proto, ActiveInstrumentationProto.CLASS);
+ proto.write(ActiveInstrumentationProto.FINISHED, mFinished);
+ for (int i=0; i<mRunningProcesses.size(); i++) {
+ mRunningProcesses.get(i).writeToProto(proto,
+ ActiveInstrumentationProto.RUNNING_PROCESSES);
+ }
+ for (String p : mTargetProcesses) {
+ proto.write(ActiveInstrumentationProto.TARGET_PROCESSES, p);
+ }
+ if (mTargetInfo != null) {
+ mTargetInfo.writeToProto(proto, ActiveInstrumentationProto.TARGET_INFO);
+ }
+ proto.write(ActiveInstrumentationProto.PROFILE_FILE, mProfileFile);
+ proto.write(ActiveInstrumentationProto.WATCHER, mWatcher.toString());
+ proto.write(ActiveInstrumentationProto.UI_AUTOMATION_CONNECTION,
+ mUiAutomationConnection.toString());
+ proto.write(ActiveInstrumentationProto.ARGUMENTS, mArguments.toString());
+ proto.end(token);
+ }
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 2f7d4c1..266abf8 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1042,20 +1042,14 @@
throw new SecurityException("Instant app " + r.appInfo.packageName
+ " does not have permission to create foreground services");
default:
- try {
- if (AppGlobals.getPackageManager().checkPermission(
- android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
- r.appInfo.packageName, UserHandle.getUserId(r.appInfo.uid))
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Instant app " + r.appInfo.packageName
- + " does not have permission to create foreground"
- + "services");
- }
- } catch (RemoteException e) {
- throw new SecurityException("Failed to check instant app permission." ,
- e);
- }
+ mAm.enforcePermission(
+ android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
+ r.app.pid, r.appInfo.uid, "startForeground");
}
+ } else if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
+ mAm.enforcePermission(
+ android.Manifest.permission.FOREGROUND_SERVICE,
+ r.app.pid, r.appInfo.uid, "startForeground");
}
if (r.fgRequired) {
if (DEBUG_SERVICE || DEBUG_BACKGROUND_CHECK) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1eec982..8397615 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -26,6 +26,7 @@
import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.Manifest.permission.REMOVE_TASKS;
+import static android.Manifest.permission.START_ACTIVITY_AS_CALLER;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
@@ -216,6 +217,7 @@
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal.ScreenObserver;
import android.app.ActivityManagerInternal.SleepToken;
+import android.app.ActivityManagerProto;
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.AlertDialog;
@@ -372,6 +374,7 @@
import android.util.TimingsTraceLog;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.RemoteAnimationDefinition;
@@ -428,12 +431,16 @@
import com.android.server.ThreadPriorityBooster;
import com.android.server.Watchdog;
import com.android.server.am.ActivityStack.ActivityState;
-import com.android.server.am.EventLogTags;
import com.android.server.am.proto.ActivityManagerServiceProto;
import com.android.server.am.proto.BroadcastProto;
import com.android.server.am.proto.GrantUriProto;
+import com.android.server.am.proto.ImportanceTokenProto;
import com.android.server.am.proto.MemInfoProto;
import com.android.server.am.proto.NeededUriGrantsProto;
+import com.android.server.am.proto.ProcessOomProto;
+import com.android.server.am.proto.ProcessToGcProto;
+import com.android.server.am.proto.ProcessesProto;
+import com.android.server.am.proto.ProcessesProto.UidObserverRegistrationProto;
import com.android.server.am.proto.StickyBroadcastProto;
import com.android.server.firewall.IntentFirewall;
import com.android.server.job.JobSchedulerInternal;
@@ -558,6 +565,23 @@
// could take much longer than usual.
static final int PROC_START_TIMEOUT_WITH_WRAPPER = 1200*1000;
+ // Permission tokens are used to temporarily granted a trusted app the ability to call
+ // #startActivityAsCaller. A client is expected to dump its token after this time has elapsed,
+ // showing any appropriate error messages to the user.
+ private static final long START_AS_CALLER_TOKEN_TIMEOUT =
+ 10 * DateUtils.MINUTE_IN_MILLIS;
+
+ // How long before the service actually expires a token. This is slightly longer than
+ // START_AS_CALLER_TOKEN_TIMEOUT, to provide a buffer so clients will rarely encounter the
+ // expiration exception.
+ private static final long START_AS_CALLER_TOKEN_TIMEOUT_IMPL =
+ START_AS_CALLER_TOKEN_TIMEOUT + 2*1000;
+
+ // How long the service will remember expired tokens, for the purpose of providing error
+ // messaging when a client uses an expired token.
+ private static final long START_AS_CALLER_TOKEN_EXPIRED_TIMEOUT =
+ START_AS_CALLER_TOKEN_TIMEOUT_IMPL + 20 * DateUtils.MINUTE_IN_MILLIS;
+
// How long we allow a receiver to run before giving up on it.
static final int BROADCAST_FG_TIMEOUT = 10*1000;
static final int BROADCAST_BG_TIMEOUT = 60*1000;
@@ -666,6 +690,13 @@
final ArrayList<ActiveInstrumentation> mActiveInstrumentation = new ArrayList<>();
+ // Activity tokens of system activities that are delegating their call to
+ // #startActivityByCaller, keyed by the permissionToken granted to the delegate.
+ final HashMap<IBinder, IBinder> mStartActivitySources = new HashMap<>();
+
+ // Permission tokens that have expired, but we remember for error reporting.
+ final ArrayList<IBinder> mExpiredStartAsCallerTokens = new ArrayList<>();
+
public final IntentFirewall mIntentFirewall;
// Whether we should show our dialogs (ANR, crash, etc) or just perform their
@@ -939,6 +970,16 @@
return "ImportanceToken { " + Integer.toHexString(System.identityHashCode(this))
+ " " + reason + " " + pid + " " + token + " }";
}
+
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long pToken = proto.start(fieldId);
+ proto.write(ImportanceTokenProto.PID, pid);
+ if (token != null) {
+ proto.write(ImportanceTokenProto.TOKEN, token.toString());
+ }
+ proto.write(ImportanceTokenProto.REASON, reason);
+ proto.end(pToken);
+ }
}
final SparseArray<ImportanceToken> mImportantProcesses = new SparseArray<ImportanceToken>();
@@ -1317,6 +1358,14 @@
duration = _duration;
tag = _tag;
}
+
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(ProcessesProto.PendingTempWhitelist.TARGET_UID, targetUid);
+ proto.write(ProcessesProto.PendingTempWhitelist.DURATION_MS, duration);
+ proto.write(ProcessesProto.PendingTempWhitelist.TAG, tag);
+ proto.end(token);
+ }
}
final SparseArray<PendingTempWhitelist> mPendingTempWhitelist = new SparseArray<>();
@@ -1641,6 +1690,20 @@
final SparseIntArray lastProcStates;
+ // Please keep the enum lists in sync
+ private static int[] ORIG_ENUMS = new int[]{
+ ActivityManager.UID_OBSERVER_IDLE,
+ ActivityManager.UID_OBSERVER_ACTIVE,
+ ActivityManager.UID_OBSERVER_GONE,
+ ActivityManager.UID_OBSERVER_PROCSTATE,
+ };
+ private static int[] PROTO_ENUMS = new int[]{
+ ActivityManagerProto.UID_OBSERVER_FLAG_IDLE,
+ ActivityManagerProto.UID_OBSERVER_FLAG_ACTIVE,
+ ActivityManagerProto.UID_OBSERVER_FLAG_GONE,
+ ActivityManagerProto.UID_OBSERVER_FLAG_PROCSTATE,
+ };
+
UidObserverRegistration(int _uid, String _pkg, int _which, int _cutpoint) {
uid = _uid;
pkg = _pkg;
@@ -1652,6 +1715,25 @@
lastProcStates = null;
}
}
+
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(UidObserverRegistrationProto.UID, uid);
+ proto.write(UidObserverRegistrationProto.PACKAGE, pkg);
+ ProtoUtils.writeBitWiseFlagsToProtoEnum(proto, UidObserverRegistrationProto.FLAGS,
+ which, ORIG_ENUMS, PROTO_ENUMS);
+ proto.write(UidObserverRegistrationProto.CUT_POINT, cutpoint);
+ if (lastProcStates != null) {
+ final int NI = lastProcStates.size();
+ for (int i=0; i<NI; i++) {
+ final long pToken = proto.start(UidObserverRegistrationProto.LAST_PROC_STATES);
+ proto.write(UidObserverRegistrationProto.ProcState.UID, lastProcStates.keyAt(i));
+ proto.write(UidObserverRegistrationProto.ProcState.STATE, lastProcStates.valueAt(i));
+ proto.end(pToken);
+ }
+ }
+ proto.end(token);
+ }
}
final List<ScreenObserver> mScreenObservers = new ArrayList<>();
@@ -1785,6 +1867,8 @@
static final int PUSH_TEMP_WHITELIST_UI_MSG = 68;
static final int SERVICE_FOREGROUND_CRASH_MSG = 69;
static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70;
+ static final int EXPIRE_START_AS_CALLER_TOKEN_MSG = 75;
+ static final int FORGET_START_AS_CALLER_TOKEN_MSG = 76;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -2449,6 +2533,19 @@
}
}
} break;
+ case EXPIRE_START_AS_CALLER_TOKEN_MSG: {
+ synchronized (ActivityManagerService.this) {
+ final IBinder permissionToken = (IBinder)msg.obj;
+ mStartActivitySources.remove(permissionToken);
+ mExpiredStartAsCallerTokens.add(permissionToken);
+ }
+ } break;
+ case FORGET_START_AS_CALLER_TOKEN_MSG: {
+ synchronized (ActivityManagerService.this) {
+ final IBinder permissionToken = (IBinder)msg.obj;
+ mExpiredStartAsCallerTokens.remove(permissionToken);
+ }
+ } break;
}
}
};
@@ -4717,16 +4814,54 @@
}
+ /**
+ * Only callable from the system. This token grants a temporary permission to call
+ * #startActivityAsCallerWithToken. The token will time out after
+ * START_AS_CALLER_TOKEN_TIMEOUT if it is not used.
+ *
+ * @param delegatorToken The Binder token referencing the system Activity that wants to delegate
+ * the #startActivityAsCaller to another app. The "caller" will be the caller of this
+ * activity's token, not the delegate's caller (which is probably the delegator itself).
+ *
+ * @return Returns a token that can be given to a "delegate" app that may call
+ * #startActivityAsCaller
+ */
@Override
- public final int startActivityAsCaller(IApplicationThread caller, String callingPackage,
- Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
- int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, boolean ignoreTargetSecurity,
- int userId) {
+ public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) {
+ int callingUid = Binder.getCallingUid();
+ if (UserHandle.getAppId(callingUid) != SYSTEM_UID) {
+ throw new SecurityException("Only the system process can request a permission token, " +
+ "received request from uid: " + callingUid);
+ }
+ IBinder permissionToken = new Binder();
+ synchronized (this) {
+ mStartActivitySources.put(permissionToken, delegatorToken);
+ }
+ Message expireMsg = mHandler.obtainMessage(EXPIRE_START_AS_CALLER_TOKEN_MSG,
+ permissionToken);
+ mHandler.sendMessageDelayed(expireMsg, START_AS_CALLER_TOKEN_TIMEOUT_IMPL);
+
+ Message forgetMsg = mHandler.obtainMessage(FORGET_START_AS_CALLER_TOKEN_MSG,
+ permissionToken);
+ mHandler.sendMessageDelayed(forgetMsg, START_AS_CALLER_TOKEN_EXPIRED_TIMEOUT);
+
+ return permissionToken;
+ }
+
+ @Override
+ public final int startActivityAsCaller(IApplicationThread caller,
+ String callingPackage, Intent intent, String resolvedType, IBinder resultTo,
+ String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo,
+ Bundle bOptions, IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
// This is very dangerous -- it allows you to perform a start activity (including
- // permission grants) as any app that may launch one of your own activities. So
- // we will only allow this to be done from activities that are part of the core framework,
- // and then only when they are running as the system.
+ // permission grants) as any app that may launch one of your own activities. So we only
+ // allow this in two cases:
+ // 1) The caller is an activity that is part of the core framework, and then only when it
+ // is running as the system.
+ // 2) The caller provides a valid permissionToken. Permission tokens are one-time use and
+ // can only be requested by a system activity, which may then delegate this call to
+ // another app.
final ActivityRecord sourceRecord;
final int targetUid;
final String targetPackage;
@@ -4734,17 +4869,47 @@
if (resultTo == null) {
throw new SecurityException("Must be called from an activity");
}
- sourceRecord = mStackSupervisor.isInAnyStackLocked(resultTo);
- if (sourceRecord == null) {
- throw new SecurityException("Called with bad activity token: " + resultTo);
+
+ final IBinder sourceToken;
+ if (permissionToken != null) {
+ // To even attempt to use a permissionToken, an app must also have this signature
+ // permission.
+ enforceCallingPermission(android.Manifest.permission.START_ACTIVITY_AS_CALLER,
+ "startActivityAsCaller");
+ // If called with a permissionToken, we want the sourceRecord from the delegator
+ // activity that requested this token.
+ sourceToken =
+ mStartActivitySources.remove(permissionToken);
+ if (sourceToken == null) {
+ // Invalid permissionToken, check if it recently expired.
+ if (mExpiredStartAsCallerTokens.contains(permissionToken)) {
+ throw new SecurityException("Called with expired permission token: "
+ + permissionToken);
+ } else {
+ throw new SecurityException("Called with invalid permission token: "
+ + permissionToken);
+ }
+ }
+ } else {
+ // This method was called directly by the source.
+ sourceToken = resultTo;
}
- if (!sourceRecord.info.packageName.equals("android")) {
- throw new SecurityException(
- "Must be called from an activity that is declared in the android package");
+
+ sourceRecord = mStackSupervisor.isInAnyStackLocked(sourceToken);
+ if (sourceRecord == null) {
+ throw new SecurityException("Called with bad activity token: " + sourceToken);
}
if (sourceRecord.app == null) {
throw new SecurityException("Called without a process attached to activity");
}
+
+ // Whether called directly or from a delegate, the source activity must be from the
+ // android package.
+ if (!sourceRecord.info.packageName.equals("android")) {
+ throw new SecurityException("Must be called from an activity that is " +
+ "declared in the android package");
+ }
+
if (UserHandle.getAppId(sourceRecord.app.uid) != SYSTEM_UID) {
// This is still okay, as long as this activity is running under the
// uid of the original calling activity.
@@ -4755,6 +4920,7 @@
+ sourceRecord.launchedFromUid);
}
}
+
if (ignoreTargetSecurity) {
if (intent.getComponent() == null) {
throw new SecurityException(
@@ -8718,6 +8884,20 @@
/**
* This can be called with or without the global lock held.
*/
+ void enforcePermission(String permission, int pid, int uid, String func) {
+ if (checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+
+ String msg = "Permission Denial: " + func + " from pid=" + pid + ", uid=" + uid
+ + " requires " + permission;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ /**
+ * This can be called with or without the global lock held.
+ */
void enforceCallerIsRecentsOrHasPermission(String permission, String func) {
if (!mRecentTasks.isCallerRecents(Binder.getCallingUid())) {
enforceCallingPermission(permission, func);
@@ -8849,7 +9029,7 @@
case AppOpsManager.MODE_ALLOWED:
// If force-background-check is enabled, restrict all apps that aren't whitelisted.
if (mForceBackgroundCheck &&
- UserHandle.isApp(uid) &&
+ !UserHandle.isCore(uid) &&
!isOnDeviceIdleWhitelistLocked(uid)) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "Force background check: " +
@@ -15251,7 +15431,6 @@
boolean dumpVisibleStacksOnly = false;
boolean dumpFocusedStackOnly = false;
String dumpPackage = null;
- int dumpAppId = -1;
int opti = 0;
while (opti < args.length) {
@@ -15325,6 +15504,15 @@
}
} else if ("service".equals(cmd)) {
mServices.writeToProto(proto);
+ } else if ("processes".equals(cmd) || "p".equals(cmd)) {
+ if (opti < args.length) {
+ dumpPackage = args[opti];
+ opti++;
+ }
+ // output proto is ProcessProto
+ synchronized (this) {
+ writeProcessesToProtoLocked(proto, dumpPackage);
+ }
} else {
// default option, dump everything, output is ActivityManagerServiceProto
synchronized (this) {
@@ -15339,6 +15527,10 @@
long serviceToken = proto.start(ActivityManagerServiceProto.SERVICES);
mServices.writeToProto(proto);
proto.end(serviceToken);
+
+ long processToken = proto.start(ActivityManagerServiceProto.PROCESSES);
+ writeProcessesToProtoLocked(proto, dumpPackage);
+ proto.end(processToken);
}
}
proto.flush();
@@ -15346,16 +15538,7 @@
return;
}
- if (dumpPackage != null) {
- try {
- ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
- dumpPackage, 0);
- dumpAppId = UserHandle.getAppId(info.uid);
- } catch (NameNotFoundException e) {
- e.printStackTrace();
- }
- }
-
+ int dumpAppId = getAppId(dumpPackage);
boolean more = false;
// Is the caller requesting to dump a particular piece of data?
if (opti < args.length) {
@@ -15397,33 +15580,17 @@
pw.println(BinderInternal.nGetBinderProxyCount(Integer.parseInt(uid)));
}
} else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
- String[] newArgs;
- String name;
- if (opti >= args.length) {
- name = null;
- newArgs = EMPTY_STRING_ARRAY;
- } else {
+ if (opti < args.length) {
dumpPackage = args[opti];
opti++;
- newArgs = new String[args.length - opti];
- if (args.length > 2) System.arraycopy(args, opti, newArgs, 0,
- args.length - opti);
}
synchronized (this) {
dumpBroadcastsLocked(fd, pw, args, opti, true, dumpPackage);
}
} else if ("broadcast-stats".equals(cmd)) {
- String[] newArgs;
- String name;
- if (opti >= args.length) {
- name = null;
- newArgs = EMPTY_STRING_ARRAY;
- } else {
+ if (opti < args.length) {
dumpPackage = args[opti];
opti++;
- newArgs = new String[args.length - opti];
- if (args.length > 2) System.arraycopy(args, opti, newArgs, 0,
- args.length - opti);
}
synchronized (this) {
if (dumpCheckinFormat) {
@@ -15434,33 +15601,17 @@
}
}
} else if ("intents".equals(cmd) || "i".equals(cmd)) {
- String[] newArgs;
- String name;
- if (opti >= args.length) {
- name = null;
- newArgs = EMPTY_STRING_ARRAY;
- } else {
+ if (opti < args.length) {
dumpPackage = args[opti];
opti++;
- newArgs = new String[args.length - opti];
- if (args.length > 2) System.arraycopy(args, opti, newArgs, 0,
- args.length - opti);
}
synchronized (this) {
dumpPendingIntentsLocked(fd, pw, args, opti, true, dumpPackage);
}
} else if ("processes".equals(cmd) || "p".equals(cmd)) {
- String[] newArgs;
- String name;
- if (opti >= args.length) {
- name = null;
- newArgs = EMPTY_STRING_ARRAY;
- } else {
+ if (opti < args.length) {
dumpPackage = args[opti];
opti++;
- newArgs = new String[args.length - opti];
- if (args.length > 2) System.arraycopy(args, opti, newArgs, 0,
- args.length - opti);
}
synchronized (this) {
dumpProcessesLocked(fd, pw, args, opti, true, dumpPackage, dumpAppId);
@@ -15881,8 +16032,21 @@
}
}
+ private int getAppId(String dumpPackage) {
+ if (dumpPackage != null) {
+ try {
+ ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
+ dumpPackage, 0);
+ return UserHandle.getAppId(info.uid);
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+ return -1;
+ }
+
boolean dumpUids(PrintWriter pw, String dumpPackage, int dumpAppId, SparseArray<UidRecord> uids,
- String header, boolean needSep) {
+ String header, boolean needSep) {
boolean printed = false;
for (int i=0; i<uids.size(); i++) {
UidRecord uidRec = uids.valueAt(i);
@@ -16100,7 +16264,7 @@
"OnHold Norm", "OnHold PERS", dumpPackage);
}
- needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, dumpPackage);
+ needSep = dumpProcessesToGc(pw, needSep, dumpPackage);
needSep = mAppErrors.dumpLocked(fd, pw, needSep, dumpPackage);
@@ -16385,8 +16549,327 @@
pw.println(" mForceBackgroundCheck=" + mForceBackgroundCheck);
}
- boolean dumpProcessesToGc(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean needSep, boolean dumpAll, String dumpPackage) {
+ void writeProcessesToProtoLocked(ProtoOutputStream proto, String dumpPackage) {
+ int numPers = 0;
+
+ final int NP = mProcessNames.getMap().size();
+ for (int ip=0; ip<NP; ip++) {
+ SparseArray<ProcessRecord> procs = mProcessNames.getMap().valueAt(ip);
+ final int NA = procs.size();
+ for (int ia = 0; ia<NA; ia++) {
+ ProcessRecord r = procs.valueAt(ia);
+ if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
+ continue;
+ }
+ r.writeToProto(proto, ProcessesProto.PROCS);
+ if (r.persistent) {
+ numPers++;
+ }
+ }
+ }
+
+ for (int i=0; i<mIsolatedProcesses.size(); i++) {
+ ProcessRecord r = mIsolatedProcesses.valueAt(i);
+ if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
+ continue;
+ }
+ r.writeToProto(proto, ProcessesProto.ISOLATED_PROCS);
+ }
+
+ for (int i=0; i<mActiveInstrumentation.size(); i++) {
+ ActiveInstrumentation ai = mActiveInstrumentation.get(i);
+ if (dumpPackage != null && !ai.mClass.getPackageName().equals(dumpPackage)
+ && !ai.mTargetInfo.packageName.equals(dumpPackage)) {
+ continue;
+ }
+ ai.writeToProto(proto, ProcessesProto.ACTIVE_INSTRUMENTATIONS);
+ }
+
+ int whichAppId = getAppId(dumpPackage);
+ for (int i=0; i<mActiveUids.size(); i++) {
+ UidRecord uidRec = mActiveUids.valueAt(i);
+ if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
+ continue;
+ }
+ uidRec.writeToProto(proto, ProcessesProto.ACTIVE_UIDS);
+ }
+
+ for (int i=0; i<mValidateUids.size(); i++) {
+ UidRecord uidRec = mValidateUids.valueAt(i);
+ if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
+ continue;
+ }
+ uidRec.writeToProto(proto, ProcessesProto.VALIDATE_UIDS);
+ }
+
+ if (mLruProcesses.size() > 0) {
+ long lruToken = proto.start(ProcessesProto.LRU_PROCS);
+ int total = mLruProcesses.size();
+ proto.write(ProcessesProto.LruProcesses.SIZE, total);
+ proto.write(ProcessesProto.LruProcesses.NON_ACT_AT, total-mLruProcessActivityStart);
+ proto.write(ProcessesProto.LruProcesses.NON_SVC_AT, total-mLruProcessServiceStart);
+ writeProcessOomListToProto(proto, ProcessesProto.LruProcesses.LIST, this,
+ mLruProcesses,false, dumpPackage);
+ proto.end(lruToken);
+ }
+
+ if (dumpPackage != null) {
+ synchronized (mPidsSelfLocked) {
+ for (int i=0; i<mPidsSelfLocked.size(); i++) {
+ ProcessRecord r = mPidsSelfLocked.valueAt(i);
+ if (!r.pkgList.containsKey(dumpPackage)) {
+ continue;
+ }
+ r.writeToProto(proto, ProcessesProto.PIDS_SELF_LOCKED);
+ }
+ }
+ }
+
+ if (mImportantProcesses.size() > 0) {
+ synchronized (mPidsSelfLocked) {
+ for (int i=0; i<mImportantProcesses.size(); i++) {
+ ImportanceToken it = mImportantProcesses.valueAt(i);
+ ProcessRecord r = mPidsSelfLocked.get(it.pid);
+ if (dumpPackage != null && (r == null
+ || !r.pkgList.containsKey(dumpPackage))) {
+ continue;
+ }
+ it.writeToProto(proto, ProcessesProto.IMPORTANT_PROCS);
+ }
+ }
+ }
+
+ for (int i=0; i<mPersistentStartingProcesses.size(); i++) {
+ ProcessRecord r = mPersistentStartingProcesses.get(i);
+ if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) {
+ continue;
+ }
+ r.writeToProto(proto, ProcessesProto.PERSISTENT_STARTING_PROCS);
+ }
+
+ for (int i=0; i<mRemovedProcesses.size(); i++) {
+ ProcessRecord r = mRemovedProcesses.get(i);
+ if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) {
+ continue;
+ }
+ r.writeToProto(proto, ProcessesProto.REMOVED_PROCS);
+ }
+
+ for (int i=0; i<mProcessesOnHold.size(); i++) {
+ ProcessRecord r = mProcessesOnHold.get(i);
+ if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) {
+ continue;
+ }
+ r.writeToProto(proto, ProcessesProto.ON_HOLD_PROCS);
+ }
+
+ writeProcessesToGcToProto(proto, ProcessesProto.GC_PROCS, dumpPackage);
+ mAppErrors.writeToProto(proto, ProcessesProto.APP_ERRORS, dumpPackage);
+
+ if (dumpPackage == null) {
+ mUserController.writeToProto(proto, ProcessesProto.USER_CONTROLLER);
+ getGlobalConfiguration().writeToProto(proto, ProcessesProto.GLOBAL_CONFIGURATION);
+ proto.write(ProcessesProto.CONFIG_WILL_CHANGE, getFocusedStack().mConfigWillChange);
+ }
+
+ if (mHomeProcess != null && (dumpPackage == null
+ || mHomeProcess.pkgList.containsKey(dumpPackage))) {
+ mHomeProcess.writeToProto(proto, ProcessesProto.HOME_PROC);
+ }
+
+ if (mPreviousProcess != null && (dumpPackage == null
+ || mPreviousProcess.pkgList.containsKey(dumpPackage))) {
+ mPreviousProcess.writeToProto(proto, ProcessesProto.PREVIOUS_PROC);
+ proto.write(ProcessesProto.PREVIOUS_PROC_VISIBLE_TIME_MS, mPreviousProcessVisibleTime);
+ }
+
+ if (mHeavyWeightProcess != null && (dumpPackage == null
+ || mHeavyWeightProcess.pkgList.containsKey(dumpPackage))) {
+ mHeavyWeightProcess.writeToProto(proto, ProcessesProto.HEAVY_WEIGHT_PROC);
+ }
+
+ for (Map.Entry<String, Integer> entry : mCompatModePackages.getPackages().entrySet()) {
+ String pkg = entry.getKey();
+ int mode = entry.getValue();
+ if (dumpPackage == null || dumpPackage.equals(pkg)) {
+ long compatToken = proto.start(ProcessesProto.SCREEN_COMPAT_PACKAGES);
+ proto.write(ProcessesProto.ScreenCompatPackage.PACKAGE, pkg);
+ proto.write(ProcessesProto.ScreenCompatPackage.MODE, mode);
+ proto.end(compatToken);
+ }
+ }
+
+ final int NI = mUidObservers.getRegisteredCallbackCount();
+ for (int i=0; i<NI; i++) {
+ final UidObserverRegistration reg = (UidObserverRegistration)
+ mUidObservers.getRegisteredCallbackCookie(i);
+ if (dumpPackage == null || dumpPackage.equals(reg.pkg)) {
+ reg.writeToProto(proto, ProcessesProto.UID_OBSERVERS);
+ }
+ }
+
+ for (int v : mDeviceIdleWhitelist) {
+ proto.write(ProcessesProto.DEVICE_IDLE_WHITELIST, v);
+ }
+
+ for (int v : mDeviceIdleTempWhitelist) {
+ proto.write(ProcessesProto.DEVICE_IDLE_TEMP_WHITELIST, v);
+ }
+
+ if (mPendingTempWhitelist.size() > 0) {
+ for (int i=0; i < mPendingTempWhitelist.size(); i++) {
+ mPendingTempWhitelist.valueAt(i).writeToProto(proto,
+ ProcessesProto.PENDING_TEMP_WHITELIST);
+ }
+ }
+
+ if (dumpPackage == null) {
+ final long sleepToken = proto.start(ProcessesProto.SLEEP_STATUS);
+ proto.write(ProcessesProto.SleepStatus.WAKEFULNESS,
+ PowerManagerInternal.wakefulnessToProtoEnum(mWakefulness));
+ for (SleepToken st : mStackSupervisor.mSleepTokens) {
+ proto.write(ProcessesProto.SleepStatus.SLEEP_TOKENS, st.toString());
+ }
+ proto.write(ProcessesProto.SleepStatus.SLEEPING, mSleeping);
+ proto.write(ProcessesProto.SleepStatus.SHUTTING_DOWN, mShuttingDown);
+ proto.write(ProcessesProto.SleepStatus.TEST_PSS_MODE, mTestPssMode);
+ proto.end(sleepToken);
+
+ if (mRunningVoice != null) {
+ final long vrToken = proto.start(ProcessesProto.RUNNING_VOICE);
+ proto.write(ProcessesProto.VoiceProto.SESSION, mRunningVoice.toString());
+ mVoiceWakeLock.writeToProto(proto, ProcessesProto.VoiceProto.WAKELOCK);
+ proto.end(vrToken);
+ }
+
+ mVrController.writeToProto(proto, ProcessesProto.VR_CONTROLLER);
+ }
+
+ if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient
+ || mOrigWaitForDebugger) {
+ if (dumpPackage == null || dumpPackage.equals(mDebugApp)
+ || dumpPackage.equals(mOrigDebugApp)) {
+ final long debugAppToken = proto.start(ProcessesProto.DEBUG);
+ proto.write(ProcessesProto.DebugApp.DEBUG_APP, mDebugApp);
+ proto.write(ProcessesProto.DebugApp.ORIG_DEBUG_APP, mOrigDebugApp);
+ proto.write(ProcessesProto.DebugApp.DEBUG_TRANSIENT, mDebugTransient);
+ proto.write(ProcessesProto.DebugApp.ORIG_WAIT_FOR_DEBUGGER, mOrigWaitForDebugger);
+ proto.end(debugAppToken);
+ }
+ }
+
+ if (mCurAppTimeTracker != null) {
+ mCurAppTimeTracker.writeToProto(proto, ProcessesProto.CURRENT_TRACKER, true);
+ }
+
+ if (mMemWatchProcesses.getMap().size() > 0) {
+ final long token = proto.start(ProcessesProto.MEM_WATCH_PROCESSES);
+ ArrayMap<String, SparseArray<Pair<Long, String>>> procs = mMemWatchProcesses.getMap();
+ for (int i=0; i<procs.size(); i++) {
+ final String proc = procs.keyAt(i);
+ final SparseArray<Pair<Long, String>> uids = procs.valueAt(i);
+ final long ptoken = proto.start(ProcessesProto.MemWatchProcess.PROCS);
+ proto.write(ProcessesProto.MemWatchProcess.Process.NAME, proc);
+ for (int j=0; j<uids.size(); j++) {
+ final long utoken = proto.start(ProcessesProto.MemWatchProcess.Process.MEM_STATS);
+ Pair<Long, String> val = uids.valueAt(j);
+ proto.write(ProcessesProto.MemWatchProcess.Process.MemStats.UID, uids.keyAt(j));
+ proto.write(ProcessesProto.MemWatchProcess.Process.MemStats.SIZE,
+ DebugUtils.sizeValueToString(val.first, new StringBuilder()));
+ proto.write(ProcessesProto.MemWatchProcess.Process.MemStats.REPORT_TO, val.second);
+ proto.end(utoken);
+ }
+ proto.end(ptoken);
+ }
+
+ final long dtoken = proto.start(ProcessesProto.MemWatchProcess.DUMP);
+ proto.write(ProcessesProto.MemWatchProcess.Dump.PROC_NAME, mMemWatchDumpProcName);
+ proto.write(ProcessesProto.MemWatchProcess.Dump.FILE, mMemWatchDumpFile);
+ proto.write(ProcessesProto.MemWatchProcess.Dump.PID, mMemWatchDumpPid);
+ proto.write(ProcessesProto.MemWatchProcess.Dump.UID, mMemWatchDumpUid);
+ proto.end(dtoken);
+
+ proto.end(token);
+ }
+
+ if (mTrackAllocationApp != null) {
+ if (dumpPackage == null || dumpPackage.equals(mTrackAllocationApp)) {
+ proto.write(ProcessesProto.TRACK_ALLOCATION_APP, mTrackAllocationApp);
+ }
+ }
+
+ if (mProfileApp != null || mProfileProc != null || (mProfilerInfo != null &&
+ (mProfilerInfo.profileFile != null || mProfilerInfo.profileFd != null))) {
+ if (dumpPackage == null || dumpPackage.equals(mProfileApp)) {
+ final long token = proto.start(ProcessesProto.PROFILE);
+ proto.write(ProcessesProto.Profile.APP_NAME, mProfileApp);
+ mProfileProc.writeToProto(proto,ProcessesProto.Profile.PROC);
+ if (mProfilerInfo != null) {
+ mProfilerInfo.writeToProto(proto, ProcessesProto.Profile.INFO);
+ proto.write(ProcessesProto.Profile.TYPE, mProfileType);
+ }
+ proto.end(token);
+ }
+ }
+
+ if (dumpPackage == null || dumpPackage.equals(mNativeDebuggingApp)) {
+ proto.write(ProcessesProto.NATIVE_DEBUGGING_APP, mNativeDebuggingApp);
+ }
+
+ if (dumpPackage == null) {
+ proto.write(ProcessesProto.ALWAYS_FINISH_ACTIVITIES, mAlwaysFinishActivities);
+ if (mController != null) {
+ final long token = proto.start(ProcessesProto.CONTROLLER);
+ proto.write(ProcessesProto.Controller.CONTROLLER, mController.toString());
+ proto.write(ProcessesProto.Controller.IS_A_MONKEY, mControllerIsAMonkey);
+ proto.end(token);
+ }
+ proto.write(ProcessesProto.TOTAL_PERSISTENT_PROCS, numPers);
+ proto.write(ProcessesProto.PROCESSES_READY, mProcessesReady);
+ proto.write(ProcessesProto.SYSTEM_READY, mSystemReady);
+ proto.write(ProcessesProto.BOOTED, mBooted);
+ proto.write(ProcessesProto.FACTORY_TEST, mFactoryTest);
+ proto.write(ProcessesProto.BOOTING, mBooting);
+ proto.write(ProcessesProto.CALL_FINISH_BOOTING, mCallFinishBooting);
+ proto.write(ProcessesProto.BOOT_ANIMATION_COMPLETE, mBootAnimationComplete);
+ proto.write(ProcessesProto.LAST_POWER_CHECK_UPTIME_MS, mLastPowerCheckUptime);
+ mStackSupervisor.mGoingToSleep.writeToProto(proto, ProcessesProto.GOING_TO_SLEEP);
+ mStackSupervisor.mLaunchingActivity.writeToProto(proto, ProcessesProto.LAUNCHING_ACTIVITY);
+ proto.write(ProcessesProto.ADJ_SEQ, mAdjSeq);
+ proto.write(ProcessesProto.LRU_SEQ, mLruSeq);
+ proto.write(ProcessesProto.NUM_NON_CACHED_PROCS, mNumNonCachedProcs);
+ proto.write(ProcessesProto.NUM_SERVICE_PROCS, mNumServiceProcs);
+ proto.write(ProcessesProto.NEW_NUM_SERVICE_PROCS, mNewNumServiceProcs);
+ proto.write(ProcessesProto.ALLOW_LOWER_MEM_LEVEL, mAllowLowerMemLevel);
+ proto.write(ProcessesProto.LAST_MEMORY_LEVEL, mLastMemoryLevel);
+ proto.write(ProcessesProto.LAST_NUM_PROCESSES, mLastNumProcesses);
+ long now = SystemClock.uptimeMillis();
+ ProtoUtils.toDuration(proto, ProcessesProto.LAST_IDLE_TIME, mLastIdleTime, now);
+ proto.write(ProcessesProto.LOW_RAM_SINCE_LAST_IDLE_MS, getLowRamTimeSinceIdle(now));
+ }
+
+ }
+
+ void writeProcessesToGcToProto(ProtoOutputStream proto, long fieldId, String dumpPackage) {
+ if (mProcessesToGc.size() > 0) {
+ long now = SystemClock.uptimeMillis();
+ for (int i=0; i<mProcessesToGc.size(); i++) {
+ ProcessRecord r = mProcessesToGc.get(i);
+ if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) {
+ continue;
+ }
+ final long token = proto.start(fieldId);
+ r.writeToProto(proto, ProcessToGcProto.PROC);
+ proto.write(ProcessToGcProto.REPORT_LOW_MEMORY, r.reportLowMemory);
+ proto.write(ProcessToGcProto.NOW_UPTIME_MS, now);
+ proto.write(ProcessToGcProto.LAST_GCED_MS, r.lastRequestedGc);
+ proto.write(ProcessToGcProto.LAST_LOW_MEMORY_MS, r.lastLowMemory);
+ proto.end(token);
+ }
+ }
+ }
+
+ boolean dumpProcessesToGc(PrintWriter pw, boolean needSep, String dumpPackage) {
if (mProcessesToGc.size() > 0) {
boolean printed = false;
long now = SystemClock.uptimeMillis();
@@ -16464,7 +16947,7 @@
needSep = true;
}
- dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, null);
+ dumpProcessesToGc(pw, needSep, null);
pw.println();
pw.println(" mHomeProcess: " + mHomeProcess);
@@ -17015,11 +17498,8 @@
return numPers;
}
- private static final boolean dumpProcessOomList(PrintWriter pw,
- ActivityManagerService service, List<ProcessRecord> origList,
- String prefix, String normalLabel, String persistentLabel,
- boolean inclDetails, String dumpPackage) {
-
+ private static final ArrayList<Pair<ProcessRecord, Integer>>
+ sortProcessOomList(List<ProcessRecord> origList, String dumpPackage) {
ArrayList<Pair<ProcessRecord, Integer>> list
= new ArrayList<Pair<ProcessRecord, Integer>>(origList.size());
for (int i=0; i<origList.size(); i++) {
@@ -17030,10 +17510,6 @@
list.add(new Pair<ProcessRecord, Integer>(origList.get(i), i));
}
- if (list.size() <= 0) {
- return false;
- }
-
Comparator<Pair<ProcessRecord, Integer>> comparator
= new Comparator<Pair<ProcessRecord, Integer>>() {
@Override
@@ -17053,6 +17529,113 @@
};
Collections.sort(list, comparator);
+ return list;
+ }
+
+ private static final boolean writeProcessOomListToProto(ProtoOutputStream proto, long fieldId,
+ ActivityManagerService service, List<ProcessRecord> origList,
+ boolean inclDetails, String dumpPackage) {
+ ArrayList<Pair<ProcessRecord, Integer>> list = sortProcessOomList(origList, dumpPackage);
+ if (list.isEmpty()) return false;
+
+ final long curUptime = SystemClock.uptimeMillis();
+
+ for (int i = list.size() - 1; i >= 0; i--) {
+ ProcessRecord r = list.get(i).first;
+ long token = proto.start(fieldId);
+ String oomAdj = ProcessList.makeOomAdjString(r.setAdj);
+ proto.write(ProcessOomProto.PERSISTENT, r.persistent);
+ proto.write(ProcessOomProto.NUM, (origList.size()-1)-list.get(i).second);
+ proto.write(ProcessOomProto.OOM_ADJ, oomAdj);
+ int schedGroup = ProcessOomProto.SCHED_GROUP_UNKNOWN;
+ switch (r.setSchedGroup) {
+ case ProcessList.SCHED_GROUP_BACKGROUND:
+ schedGroup = ProcessOomProto.SCHED_GROUP_BACKGROUND;
+ break;
+ case ProcessList.SCHED_GROUP_DEFAULT:
+ schedGroup = ProcessOomProto.SCHED_GROUP_DEFAULT;
+ break;
+ case ProcessList.SCHED_GROUP_TOP_APP:
+ schedGroup = ProcessOomProto.SCHED_GROUP_TOP_APP;
+ break;
+ case ProcessList.SCHED_GROUP_TOP_APP_BOUND:
+ schedGroup = ProcessOomProto.SCHED_GROUP_TOP_APP_BOUND;
+ break;
+ }
+ if (schedGroup != ProcessOomProto.SCHED_GROUP_UNKNOWN) {
+ proto.write(ProcessOomProto.SCHED_GROUP, schedGroup);
+ }
+ if (r.foregroundActivities) {
+ proto.write(ProcessOomProto.ACTIVITIES, true);
+ } else if (r.foregroundServices) {
+ proto.write(ProcessOomProto.SERVICES, true);
+ }
+ proto.write(ProcessOomProto.STATE, ProcessList.makeProcStateProtoEnum(r.curProcState));
+ proto.write(ProcessOomProto.TRIM_MEMORY_LEVEL, r.trimMemoryLevel);
+ r.writeToProto(proto, ProcessOomProto.PROC);
+ proto.write(ProcessOomProto.ADJ_TYPE, r.adjType);
+ if (r.adjSource != null || r.adjTarget != null) {
+ if (r.adjTarget instanceof ComponentName) {
+ ComponentName cn = (ComponentName) r.adjTarget;
+ cn.writeToProto(proto, ProcessOomProto.ADJ_TARGET_COMPONENT_NAME);
+ } else if (r.adjTarget != null) {
+ proto.write(ProcessOomProto.ADJ_TARGET_OBJECT, r.adjTarget.toString());
+ }
+ if (r.adjSource instanceof ProcessRecord) {
+ ProcessRecord p = (ProcessRecord) r.adjSource;
+ p.writeToProto(proto, ProcessOomProto.ADJ_SOURCE_PROC);
+ } else if (r.adjSource != null) {
+ proto.write(ProcessOomProto.ADJ_SOURCE_OBJECT, r.adjSource.toString());
+ }
+ }
+ if (inclDetails) {
+ long detailToken = proto.start(ProcessOomProto.DETAIL);
+ proto.write(ProcessOomProto.Detail.MAX_ADJ, r.maxAdj);
+ proto.write(ProcessOomProto.Detail.CUR_RAW_ADJ, r.curRawAdj);
+ proto.write(ProcessOomProto.Detail.SET_RAW_ADJ, r.setRawAdj);
+ proto.write(ProcessOomProto.Detail.CUR_ADJ, r.curAdj);
+ proto.write(ProcessOomProto.Detail.SET_ADJ, r.setAdj);
+ proto.write(ProcessOomProto.Detail.CURRENT_STATE,
+ ProcessList.makeProcStateProtoEnum(r.curProcState));
+ proto.write(ProcessOomProto.Detail.SET_STATE,
+ ProcessList.makeProcStateProtoEnum(r.setProcState));
+ proto.write(ProcessOomProto.Detail.LAST_PSS, DebugUtils.sizeValueToString(
+ r.lastPss*1024, new StringBuilder()));
+ proto.write(ProcessOomProto.Detail.LAST_SWAP_PSS, DebugUtils.sizeValueToString(
+ r.lastSwapPss*1024, new StringBuilder()));
+ proto.write(ProcessOomProto.Detail.LAST_CACHED_PSS, DebugUtils.sizeValueToString(
+ r.lastCachedPss*1024, new StringBuilder()));
+ proto.write(ProcessOomProto.Detail.CACHED, r.cached);
+ proto.write(ProcessOomProto.Detail.EMPTY, r.empty);
+ proto.write(ProcessOomProto.Detail.HAS_ABOVE_CLIENT, r.hasAboveClient);
+
+ if (r.setProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
+ if (r.lastCpuTime != 0) {
+ long uptimeSince = curUptime - service.mLastPowerCheckUptime;
+ long timeUsed = r.curCpuTime - r.lastCpuTime;
+ long cpuTimeToken = proto.start(ProcessOomProto.Detail.SERVICE_RUN_TIME);
+ proto.write(ProcessOomProto.Detail.CpuRunTime.OVER_MS, uptimeSince);
+ proto.write(ProcessOomProto.Detail.CpuRunTime.USED_MS, timeUsed);
+ proto.write(ProcessOomProto.Detail.CpuRunTime.ULTILIZATION,
+ (100.0*timeUsed)/uptimeSince);
+ proto.end(cpuTimeToken);
+ }
+ }
+ proto.end(detailToken);
+ }
+ proto.end(token);
+ }
+
+ return true;
+ }
+
+ private static final boolean dumpProcessOomList(PrintWriter pw,
+ ActivityManagerService service, List<ProcessRecord> origList,
+ String prefix, String normalLabel, String persistentLabel,
+ boolean inclDetails, String dumpPackage) {
+
+ ArrayList<Pair<ProcessRecord, Integer>> list = sortProcessOomList(origList, dumpPackage);
+ if (list.isEmpty()) return false;
final long curUptime = SystemClock.uptimeMillis();
final long uptimeSince = curUptime - service.mLastPowerCheckUptime;
@@ -24120,7 +24703,7 @@
final int size = mActiveUids.size();
for (int i = 0; i < size; i++) {
final int uid = mActiveUids.keyAt(i);
- if (!UserHandle.isApp(uid)) {
+ if (UserHandle.isCore(uid)) {
continue;
}
final UidRecord uidRec = mActiveUids.valueAt(i);
diff --git a/services/core/java/com/android/server/am/ActivityStartController.java b/services/core/java/com/android/server/am/ActivityStartController.java
index f9932b2..5551914 100644
--- a/services/core/java/com/android/server/am/ActivityStartController.java
+++ b/services/core/java/com/android/server/am/ActivityStartController.java
@@ -220,7 +220,7 @@
}
}
- final int startActivityInPackage(int uid, int realCallingUid, int realCallingPid,
+ final int startActivityInPackage(int uid, int realCallingPid, int realCallingUid,
String callingPackage, Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags, SafeActivityOptions options,
int userId, TaskRecord inTask, String reason) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 8595aa3..4dc30dd 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1252,7 +1252,7 @@
outActivity[0] = reusedActivity;
}
- return START_DELIVERED_TO_TOP;
+ return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
}
}
diff --git a/services/core/java/com/android/server/am/AppErrorDialog.java b/services/core/java/com/android/server/am/AppErrorDialog.java
index 5412266..68c63a2 100644
--- a/services/core/java/com/android/server/am/AppErrorDialog.java
+++ b/services/core/java/com/android/server/am/AppErrorDialog.java
@@ -38,9 +38,7 @@
private final ActivityManagerService mService;
private final AppErrorResult mResult;
private final ProcessRecord mProc;
- private final boolean mRepeating;
private final boolean mIsRestartable;
- private CharSequence mName;
static int CANT_SHOW = -1;
static int BACKGROUND_USER = -2;
@@ -53,6 +51,7 @@
static final int MUTE = 5;
static final int TIMEOUT = 6;
static final int CANCEL = 7;
+ static final int APP_INFO = 8;
// 5-minute timeout, then we automatically dismiss the crash dialog
static final long DISMISS_TIMEOUT = 1000 * 60 * 5;
@@ -64,23 +63,25 @@
mService = service;
mProc = data.proc;
mResult = data.result;
- mRepeating = data.repeating;
- mIsRestartable = data.task != null || data.isRestartableForService;
+ mIsRestartable = (data.task != null || data.isRestartableForService)
+ && Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG, 0) != 0;
BidiFormatter bidi = BidiFormatter.getInstance();
+ CharSequence name;
if ((mProc.pkgList.size() == 1) &&
- (mName = context.getPackageManager().getApplicationLabel(mProc.info)) != null) {
+ (name = context.getPackageManager().getApplicationLabel(mProc.info)) != null) {
setTitle(res.getString(
- mRepeating ? com.android.internal.R.string.aerr_application_repeated
+ data.repeating ? com.android.internal.R.string.aerr_application_repeated
: com.android.internal.R.string.aerr_application,
- bidi.unicodeWrap(mName.toString()),
+ bidi.unicodeWrap(name.toString()),
bidi.unicodeWrap(mProc.info.processName)));
} else {
- mName = mProc.processName;
+ name = mProc.processName;
setTitle(res.getString(
- mRepeating ? com.android.internal.R.string.aerr_process_repeated
+ data.repeating ? com.android.internal.R.string.aerr_process_repeated
: com.android.internal.R.string.aerr_process,
- bidi.unicodeWrap(mName.toString())));
+ bidi.unicodeWrap(name.toString())));
}
setCancelable(true);
@@ -118,11 +119,14 @@
report.setOnClickListener(this);
report.setVisibility(hasReceiver ? View.VISIBLE : View.GONE);
final TextView close = findViewById(com.android.internal.R.id.aerr_close);
- close.setVisibility(mRepeating ? View.VISIBLE : View.GONE);
close.setOnClickListener(this);
+ final TextView appInfo = findViewById(com.android.internal.R.id.aerr_app_info);
+ appInfo.setOnClickListener(this);
boolean showMute = !Build.IS_USER && Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0
+ && Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG, 0) != 0;
final TextView mute = findViewById(com.android.internal.R.id.aerr_mute);
mute.setOnClickListener(this);
mute.setVisibility(showMute ? View.VISIBLE : View.GONE);
@@ -183,6 +187,9 @@
case com.android.internal.R.id.aerr_close:
mHandler.obtainMessage(FORCE_QUIT).sendToTarget();
break;
+ case com.android.internal.R.id.aerr_app_info:
+ mHandler.obtainMessage(APP_INFO).sendToTarget();
+ break;
case com.android.internal.R.id.aerr_mute:
mHandler.obtainMessage(MUTE).sendToTarget();
break;
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 0da7e0e..9776c4d 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -22,6 +22,7 @@
import com.android.internal.os.ProcessCpuTracker;
import com.android.server.RescueParty;
import com.android.server.Watchdog;
+import com.android.server.am.proto.AppErrorsProto;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -33,6 +34,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.net.Uri;
import android.os.Binder;
import android.os.Message;
import android.os.Process;
@@ -48,6 +50,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
import java.io.File;
import java.io.FileDescriptor;
@@ -103,8 +106,76 @@
mContext = context;
}
- boolean dumpLocked(FileDescriptor fd, PrintWriter pw, boolean needSep,
- String dumpPackage) {
+ void writeToProto(ProtoOutputStream proto, long fieldId, String dumpPackage) {
+ if (mProcessCrashTimes.getMap().isEmpty() && mBadProcesses.getMap().isEmpty()) {
+ return;
+ }
+
+ final long token = proto.start(fieldId);
+ final long now = SystemClock.uptimeMillis();
+ proto.write(AppErrorsProto.NOW_UPTIME_MS, now);
+
+ if (!mProcessCrashTimes.getMap().isEmpty()) {
+ final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap();
+ final int procCount = pmap.size();
+ for (int ip = 0; ip < procCount; ip++) {
+ final long ctoken = proto.start(AppErrorsProto.PROCESS_CRASH_TIMES);
+ final String pname = pmap.keyAt(ip);
+ final SparseArray<Long> uids = pmap.valueAt(ip);
+ final int uidCount = uids.size();
+
+ proto.write(AppErrorsProto.ProcessCrashTime.PROCESS_NAME, pname);
+ for (int i = 0; i < uidCount; i++) {
+ final int puid = uids.keyAt(i);
+ final ProcessRecord r = mService.mProcessNames.get(pname, puid);
+ if (dumpPackage != null && (r == null || !r.pkgList.containsKey(dumpPackage))) {
+ continue;
+ }
+ final long etoken = proto.start(AppErrorsProto.ProcessCrashTime.ENTRIES);
+ proto.write(AppErrorsProto.ProcessCrashTime.Entry.UID, puid);
+ proto.write(AppErrorsProto.ProcessCrashTime.Entry.LAST_CRASHED_AT_MS,
+ uids.valueAt(i));
+ proto.end(etoken);
+ }
+ proto.end(ctoken);
+ }
+
+ }
+
+ if (!mBadProcesses.getMap().isEmpty()) {
+ final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = mBadProcesses.getMap();
+ final int processCount = pmap.size();
+ for (int ip = 0; ip < processCount; ip++) {
+ final long btoken = proto.start(AppErrorsProto.BAD_PROCESSES);
+ final String pname = pmap.keyAt(ip);
+ final SparseArray<BadProcessInfo> uids = pmap.valueAt(ip);
+ final int uidCount = uids.size();
+
+ proto.write(AppErrorsProto.BadProcess.PROCESS_NAME, pname);
+ for (int i = 0; i < uidCount; i++) {
+ final int puid = uids.keyAt(i);
+ final ProcessRecord r = mService.mProcessNames.get(pname, puid);
+ if (dumpPackage != null && (r == null
+ || !r.pkgList.containsKey(dumpPackage))) {
+ continue;
+ }
+ final BadProcessInfo info = uids.valueAt(i);
+ final long etoken = proto.start(AppErrorsProto.BadProcess.ENTRIES);
+ proto.write(AppErrorsProto.BadProcess.Entry.UID, puid);
+ proto.write(AppErrorsProto.BadProcess.Entry.CRASHED_AT_MS, info.time);
+ proto.write(AppErrorsProto.BadProcess.Entry.SHORT_MSG, info.shortMsg);
+ proto.write(AppErrorsProto.BadProcess.Entry.LONG_MSG, info.longMsg);
+ proto.write(AppErrorsProto.BadProcess.Entry.STACK, info.stack);
+ proto.end(etoken);
+ }
+ proto.end(btoken);
+ }
+ }
+
+ proto.end(token);
+ }
+
+ boolean dumpLocked(FileDescriptor fd, PrintWriter pw, boolean needSep, String dumpPackage) {
if (!mProcessCrashTimes.getMap().isEmpty()) {
boolean printed = false;
final long now = SystemClock.uptimeMillis();
@@ -430,6 +501,11 @@
Binder.restoreCallingIdentity(orig);
}
}
+ if (res == AppErrorDialog.APP_INFO) {
+ appErrorIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ appErrorIntent.setData(Uri.parse("package:" + r.info.packageName));
+ appErrorIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) {
appErrorIntent = createAppErrorIntentLocked(r, timeMillis, crashInfo);
}
diff --git a/services/core/java/com/android/server/am/AppTimeTracker.java b/services/core/java/com/android/server/am/AppTimeTracker.java
index 910f33d..d96364a 100644
--- a/services/core/java/com/android/server/am/AppTimeTracker.java
+++ b/services/core/java/com/android/server/am/AppTimeTracker.java
@@ -25,6 +25,10 @@
import android.util.ArrayMap;
import android.util.MutableLong;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
+
+import com.android.server.am.proto.AppTimeTrackerProto;
import java.io.PrintWriter;
@@ -119,4 +123,22 @@
pw.print(prefix); pw.print("mStartedPackage="); pw.println(mStartedPackage);
}
}
+
+ void writeToProto(ProtoOutputStream proto, long fieldId, boolean details) {
+ final long token = proto.start(fieldId);
+ proto.write(AppTimeTrackerProto.RECEIVER, mReceiver.toString());
+ proto.write(AppTimeTrackerProto.TOTAL_DURATION_MS, mTotalTime);
+ for (int i=0; i<mPackageTimes.size(); i++) {
+ final long ptoken = proto.start(AppTimeTrackerProto.PACKAGE_TIMES);
+ proto.write(AppTimeTrackerProto.PackageTime.PACKAGE, mPackageTimes.keyAt(i));
+ proto.write(AppTimeTrackerProto.PackageTime.DURATION_MS, mPackageTimes.valueAt(i).value);
+ proto.end(ptoken);
+ }
+ if (details && mStartedTime != 0) {
+ ProtoUtils.toDuration(proto, AppTimeTrackerProto.STARTED_TIME,
+ mStartedTime, SystemClock.elapsedRealtime());
+ proto.write(AppTimeTrackerProto.STARTED_PACKAGE, mStartedPackage);
+ }
+ proto.end(token);
+ }
}
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index 6df283c..d320fb1 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -20,6 +20,7 @@
import android.app.PendingIntent;
import android.content.Context;
import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
import com.android.server.am.proto.ConnectionRecordProto;
@@ -37,7 +38,43 @@
final PendingIntent clientIntent; // How to launch the client.
String stringName; // Caching of toString.
boolean serviceDead; // Well is it?
-
+
+ // Please keep the following two enum list synced.
+ private static int[] BIND_ORIG_ENUMS = new int[] {
+ Context.BIND_AUTO_CREATE,
+ Context.BIND_DEBUG_UNBIND,
+ Context.BIND_NOT_FOREGROUND,
+ Context.BIND_IMPORTANT_BACKGROUND,
+ Context.BIND_ABOVE_CLIENT,
+ Context.BIND_ALLOW_OOM_MANAGEMENT,
+ Context.BIND_WAIVE_PRIORITY,
+ Context.BIND_IMPORTANT,
+ Context.BIND_ADJUST_WITH_ACTIVITY,
+ Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
+ Context.BIND_FOREGROUND_SERVICE,
+ Context.BIND_TREAT_LIKE_ACTIVITY,
+ Context.BIND_VISIBLE,
+ Context.BIND_SHOWING_UI,
+ Context.BIND_NOT_VISIBLE,
+ };
+ private static int[] BIND_PROTO_ENUMS = new int[] {
+ ConnectionRecordProto.AUTO_CREATE,
+ ConnectionRecordProto.DEBUG_UNBIND,
+ ConnectionRecordProto.NOT_FG,
+ ConnectionRecordProto.IMPORTANT_BG,
+ ConnectionRecordProto.ABOVE_CLIENT,
+ ConnectionRecordProto.ALLOW_OOM_MANAGEMENT,
+ ConnectionRecordProto.WAIVE_PRIORITY,
+ ConnectionRecordProto.IMPORTANT,
+ ConnectionRecordProto.ADJUST_WITH_ACTIVITY,
+ ConnectionRecordProto.FG_SERVICE_WHILE_AWAKE,
+ ConnectionRecordProto.FG_SERVICE,
+ ConnectionRecordProto.TREAT_LIKE_ACTIVITY,
+ ConnectionRecordProto.VISIBLE,
+ ConnectionRecordProto.SHOWING_UI,
+ ConnectionRecordProto.NOT_VISIBLE,
+ };
+
void dump(PrintWriter pw, String prefix) {
pw.println(prefix + "binding=" + binding);
if (activity != null) {
@@ -46,7 +83,7 @@
pw.println(prefix + "conn=" + conn.asBinder()
+ " flags=0x" + Integer.toHexString(flags));
}
-
+
ConnectionRecord(AppBindRecord _binding, ActivityRecord _activity,
IServiceConnection _conn, int _flags,
int _clientLabel, PendingIntent _clientIntent) {
@@ -131,51 +168,8 @@
if (binding.client != null) {
proto.write(ConnectionRecordProto.USER_ID, binding.client.userId);
}
- if ((flags&Context.BIND_AUTO_CREATE) != 0) {
- proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.AUTO_CREATE);
- }
- if ((flags&Context.BIND_DEBUG_UNBIND) != 0) {
- proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.DEBUG_UNBIND);
- }
- if ((flags&Context.BIND_NOT_FOREGROUND) != 0) {
- proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.NOT_FG);
- }
- if ((flags&Context.BIND_IMPORTANT_BACKGROUND) != 0) {
- proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.IMPORTANT_BG);
- }
- if ((flags&Context.BIND_ABOVE_CLIENT) != 0) {
- proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ABOVE_CLIENT);
- }
- if ((flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
- proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ALLOW_OOM_MANAGEMENT);
- }
- if ((flags&Context.BIND_WAIVE_PRIORITY) != 0) {
- proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.WAIVE_PRIORITY);
- }
- if ((flags&Context.BIND_IMPORTANT) != 0) {
- proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.IMPORTANT);
- }
- if ((flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
- proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ADJUST_WITH_ACTIVITY);
- }
- if ((flags&Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE) != 0) {
- proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.FG_SERVICE_WHILE_WAKE);
- }
- if ((flags&Context.BIND_FOREGROUND_SERVICE) != 0) {
- proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.FG_SERVICE);
- }
- if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
- proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.TREAT_LIKE_ACTIVITY);
- }
- if ((flags&Context.BIND_VISIBLE) != 0) {
- proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.VISIBLE);
- }
- if ((flags&Context.BIND_SHOWING_UI) != 0) {
- proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.SHOWING_UI);
- }
- if ((flags&Context.BIND_NOT_VISIBLE) != 0) {
- proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.NOT_VISIBLE);
- }
+ ProtoUtils.writeBitWiseFlagsToProtoEnum(proto, ConnectionRecordProto.FLAGS,
+ flags, BIND_ORIG_ENUMS, BIND_PROTO_ENUMS);
if (serviceDead) {
proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.DEAD);
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 77f5c16..29bfebe 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -24,6 +24,7 @@
import java.nio.ByteBuffer;
import android.app.ActivityManager;
+import android.app.ActivityManagerProto;
import android.os.Build;
import android.os.SystemClock;
import com.android.internal.util.MemInfoReader;
@@ -416,6 +417,53 @@
return procState;
}
+ public static int makeProcStateProtoEnum(int curProcState) {
+ switch (curProcState) {
+ case ActivityManager.PROCESS_STATE_PERSISTENT:
+ return ActivityManagerProto.PROCESS_STATE_PERSISTENT;
+ case ActivityManager.PROCESS_STATE_PERSISTENT_UI:
+ return ActivityManagerProto.PROCESS_STATE_PERSISTENT_UI;
+ case ActivityManager.PROCESS_STATE_TOP:
+ return ActivityManagerProto.PROCESS_STATE_TOP;
+ case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
+ return ActivityManagerProto.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
+ return ActivityManagerProto.PROCESS_STATE_FOREGROUND_SERVICE;
+ case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
+ return ActivityManagerProto.PROCESS_STATE_TOP_SLEEPING;
+ case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
+ return ActivityManagerProto.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND:
+ return ActivityManagerProto.PROCESS_STATE_IMPORTANT_BACKGROUND;
+ case ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND:
+ return ActivityManagerProto.PROCESS_STATE_TRANSIENT_BACKGROUND;
+ case ActivityManager.PROCESS_STATE_BACKUP:
+ return ActivityManagerProto.PROCESS_STATE_BACKUP;
+ case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT:
+ return ActivityManagerProto.PROCESS_STATE_HEAVY_WEIGHT;
+ case ActivityManager.PROCESS_STATE_SERVICE:
+ return ActivityManagerProto.PROCESS_STATE_SERVICE;
+ case ActivityManager.PROCESS_STATE_RECEIVER:
+ return ActivityManagerProto.PROCESS_STATE_RECEIVER;
+ case ActivityManager.PROCESS_STATE_HOME:
+ return ActivityManagerProto.PROCESS_STATE_HOME;
+ case ActivityManager.PROCESS_STATE_LAST_ACTIVITY:
+ return ActivityManagerProto.PROCESS_STATE_LAST_ACTIVITY;
+ case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
+ return ActivityManagerProto.PROCESS_STATE_CACHED_ACTIVITY;
+ case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+ return ActivityManagerProto.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
+ case ActivityManager.PROCESS_STATE_CACHED_RECENT:
+ return ActivityManagerProto.PROCESS_STATE_CACHED_RECENT;
+ case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
+ return ActivityManagerProto.PROCESS_STATE_CACHED_EMPTY;
+ case ActivityManager.PROCESS_STATE_NONEXISTENT:
+ return ActivityManagerProto.PROCESS_STATE_NONEXISTENT;
+ default:
+ return -1;
+ }
+ }
+
public static void appendRamKb(StringBuilder sb, long ramKb) {
for (int j=0, fact=10; j<6; j++, fact*=10) {
if (ramKb < fact) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index a1e5947..03e140d 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -679,6 +679,7 @@
proto.write(ProcessRecordProto.ISOLATED_APP_ID, UserHandle.getAppId(uid));
}
}
+ proto.write(ProcessRecordProto.PERSISTENT, persistent);
proto.end(token);
}
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index 8efcb4f..3886e5a 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -18,13 +18,17 @@
import android.Manifest;
import android.app.ActivityManager;
+import android.app.ActivityManagerProto;
import android.content.pm.PackageManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.am.proto.UidRecordProto;
/**
* Overall information about a uid that has actively running processes.
@@ -86,6 +90,22 @@
static final int CHANGE_CACHED = 1<<3;
static final int CHANGE_UNCACHED = 1<<4;
+ // Keep the enum lists in sync
+ private static int[] ORIG_ENUMS = new int[] {
+ CHANGE_GONE,
+ CHANGE_IDLE,
+ CHANGE_ACTIVE,
+ CHANGE_CACHED,
+ CHANGE_UNCACHED,
+ };
+ private static int[] PROTO_ENUMS = new int[] {
+ UidRecordProto.CHANGE_GONE,
+ UidRecordProto.CHANGE_IDLE,
+ UidRecordProto.CHANGE_ACTIVE,
+ UidRecordProto.CHANGE_CACHED,
+ UidRecordProto.CHANGE_UNCACHED,
+ };
+
static final class ChangeItem {
UidRecord uidRecord;
int uid;
@@ -125,6 +145,34 @@
}
}
+
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(UidRecordProto.HEX_HASH, Integer.toHexString(System.identityHashCode(this)));
+ proto.write(UidRecordProto.UID, uid);
+ proto.write(UidRecordProto.CURRENT, ProcessList.makeProcStateProtoEnum(curProcState));
+ proto.write(UidRecordProto.EPHEMERAL, ephemeral);
+ proto.write(UidRecordProto.FG_SERVICES, foregroundServices);
+ proto.write(UidRecordProto.WHILELIST, curWhitelist);
+ ProtoUtils.toDuration(proto, UidRecordProto.LAST_BACKGROUND_TIME,
+ lastBackgroundTime, SystemClock.elapsedRealtime());
+ proto.write(UidRecordProto.IDLE, idle);
+ if (lastReportedChange != 0) {
+ ProtoUtils.writeBitWiseFlagsToProtoEnum(proto, UidRecordProto.LAST_REPORTED_CHANGES,
+ lastReportedChange, ORIG_ENUMS, PROTO_ENUMS);
+ }
+ proto.write(UidRecordProto.NUM_PROCS, numProcs);
+
+ long seqToken = proto.start(UidRecordProto.NETWORK_STATE_UPDATE);
+ proto.write(UidRecordProto.ProcStateSequence.CURURENT, curProcStateSeq);
+ proto.write(UidRecordProto.ProcStateSequence.LAST_NETWORK_UPDATED,
+ lastNetworkUpdatedProcStateSeq);
+ proto.write(UidRecordProto.ProcStateSequence.LAST_DISPATCHED, lastDispatchedProcStateSeq);
+ proto.end(seqToken);
+
+ proto.end(token);
+ }
+
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("UidRecord{");
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 5ada484..7b0c714 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -83,6 +83,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TimingsTraceLog;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -94,6 +95,7 @@
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemServiceManager;
+import com.android.server.am.proto.UserControllerProto;
import com.android.server.pm.UserManagerService;
import com.android.server.wm.WindowManagerService;
@@ -1844,6 +1846,36 @@
}
}
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ synchronized (mLock) {
+ long token = proto.start(fieldId);
+ for (int i = 0; i < mStartedUsers.size(); i++) {
+ UserState uss = mStartedUsers.valueAt(i);
+ final long uToken = proto.start(UserControllerProto.STARTED_USERS);
+ proto.write(UserControllerProto.User.ID, uss.mHandle.getIdentifier());
+ uss.writeToProto(proto, UserControllerProto.User.STATE);
+ proto.end(uToken);
+ }
+ for (int i = 0; i < mStartedUserArray.length; i++) {
+ proto.write(UserControllerProto.STARTED_USER_ARRAY, mStartedUserArray[i]);
+ }
+ for (int i = 0; i < mUserLru.size(); i++) {
+ proto.write(UserControllerProto.USER_LRU, mUserLru.get(i));
+ }
+ if (mUserProfileGroupIds.size() > 0) {
+ for (int i = 0; i < mUserProfileGroupIds.size(); i++) {
+ final long uToken = proto.start(UserControllerProto.USER_PROFILE_GROUP_IDS);
+ proto.write(UserControllerProto.UserProfile.USER,
+ mUserProfileGroupIds.keyAt(i));
+ proto.write(UserControllerProto.UserProfile.PROFILE,
+ mUserProfileGroupIds.valueAt(i));
+ proto.end(uToken);
+ }
+ }
+ proto.end(token);
+ }
+ }
+
void dump(PrintWriter pw, boolean dumpAll) {
synchronized (mLock) {
pw.println(" mStartedUsers:");
@@ -1868,10 +1900,6 @@
pw.print(mUserLru.get(i));
}
pw.println("]");
- if (dumpAll) {
- pw.print(" mStartedUserArray: ");
- pw.println(Arrays.toString(mStartedUserArray));
- }
if (mUserProfileGroupIds.size() > 0) {
pw.println(" mUserProfileGroupIds:");
for (int i=0; i< mUserProfileGroupIds.size(); i++) {
diff --git a/services/core/java/com/android/server/am/UserState.java b/services/core/java/com/android/server/am/UserState.java
index d36d9cb..00597e2 100644
--- a/services/core/java/com/android/server/am/UserState.java
+++ b/services/core/java/com/android/server/am/UserState.java
@@ -24,8 +24,10 @@
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.util.ProgressReporter;
+import com.android.server.am.proto.UserStateProto;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -112,10 +114,29 @@
}
}
+ public static int stateToProtoEnum(int state) {
+ switch (state) {
+ case STATE_BOOTING: return UserStateProto.STATE_BOOTING;
+ case STATE_RUNNING_LOCKED: return UserStateProto.STATE_RUNNING_LOCKED;
+ case STATE_RUNNING_UNLOCKING: return UserStateProto.STATE_RUNNING_UNLOCKING;
+ case STATE_RUNNING_UNLOCKED: return UserStateProto.STATE_RUNNING_UNLOCKED;
+ case STATE_STOPPING: return UserStateProto.STATE_STOPPING;
+ case STATE_SHUTDOWN: return UserStateProto.STATE_SHUTDOWN;
+ default: return state;
+ }
+ }
+
void dump(String prefix, PrintWriter pw) {
pw.print(prefix);
pw.print("state="); pw.print(stateToString(state));
if (switching) pw.print(" SWITCHING");
pw.println();
}
+
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(UserStateProto.STATE, stateToProtoEnum(state));
+ proto.write(UserStateProto.SWITCHING, switching);
+ proto.end(token);
+ }
}
diff --git a/services/core/java/com/android/server/am/VrController.java b/services/core/java/com/android/server/am/VrController.java
index feddfe3..d32db7e 100644
--- a/services/core/java/com/android/server/am/VrController.java
+++ b/services/core/java/com/android/server/am/VrController.java
@@ -20,7 +20,11 @@
import android.os.Process;
import android.service.vr.IPersistentVrStateCallbacks;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
+
import com.android.server.LocalServices;
+import com.android.server.am.proto.ProcessesProto.VrControllerProto;
import com.android.server.vr.VrManagerInternal;
/**
@@ -49,6 +53,18 @@
private static final int FLAG_VR_MODE = 1;
private static final int FLAG_PERSISTENT_VR_MODE = 2;
+ // Keep the enum lists in sync
+ private static int[] ORIG_ENUMS = new int[] {
+ FLAG_NON_VR_MODE,
+ FLAG_VR_MODE,
+ FLAG_PERSISTENT_VR_MODE,
+ };
+ private static int[] PROTO_ENUMS = new int[] {
+ VrControllerProto.FLAG_NON_VR_MODE,
+ VrControllerProto.FLAG_VR_MODE,
+ VrControllerProto.FLAG_PERSISTENT_VR_MODE,
+ };
+
// Invariants maintained for mVrState
//
// Always true:
@@ -420,4 +436,12 @@
public String toString() {
return String.format("[VrState=0x%x,VrRenderThreadTid=%d]", mVrState, mVrRenderThreadTid);
}
+
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ ProtoUtils.writeBitWiseFlagsToProtoEnum(proto, VrControllerProto.VR_MODE,
+ mVrState, ORIG_ENUMS, PROTO_ENUMS);
+ proto.write(VrControllerProto.RENDER_THREAD_ID, mVrRenderThreadTid);
+ proto.end(token);
+ }
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f4c99f5..bedf043 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -138,7 +138,6 @@
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -771,7 +770,7 @@
// Register for device connection intent broadcasts.
IntentFilter intentFilter =
new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
- intentFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+ intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
@@ -2967,14 +2966,28 @@
}
public void setBluetoothScoOnInt(boolean on, String eventSource) {
+ if (DEBUG_DEVICES) {
+ Log.d(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
+ }
if (on) {
// do not accept SCO ON if SCO audio is not connected
- synchronized(mScoClients) {
- if ((mBluetoothHeadset != null) &&
- (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
- != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
- mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
- return;
+ synchronized (mScoClients) {
+ if (mBluetoothHeadset != null) {
+ if (mBluetoothHeadsetDevice == null) {
+ BluetoothDevice activeDevice = mBluetoothHeadset.getActiveDevice();
+ if (activeDevice != null) {
+ // setBtScoActiveDevice() might trigger resetBluetoothSco() which
+ // will call setBluetoothScoOnInt(false, "resetBluetoothSco")
+ setBtScoActiveDevice(activeDevice);
+ }
+ }
+ if (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+ != BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+ mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
+ Log.w(TAG, "setBluetoothScoOnInt(true) failed because "
+ + mBluetoothHeadsetDevice + " is not in audio connected mode");
+ return;
+ }
}
}
mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
@@ -3362,24 +3375,23 @@
}
}
- void setBtScoDeviceConnectionState(BluetoothDevice btDevice, int state) {
+ private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
if (btDevice == null) {
- return;
+ return true;
}
-
String address = btDevice.getAddress();
BluetoothClass btClass = btDevice.getBluetoothClass();
int outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
if (btClass != null) {
switch (btClass.getDeviceClass()) {
- case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
- case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
- outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
- break;
- case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
- outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
- break;
+ case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
+ case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
+ outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
+ break;
+ case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+ outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
+ break;
}
}
@@ -3387,34 +3399,33 @@
address = "";
}
- boolean connected = (state == BluetoothProfile.STATE_CONNECTED);
-
String btDeviceName = btDevice.getName();
- boolean success =
- handleDeviceConnection(connected, outDevice, address, btDeviceName) &&
- handleDeviceConnection(connected, inDevice, address, btDeviceName);
+ boolean result = handleDeviceConnection(isActive, outDevice, address, btDeviceName);
+ // handleDeviceConnection() && result to make sure the method get executed
+ result = handleDeviceConnection(isActive, inDevice, address, btDeviceName) && result;
+ return result;
+ }
- if (!success) {
- return;
+ void setBtScoActiveDevice(BluetoothDevice btDevice) {
+ if (DEBUG_DEVICES) {
+ Log.d(TAG, "setBtScoActiveDevice(" + btDevice + ")");
}
-
- /* When one BT headset is disconnected while another BT headset
- * is connected, don't mess with the headset device.
- */
- if ((state == BluetoothProfile.STATE_DISCONNECTED ||
- state == BluetoothProfile.STATE_DISCONNECTING) &&
- mBluetoothHeadset != null &&
- mBluetoothHeadset.getAudioState(btDevice) == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
- Log.w(TAG, "SCO connected through another device, returning");
- return;
- }
-
synchronized (mScoClients) {
- if (connected) {
+ final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
+ if (!Objects.equals(btDevice, previousActiveDevice)) {
+ if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
+ Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device "
+ + previousActiveDevice);
+ }
+ if (!handleBtScoActiveDeviceChange(btDevice, true)) {
+ Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice);
+ // set mBluetoothHeadsetDevice to null when failing to add new device
+ btDevice = null;
+ }
mBluetoothHeadsetDevice = btDevice;
- } else {
- mBluetoothHeadsetDevice = null;
- resetBluetoothSco();
+ if (mBluetoothHeadsetDevice == null) {
+ resetBluetoothSco();
+ }
}
}
}
@@ -3469,12 +3480,7 @@
// Discard timeout message
mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
mBluetoothHeadset = (BluetoothHeadset) proxy;
- deviceList = mBluetoothHeadset.getConnectedDevices();
- if (deviceList.size() > 0) {
- mBluetoothHeadsetDevice = deviceList.get(0);
- } else {
- mBluetoothHeadsetDevice = null;
- }
+ setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice());
// Refresh SCO audio state
checkScoAudioState();
// Continue pending action if any
@@ -3595,10 +3601,7 @@
void disconnectHeadset() {
synchronized (mScoClients) {
- if (mBluetoothHeadsetDevice != null) {
- setBtScoDeviceConnectionState(mBluetoothHeadsetDevice,
- BluetoothProfile.STATE_DISCONNECTED);
- }
+ setBtScoActiveDevice(null);
mBluetoothHeadset = null;
}
}
@@ -5788,11 +5791,9 @@
AudioSystem.setForceUse(AudioSystem.FOR_DOCK, config);
}
mDockState = dockState;
- } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
- state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
- BluetoothProfile.STATE_DISCONNECTED);
+ } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- setBtScoDeviceConnectionState(btDevice, state);
+ setBtScoActiveDevice(btDevice);
} else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
boolean broadcast = false;
int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
@@ -6634,7 +6635,19 @@
// Inform AudioFlinger of our device's low RAM attribute
private static void readAndSetLowRamDevice()
{
- int status = AudioSystem.setLowRamDevice(ActivityManager.isLowRamDeviceStatic());
+ boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
+ long totalMemory = 1024 * 1024 * 1024; // 1GB is the default if ActivityManager fails.
+
+ try {
+ final ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
+ ActivityManager.getService().getMemoryInfo(info);
+ totalMemory = info.totalMem;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot obtain MemoryInfo from ActivityManager, assume low memory device");
+ isLowRamDevice = true;
+ }
+
+ final int status = AudioSystem.setLowRamDevice(isLowRamDevice, totalMemory);
if (status != 0) {
Log.w(TAG, "AudioFlinger informed of device's low RAM attribute; status " + status);
}
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
index 10e6cad..4289a25 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
@@ -20,6 +20,8 @@
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
import android.hardware.radio.IRadioService;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
@@ -28,14 +30,18 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.internal.util.Preconditions;
import com.android.server.SystemService;
+import com.android.server.broadcastradio.hal2.AnnouncementAggregator;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.OptionalInt;
public class BroadcastRadioService extends SystemService {
private static final String TAG = "BcRadioSrv";
+ private static final boolean DEBUG = false;
private final ServiceImpl mServiceImpl = new ServiceImpl();
@@ -88,7 +94,7 @@
@Override
public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
boolean withAudio, ITunerCallback callback) throws RemoteException {
- Slog.i(TAG, "openTuner(" + moduleId + ", _, " + withAudio + ", _)");
+ if (DEBUG) Slog.i(TAG, "Opening module " + moduleId);
enforcePolicyAccess();
if (callback == null) {
throw new IllegalArgumentException("Callback must not be empty");
@@ -101,5 +107,25 @@
}
}
}
+
+ @Override
+ public ICloseHandle addAnnouncementListener(int[] enabledTypes,
+ IAnnouncementListener listener) {
+ if (DEBUG) {
+ Slog.i(TAG, "Adding announcement listener for " + Arrays.toString(enabledTypes));
+ }
+ Objects.requireNonNull(enabledTypes);
+ Objects.requireNonNull(listener);
+ enforcePolicyAccess();
+
+ synchronized (mLock) {
+ if (!mHal2.hasAnyModules()) {
+ Slog.i(TAG, "There are no HAL 2.x modules registered");
+ return new AnnouncementAggregator(listener);
+ }
+
+ return mHal2.addAnnouncementListener(enabledTypes, listener);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
new file mode 100644
index 0000000..0bbaf25
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
@@ -0,0 +1,128 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.hal2;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+public class AnnouncementAggregator extends ICloseHandle.Stub {
+ private static final String TAG = "BcRadio2Srv.AnnAggr";
+
+ private final Object mLock = new Object();
+ @NonNull private final IAnnouncementListener mListener;
+ private final IBinder.DeathRecipient mDeathRecipient = new DeathRecipient();
+
+ @GuardedBy("mLock")
+ private final Collection<ModuleWatcher> mModuleWatchers = new ArrayList<>();
+
+ @GuardedBy("mLock")
+ private boolean mIsClosed = false;
+
+ public AnnouncementAggregator(@NonNull IAnnouncementListener listener) {
+ mListener = Objects.requireNonNull(listener);
+ try {
+ listener.asBinder().linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ }
+
+ private class ModuleWatcher extends IAnnouncementListener.Stub {
+ private @Nullable ICloseHandle mCloseHandle;
+ public @NonNull List<Announcement> currentList = new ArrayList<>();
+
+ public void onListUpdated(List<Announcement> active) {
+ currentList = Objects.requireNonNull(active);
+ AnnouncementAggregator.this.onListUpdated();
+ }
+
+ public void setCloseHandle(@NonNull ICloseHandle closeHandle) {
+ mCloseHandle = Objects.requireNonNull(closeHandle);
+ }
+
+ public void close() throws RemoteException {
+ if (mCloseHandle != null) mCloseHandle.close();
+ }
+ }
+
+ private class DeathRecipient implements IBinder.DeathRecipient {
+ public void binderDied() {
+ try {
+ close();
+ } catch (RemoteException ex) {}
+ }
+ }
+
+ private void onListUpdated() {
+ synchronized (mLock) {
+ if (mIsClosed) {
+ Slog.e(TAG, "Announcement aggregator is closed, it shouldn't receive callbacks");
+ return;
+ }
+ List<Announcement> combined = new ArrayList<>();
+ for (ModuleWatcher watcher : mModuleWatchers) {
+ combined.addAll(watcher.currentList);
+ }
+ TunerCallback.dispatch(() -> mListener.onListUpdated(combined));
+ }
+ }
+
+ public void watchModule(@NonNull RadioModule module, @NonNull int[] enabledTypes) {
+ synchronized (mLock) {
+ if (mIsClosed) throw new IllegalStateException();
+
+ ModuleWatcher watcher = new ModuleWatcher();
+ ICloseHandle closeHandle;
+ try {
+ closeHandle = module.addAnnouncementListener(enabledTypes, watcher);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to add announcement listener", ex);
+ return;
+ }
+ watcher.setCloseHandle(closeHandle);
+ mModuleWatchers.add(watcher);
+ }
+ }
+
+ @Override
+ public void close() throws RemoteException {
+ synchronized (mLock) {
+ if (mIsClosed) return;
+ mIsClosed = true;
+
+ mListener.asBinder().unlinkToDeath(mDeathRecipient, 0);
+
+ for (ModuleWatcher watcher : mModuleWatchers) {
+ watcher.close();
+ }
+ mModuleWatchers.clear();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index fc9a5d6..406231a 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
@@ -81,6 +83,10 @@
return mModules.containsKey(id);
}
+ public boolean hasAnyModules() {
+ return !mModules.isEmpty();
+ }
+
public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
Objects.requireNonNull(callback);
@@ -98,4 +104,22 @@
session.setConfiguration(legacyConfig);
return session;
}
+
+ public ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
+ @NonNull IAnnouncementListener listener) {
+ AnnouncementAggregator aggregator = new AnnouncementAggregator(listener);
+ boolean anySupported = false;
+ for (RadioModule module : mModules.values()) {
+ try {
+ aggregator.watchModule(module, enabledTypes);
+ anySupported = true;
+ } catch (UnsupportedOperationException ex) {
+ Slog.v(TAG, "Announcements not supported for this module", ex);
+ }
+ }
+ if (!anySupported) {
+ Slog.i(TAG, "There are no HAL modules that support announcements");
+ }
+ return aggregator;
+ }
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
index 60a927c..7a95971 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
@@ -20,6 +20,8 @@
import android.annotation.Nullable;
import android.hardware.broadcastradio.V2_0.AmFmBandRange;
import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
+import android.hardware.broadcastradio.V2_0.Announcement;
+import android.hardware.broadcastradio.V2_0.IdentifierType;
import android.hardware.broadcastradio.V2_0.ProgramFilter;
import android.hardware.broadcastradio.V2_0.ProgramIdentifier;
import android.hardware.broadcastradio.V2_0.ProgramInfo;
@@ -36,6 +38,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -218,31 +221,38 @@
return hwId;
}
- static @NonNull ProgramSelector.Identifier programIdentifierFromHal(@NonNull ProgramIdentifier id) {
+ static @Nullable ProgramSelector.Identifier programIdentifierFromHal(
+ @NonNull ProgramIdentifier id) {
+ if (id.type == IdentifierType.INVALID) return null;
return new ProgramSelector.Identifier(id.type, id.value);
}
static @NonNull ProgramSelector programSelectorFromHal(
@NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) {
- ProgramSelector.Identifier[] secondaryIds = sel.secondaryIds.stream().map(
- id -> programIdentifierFromHal(id)).toArray(ProgramSelector.Identifier[]::new);
+ ProgramSelector.Identifier[] secondaryIds = sel.secondaryIds.stream().
+ map(id -> Objects.requireNonNull(programIdentifierFromHal(id))).
+ toArray(ProgramSelector.Identifier[]::new);
return new ProgramSelector(
- identifierTypeToProgramType(sel.primaryId.type),
- programIdentifierFromHal(sel.primaryId),
- secondaryIds, null);
+ identifierTypeToProgramType(sel.primaryId.type),
+ Objects.requireNonNull(programIdentifierFromHal(sel.primaryId)),
+ secondaryIds, null);
}
static @NonNull RadioManager.ProgramInfo programInfoFromHal(@NonNull ProgramInfo info) {
+ Collection<ProgramSelector.Identifier> relatedContent = info.relatedContent.stream().
+ map(id -> Objects.requireNonNull(programIdentifierFromHal(id))).
+ collect(Collectors.toList());
+
return new RadioManager.ProgramInfo(
- programSelectorFromHal(info.selector),
- (info.infoFlags & ProgramInfoFlags.TUNED) != 0,
- (info.infoFlags & ProgramInfoFlags.STEREO) != 0,
- false, // TODO(b/69860743): digital
- info.signalQuality,
- null, // TODO(b/69860743): metadata
- info.infoFlags,
- vendorInfoFromHal(info.vendorInfo)
+ programSelectorFromHal(info.selector),
+ programIdentifierFromHal(info.logicallyTunedTo),
+ programIdentifierFromHal(info.physicallyTunedTo),
+ relatedContent,
+ info.infoFlags,
+ info.signalQuality,
+ null, // TODO(b/69860743): metadata
+ vendorInfoFromHal(info.vendorInfo)
);
}
@@ -259,11 +269,21 @@
}
static @NonNull ProgramList.Chunk programListChunkFromHal(@NonNull ProgramListChunk chunk) {
- Set<RadioManager.ProgramInfo> modified = chunk.modified.stream().map(
- info -> programInfoFromHal(info)).collect(Collectors.toSet());
- Set<ProgramSelector.Identifier> removed = chunk.removed.stream().map(
- id -> programIdentifierFromHal(id)).collect(Collectors.toSet());
+ Set<RadioManager.ProgramInfo> modified = chunk.modified.stream().
+ map(info -> programInfoFromHal(info)).collect(Collectors.toSet());
+ Set<ProgramSelector.Identifier> removed = chunk.removed.stream().
+ map(id -> Objects.requireNonNull(programIdentifierFromHal(id))).
+ collect(Collectors.toSet());
return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed);
}
+
+ public static @NonNull android.hardware.radio.Announcement announcementFromHal(
+ @NonNull Announcement hwAnnouncement) {
+ return new android.hardware.radio.Announcement(
+ programSelectorFromHal(hwAnnouncement.selector),
+ hwAnnouncement.type,
+ vendorInfoFromHal(hwAnnouncement.vendorInfo)
+ );
+ }
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index c8e15c1..4dff9e0 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -21,7 +21,10 @@
import android.hardware.radio.ITuner;
import android.hardware.radio.RadioManager;
import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
+import android.hardware.broadcastradio.V2_0.Announcement;
+import android.hardware.broadcastradio.V2_0.IAnnouncementListener;
import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
+import android.hardware.broadcastradio.V2_0.ICloseHandle;
import android.hardware.broadcastradio.V2_0.ITunerSession;
import android.hardware.broadcastradio.V2_0.Result;
import android.os.ParcelableException;
@@ -29,7 +32,11 @@
import android.util.MutableInt;
import android.util.Slog;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.Objects;
+import java.util.stream.Collectors;
class RadioModule {
private static final String TAG = "BcRadio2Srv.module";
@@ -79,4 +86,37 @@
return new TunerSession(hwSession.value, cb);
}
+
+ public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
+ @NonNull android.hardware.radio.IAnnouncementListener listener) throws RemoteException {
+ ArrayList<Byte> enabledList = new ArrayList<>();
+ for (int type : enabledTypes) {
+ enabledList.add((byte)type);
+ }
+
+ MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
+ Mutable<ICloseHandle> hwCloseHandle = new Mutable<>();
+ IAnnouncementListener hwListener = new IAnnouncementListener.Stub() {
+ public void onListUpdated(ArrayList<Announcement> hwAnnouncements)
+ throws RemoteException {
+ listener.onListUpdated(hwAnnouncements.stream().
+ map(a -> Convert.announcementFromHal(a)).collect(Collectors.toList()));
+ }
+ };
+ mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHandle) -> {
+ halResult.value = result;
+ hwCloseHandle.value = closeHandle;
+ });
+ Convert.throwOnError("addAnnouncementListener", halResult.value);
+
+ return new android.hardware.radio.ICloseHandle.Stub() {
+ public void close() {
+ try {
+ hwCloseHandle.value.close();
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed closing announcement listener", ex);
+ }
+ }
+ };
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/ConnectivityConstants.java b/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
new file mode 100644
index 0000000..24865bc
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+/**
+ * A class encapsulating various constants used by Connectivity.
+ * @hide
+ */
+public class ConnectivityConstants {
+ // IPC constants
+ public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
+ "android.net.conn.NETWORK_CONDITIONS_MEASURED";
+ public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
+ public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
+ public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
+ public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
+ public static final String EXTRA_CELL_ID = "extra_cellid";
+ public static final String EXTRA_SSID = "extra_ssid";
+ public static final String EXTRA_BSSID = "extra_bssid";
+ /** real time since boot */
+ public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
+ public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
+
+ public static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
+ "android.permission.ACCESS_NETWORK_CONDITIONS";
+
+ // Penalty applied to scores of Networks that have not been validated.
+ public static final int UNVALIDATED_SCORE_PENALTY = 40;
+
+ // Score for explicitly connected network.
+ //
+ // This ensures that a) the explicitly selected network is never trumped by anything else, and
+ // b) the explicitly selected network is never torn down.
+ public static final int MAXIMUM_NETWORK_SCORE = 100;
+ // VPNs typically have priority over other networks. Give them a score that will
+ // let them win every single time.
+ public static final int VPN_DEFAULT_SCORE = 101;
+}
diff --git a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java b/services/core/java/com/android/server/connectivity/KeepalivePacketData.java
index 2ccfdd1..f6b73b7 100644
--- a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java
+++ b/services/core/java/com/android/server/connectivity/KeepalivePacketData.java
@@ -16,6 +16,9 @@
package com.android.server.connectivity;
+import static android.net.util.NetworkConstants.IPV4_HEADER_MIN_LEN;
+import static android.net.util.NetworkConstants.UDP_HEADER_LEN;
+
import android.system.OsConstants;
import android.net.ConnectivityManager;
import android.net.NetworkUtils;
@@ -57,9 +60,6 @@
/** Packet data. A raw byte string of packet data, not including the link-layer header. */
public final byte[] data;
- private static final int IPV4_HEADER_LENGTH = 20;
- private static final int UDP_HEADER_LENGTH = 8;
-
protected KeepalivePacketData(InetAddress srcAddress, int srcPort,
InetAddress dstAddress, int dstPort, byte[] data) throws InvalidPacketException {
this.srcAddress = srcAddress;
@@ -111,7 +111,7 @@
throw new InvalidPacketException(ERROR_INVALID_PORT);
}
- int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1;
+ int length = IPV4_HEADER_MIN_LEN + UDP_HEADER_LEN + 1;
ByteBuffer buf = ByteBuffer.allocate(length);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort((short) 0x4500); // IP version and TOS
@@ -130,7 +130,7 @@
buf.putShort((short) 0); // UDP checksum
buf.put((byte) 0xff); // NAT-T keepalive
buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0));
- buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_LENGTH));
+ buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_MIN_LEN));
return new KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array());
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index a4d7242..85b70ca 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -223,14 +223,6 @@
// This represents the last score received from the NetworkAgent.
private int currentScore;
- // Penalty applied to scores of Networks that have not been validated.
- private static final int UNVALIDATED_SCORE_PENALTY = 40;
-
- // Score for explicitly connected network.
- //
- // This ensures that a) the explicitly selected network is never trumped by anything else, and
- // b) the explicitly selected network is never torn down.
- private static final int MAXIMUM_NETWORK_SCORE = 100;
// The list of NetworkRequests being satisfied by this Network.
private final SparseArray<NetworkRequest> mNetworkRequests = new SparseArray<>();
@@ -428,12 +420,12 @@
// down an explicitly selected network before the user gets a chance to prefer it when
// a higher-scoring network (e.g., Ethernet) is available.
if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
- return MAXIMUM_NETWORK_SCORE;
+ return ConnectivityConstants.MAXIMUM_NETWORK_SCORE;
}
int score = currentScore;
if (!lastValidated && !pretendValidated && !ignoreWifiUnvalidationPenalty()) {
- score -= UNVALIDATED_SCORE_PENALTY;
+ score -= ConnectivityConstants.UNVALIDATED_SCORE_PENALTY;
}
if (score < 0) score = 0;
return score;
diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
index 85d1d1e..c471f0c 100644
--- a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
@@ -24,6 +24,7 @@
import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.TrafficStats;
+import android.net.util.NetworkConstants;
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
@@ -421,8 +422,6 @@
private class IcmpCheck extends SimpleSocketCheck implements Runnable {
private static final int TIMEOUT_SEND = 100;
private static final int TIMEOUT_RECV = 300;
- private static final int ICMPV4_ECHO_REQUEST = 8;
- private static final int ICMPV6_ECHO_REQUEST = 128;
private static final int PACKET_BUFSIZE = 512;
private final int mProtocol;
private final int mIcmpType;
@@ -432,11 +431,11 @@
if (mAddressFamily == AF_INET6) {
mProtocol = IPPROTO_ICMPV6;
- mIcmpType = ICMPV6_ECHO_REQUEST;
+ mIcmpType = NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE;
mMeasurement.description = "ICMPv6";
} else {
mProtocol = IPPROTO_ICMP;
- mIcmpType = ICMPV4_ECHO_REQUEST;
+ mIcmpType = NetworkConstants.ICMPV4_ECHO_REQUEST_TYPE;
mMeasurement.description = "ICMPv4";
}
@@ -504,7 +503,6 @@
private class DnsUdpCheck extends SimpleSocketCheck implements Runnable {
private static final int TIMEOUT_SEND = 100;
private static final int TIMEOUT_RECV = 500;
- private static final int DNS_SERVER_PORT = 53;
private static final int RR_TYPE_A = 1;
private static final int RR_TYPE_AAAA = 28;
private static final int PACKET_BUFSIZE = 512;
@@ -546,7 +544,8 @@
}
try {
- setupSocket(SOCK_DGRAM, IPPROTO_UDP, TIMEOUT_SEND, TIMEOUT_RECV, DNS_SERVER_PORT);
+ setupSocket(SOCK_DGRAM, IPPROTO_UDP, TIMEOUT_SEND, TIMEOUT_RECV,
+ NetworkConstants.DNS_SERVER_PORT);
} catch (ErrnoException | IOException e) {
mMeasurement.recordFailure(e.toString());
return;
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index ed26858..8a2e71c 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -122,22 +122,6 @@
}
}
- public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
- "android.net.conn.NETWORK_CONDITIONS_MEASURED";
- public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
- public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
- public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
- public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
- public static final String EXTRA_CELL_ID = "extra_cellid";
- public static final String EXTRA_SSID = "extra_ssid";
- public static final String EXTRA_BSSID = "extra_bssid";
- /** real time since boot */
- public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
- public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
-
- private static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
- "android.permission.ACCESS_NETWORK_CONDITIONS";
-
// After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
// The network should be used as a default internet connection. It was found to be:
// 1. a functioning network providing internet access, or
@@ -1136,7 +1120,8 @@
return;
}
- Intent latencyBroadcast = new Intent(ACTION_NETWORK_CONDITIONS_MEASURED);
+ Intent latencyBroadcast =
+ new Intent(ConnectivityConstants.ACTION_NETWORK_CONDITIONS_MEASURED);
switch (mNetworkAgentInfo.networkInfo.getType()) {
case ConnectivityManager.TYPE_WIFI:
WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
@@ -1148,15 +1133,18 @@
// not change it here as it would become impossible to tell whether the SSID is
// simply being surrounded by quotes due to the API, or whether those quotes
// are actually part of the SSID.
- latencyBroadcast.putExtra(EXTRA_SSID, currentWifiInfo.getSSID());
- latencyBroadcast.putExtra(EXTRA_BSSID, currentWifiInfo.getBSSID());
+ latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_SSID,
+ currentWifiInfo.getSSID());
+ latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_BSSID,
+ currentWifiInfo.getBSSID());
} else {
if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
return;
}
break;
case ConnectivityManager.TYPE_MOBILE:
- latencyBroadcast.putExtra(EXTRA_NETWORK_TYPE, mTelephonyManager.getNetworkType());
+ latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_NETWORK_TYPE,
+ mTelephonyManager.getNetworkType());
List<CellInfo> info = mTelephonyManager.getAllCellInfo();
if (info == null) return;
int numRegisteredCellInfo = 0;
@@ -1170,16 +1158,16 @@
}
if (cellInfo instanceof CellInfoCdma) {
CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity();
- latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
+ latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
} else if (cellInfo instanceof CellInfoGsm) {
CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity();
- latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
+ latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
} else if (cellInfo instanceof CellInfoLte) {
CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity();
- latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
+ latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
} else if (cellInfo instanceof CellInfoWcdma) {
CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
- latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
+ latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
} else {
if (VDBG) logw("Registered cellinfo is unrecognized");
return;
@@ -1190,16 +1178,21 @@
default:
return;
}
- latencyBroadcast.putExtra(EXTRA_CONNECTIVITY_TYPE, mNetworkAgentInfo.networkInfo.getType());
- latencyBroadcast.putExtra(EXTRA_RESPONSE_RECEIVED, responseReceived);
- latencyBroadcast.putExtra(EXTRA_REQUEST_TIMESTAMP_MS, requestTimestampMs);
+ latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CONNECTIVITY_TYPE,
+ mNetworkAgentInfo.networkInfo.getType());
+ latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_RESPONSE_RECEIVED,
+ responseReceived);
+ latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_REQUEST_TIMESTAMP_MS,
+ requestTimestampMs);
if (responseReceived) {
- latencyBroadcast.putExtra(EXTRA_IS_CAPTIVE_PORTAL, isCaptivePortal);
- latencyBroadcast.putExtra(EXTRA_RESPONSE_TIMESTAMP_MS, responseTimestampMs);
+ latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_IS_CAPTIVE_PORTAL,
+ isCaptivePortal);
+ latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_RESPONSE_TIMESTAMP_MS,
+ responseTimestampMs);
}
mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT,
- PERMISSION_ACCESS_NETWORK_CONDITIONS);
+ ConnectivityConstants.PERMISSION_ACCESS_NETWORK_CONDITIONS);
}
private void logNetworkEvent(int evtype) {
diff --git a/services/core/java/com/android/server/connectivity/PacManager.java b/services/core/java/com/android/server/connectivity/PacManager.java
index d56fb1a..3a27fcb3 100644
--- a/services/core/java/com/android/server/connectivity/PacManager.java
+++ b/services/core/java/com/android/server/connectivity/PacManager.java
@@ -54,12 +54,12 @@
* @hide
*/
public class PacManager {
- public static final String PAC_PACKAGE = "com.android.pacprocessor";
- public static final String PAC_SERVICE = "com.android.pacprocessor.PacService";
- public static final String PAC_SERVICE_NAME = "com.android.net.IProxyService";
+ private static final String PAC_PACKAGE = "com.android.pacprocessor";
+ private static final String PAC_SERVICE = "com.android.pacprocessor.PacService";
+ private static final String PAC_SERVICE_NAME = "com.android.net.IProxyService";
- public static final String PROXY_PACKAGE = "com.android.proxyhandler";
- public static final String PROXY_SERVICE = "com.android.proxyhandler.ProxyService";
+ private static final String PROXY_PACKAGE = "com.android.proxyhandler";
+ private static final String PROXY_SERVICE = "com.android.proxyhandler.ProxyService";
private static final String TAG = "PacManager";
@@ -71,8 +71,6 @@
private static final int DELAY_LONG = 4;
private static final long MAX_PAC_SIZE = 20 * 1000 * 1000;
- /** Keep these values up-to-date with ProxyService.java */
- public static final String KEY_PROXY = "keyProxy";
private String mCurrentPac;
@GuardedBy("mProxyLock")
private volatile Uri mPacUrl = Uri.EMPTY;
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index aa174e3..bb46d5e 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -164,19 +164,6 @@
private boolean mLockdown = false;
/**
- * List of UIDs that are set to use this VPN by default. Normally, every UID in the user is
- * added to this set but that can be changed by adding allowed or disallowed applications. It
- * is non-null iff the VPN is connected.
- *
- * Unless the VPN has set allowBypass=true, these UIDs are forced into the VPN.
- *
- * @see VpnService.Builder#addAllowedApplication(String)
- * @see VpnService.Builder#addDisallowedApplication(String)
- */
- @GuardedBy("this")
- private Set<UidRange> mVpnUsers = null;
-
- /**
* List of UIDs for which networking should be blocked until VPN is ready, during brief periods
* when VPN is not running. For example, during system startup or after a crash.
* @see mLockdown
@@ -688,7 +675,7 @@
agentDisconnect();
jniReset(mInterface);
mInterface = null;
- mVpnUsers = null;
+ mNetworkCapabilities.setUids(null);
}
// Revoke the connection or stop LegacyVpnRunner.
@@ -857,10 +844,14 @@
NetworkMisc networkMisc = new NetworkMisc();
networkMisc.allowBypass = mConfig.allowBypass && !mLockdown;
+ mNetworkCapabilities.setEstablishingVpnAppUid(Binder.getCallingUid());
+ mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserHandle,
+ mConfig.allowedApplications, mConfig.disallowedApplications));
long token = Binder.clearCallingIdentity();
try {
mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE /* logtag */,
- mNetworkInfo, mNetworkCapabilities, lp, 0 /* score */, networkMisc) {
+ mNetworkInfo, mNetworkCapabilities, lp,
+ ConnectivityConstants.VPN_DEFAULT_SCORE, networkMisc) {
@Override
public void unwanted() {
// We are user controlled, not driven by NetworkRequest.
@@ -869,11 +860,6 @@
} finally {
Binder.restoreCallingIdentity(token);
}
-
- mVpnUsers = createUserAndRestrictedProfilesRanges(mUserHandle,
- mConfig.allowedApplications, mConfig.disallowedApplications);
- mNetworkAgent.addUidRanges(mVpnUsers.toArray(new UidRange[mVpnUsers.size()]));
-
mNetworkInfo.setIsAvailable(true);
updateState(DetailedState.CONNECTED, "agentConnect");
}
@@ -953,7 +939,7 @@
Connection oldConnection = mConnection;
NetworkAgent oldNetworkAgent = mNetworkAgent;
mNetworkAgent = null;
- Set<UidRange> oldUsers = mVpnUsers;
+ Set<UidRange> oldUsers = mNetworkCapabilities.getUids();
// Configure the interface. Abort if any of these steps fails.
ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
@@ -1011,7 +997,7 @@
// restore old state
mConfig = oldConfig;
mConnection = oldConnection;
- mVpnUsers = oldUsers;
+ mNetworkCapabilities.setUids(oldUsers);
mNetworkAgent = oldNetworkAgent;
mInterface = oldInterface;
throw e;
@@ -1131,10 +1117,12 @@
// Returns the subset of the full list of active UID ranges the VPN applies to (mVpnUsers) that
// apply to userHandle.
- private List<UidRange> uidRangesForUser(int userHandle) {
+ static private List<UidRange> uidRangesForUser(int userHandle, Set<UidRange> existingRanges) {
+ // UidRange#createForUser returns the entire range of UIDs available to a macro-user.
+ // This is something like 0-99999 ; {@see UserHandle#PER_USER_RANGE}
final UidRange userRange = UidRange.createForUser(userHandle);
final List<UidRange> ranges = new ArrayList<UidRange>();
- for (UidRange range : mVpnUsers) {
+ for (UidRange range : existingRanges) {
if (userRange.containsRange(range)) {
ranges.add(range);
}
@@ -1142,30 +1130,18 @@
return ranges;
}
- private void removeVpnUserLocked(int userHandle) {
- if (mVpnUsers == null) {
- throw new IllegalStateException("VPN is not active");
- }
- final List<UidRange> ranges = uidRangesForUser(userHandle);
- if (mNetworkAgent != null) {
- mNetworkAgent.removeUidRanges(ranges.toArray(new UidRange[ranges.size()]));
- }
- mVpnUsers.removeAll(ranges);
- }
-
public void onUserAdded(int userHandle) {
// If the user is restricted tie them to the parent user's VPN
UserInfo user = UserManager.get(mContext).getUserInfo(userHandle);
if (user.isRestricted() && user.restrictedProfileParentId == mUserHandle) {
synchronized(Vpn.this) {
- if (mVpnUsers != null) {
+ final Set<UidRange> existingRanges = mNetworkCapabilities.getUids();
+ if (existingRanges != null) {
try {
- addUserToRanges(mVpnUsers, userHandle, mConfig.allowedApplications,
+ addUserToRanges(existingRanges, userHandle, mConfig.allowedApplications,
mConfig.disallowedApplications);
- if (mNetworkAgent != null) {
- final List<UidRange> ranges = uidRangesForUser(userHandle);
- mNetworkAgent.addUidRanges(ranges.toArray(new UidRange[ranges.size()]));
- }
+ mNetworkCapabilities.setUids(existingRanges);
+ updateCapabilities();
} catch (Exception e) {
Log.wtf(TAG, "Failed to add restricted user to owner", e);
}
@@ -1180,9 +1156,14 @@
UserInfo user = UserManager.get(mContext).getUserInfo(userHandle);
if (user.isRestricted() && user.restrictedProfileParentId == mUserHandle) {
synchronized(Vpn.this) {
- if (mVpnUsers != null) {
+ final Set<UidRange> existingRanges = mNetworkCapabilities.getUids();
+ if (existingRanges != null) {
try {
- removeVpnUserLocked(userHandle);
+ final List<UidRange> removedRanges =
+ uidRangesForUser(userHandle, existingRanges);
+ existingRanges.removeAll(removedRanges);
+ mNetworkCapabilities.setUids(existingRanges);
+ updateCapabilities();
} catch (Exception e) {
Log.wtf(TAG, "Failed to remove restricted user to owner", e);
}
@@ -1226,15 +1207,6 @@
private void setVpnForcedLocked(boolean enforce) {
final List<String> exemptedPackages =
isNullOrLegacyVpn(mPackage) ? null : Collections.singletonList(mPackage);
- setVpnForcedWithExemptionsLocked(enforce, exemptedPackages);
- }
-
- /**
- * @see #setVpnForcedLocked
- */
- @GuardedBy("this")
- private void setVpnForcedWithExemptionsLocked(boolean enforce,
- @Nullable List<String> exemptedPackages) {
final Set<UidRange> removedRanges = new ArraySet<>(mBlockedUsers);
Set<UidRange> addedRanges = Collections.emptySet();
@@ -1314,7 +1286,7 @@
synchronized (Vpn.this) {
if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
mStatusIntent = null;
- mVpnUsers = null;
+ mNetworkCapabilities.setUids(null);
mConfig = null;
mInterface = null;
if (mConnection != null) {
@@ -1433,12 +1405,7 @@
if (!isRunningLocked()) {
return false;
}
- for (UidRange uidRange : mVpnUsers) {
- if (uidRange.contains(uid)) {
- return true;
- }
- }
- return false;
+ return mNetworkCapabilities.appliesToUid(uid);
}
/**
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
index acbc10b..09bce7f 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -28,6 +28,8 @@
import android.telephony.TelephonyManager;
import android.net.util.SharedLog;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -49,6 +51,7 @@
public class TetheringConfiguration {
private static final String TAG = TetheringConfiguration.class.getSimpleName();
+ @VisibleForTesting
public static final int DUN_NOT_REQUIRED = 0;
public static final int DUN_REQUIRED = 1;
public static final int DUN_UNSPECIFIED = 2;
diff --git a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
index 370e569..d30b13c 100644
--- a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
+++ b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
@@ -16,18 +16,22 @@
package com.android.server.fingerprint;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
import android.content.Context;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintDialog;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.IFingerprintDialogReceiver;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.statusbar.IStatusBarService;
+
/**
* A class to keep track of the authentication state for a given client.
*/
@@ -41,11 +45,99 @@
public static final int LOCKOUT_TIMED = 1;
public static final int LOCKOUT_PERMANENT = 2;
+ // Callback mechanism received from the client
+ // (FingerprintDialog -> FingerprintManager -> FingerprintService -> AuthenticationClient)
+ private IFingerprintDialogReceiver mDialogReceiverFromClient;
+ private Bundle mBundle;
+ private IStatusBarService mStatusBarService;
+ private boolean mInLockout;
+ private final FingerprintManager mFingerprintManager;
+ protected boolean mDialogDismissed;
+
+ // Receives events from SystemUI
+ protected IFingerprintDialogReceiver mDialogReceiver = new IFingerprintDialogReceiver.Stub() {
+ @Override // binder call
+ public void onDialogDismissed(int reason) {
+ if (mBundle != null && mDialogReceiverFromClient != null) {
+ try {
+ mDialogReceiverFromClient.onDialogDismissed(reason);
+ if (reason == FingerprintDialog.DISMISSED_REASON_USER_CANCEL) {
+ onError(FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED,
+ 0 /* vendorCode */);
+ }
+ mDialogDismissed = true;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify dialog dismissed", e);
+ }
+ stop(true /* initiatedByClient */);
+ }
+ }
+ };
+
public AuthenticationClient(Context context, long halDeviceId, IBinder token,
IFingerprintServiceReceiver receiver, int targetUserId, int groupId, long opId,
- boolean restricted, String owner) {
+ boolean restricted, String owner, Bundle bundle,
+ IFingerprintDialogReceiver dialogReceiver, IStatusBarService statusBarService) {
super(context, halDeviceId, token, receiver, targetUserId, groupId, restricted, owner);
mOpId = opId;
+ mBundle = bundle;
+ mDialogReceiverFromClient = dialogReceiver;
+ mStatusBarService = statusBarService;
+ mFingerprintManager = (FingerprintManager) getContext()
+ .getSystemService(Context.FINGERPRINT_SERVICE);
+ }
+
+ @Override
+ public void binderDied() {
+ super.binderDied();
+ // When the binder dies, we should stop the client. This probably belongs in
+ // ClientMonitor's binderDied(), but testing all the cases would be tricky.
+ // AuthenticationClient is the most user-visible case.
+ stop(false /* initiatedByClient */);
+ }
+
+ @Override
+ public boolean onAcquired(int acquiredInfo, int vendorCode) {
+ // If the dialog is showing, the client doesn't need to receive onAcquired messages.
+ if (mBundle != null) {
+ try {
+ if (acquiredInfo != FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) {
+ mStatusBarService.onFingerprintHelp(
+ mFingerprintManager.getAcquiredString(acquiredInfo, vendorCode));
+ }
+ return false; // acquisition continues
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when sending acquired message", e);
+ return true; // client failed
+ } finally {
+ // Good scans will keep the device awake
+ if (acquiredInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) {
+ notifyUserActivity();
+ }
+ }
+ } else {
+ return super.onAcquired(acquiredInfo, vendorCode);
+ }
+ }
+
+ @Override
+ public boolean onError(int error, int vendorCode) {
+ if (mDialogDismissed) {
+ // If user cancels authentication, the application has already received the
+ // FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED message from onDialogDismissed()
+ // and stopped the fingerprint hardware, so there is no need to send a
+ // FingerprintManager.FINGERPRINT_ERROR_CANCELED message.
+ return true;
+ }
+ if (mBundle != null) {
+ try {
+ mStatusBarService.onFingerprintError(
+ mFingerprintManager.getErrorString(error, vendorCode));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when sending error", e);
+ }
+ }
+ return super.onError(error, vendorCode);
}
@Override
@@ -53,6 +145,20 @@
boolean result = false;
boolean authenticated = fingerId != 0;
+ // If the fingerprint dialog is showing, notify authentication succeeded
+ if (mBundle != null) {
+ try {
+ if (authenticated) {
+ mStatusBarService.onFingerprintAuthenticated();
+ } else {
+ mStatusBarService.onFingerprintHelp(getContext().getResources().getString(
+ com.android.internal.R.string.fingerprint_not_recognized));
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to notify Authenticated:", e);
+ }
+ }
+
IFingerprintServiceReceiver receiver = getReceiver();
if (receiver != null) {
try {
@@ -85,13 +191,24 @@
int lockoutMode = handleFailedAttempt();
if (lockoutMode != LOCKOUT_NONE) {
try {
+ mInLockout = true;
Slog.w(TAG, "Forcing lockout (fp driver code should do this!), mode(" +
lockoutMode + ")");
stop(false);
int errorCode = lockoutMode == LOCKOUT_TIMED ?
FingerprintManager.FINGERPRINT_ERROR_LOCKOUT :
FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+
+ // TODO: if the dialog is showing, this error should be delayed. On a similar
+ // note, AuthenticationClient should override onError and delay all other errors
+ // as well, if the dialog is showing
receiver.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
+
+ // Send the lockout message to the system dialog
+ if (mBundle != null) {
+ mStatusBarService.onFingerprintError(
+ mFingerprintManager.getErrorString(errorCode, 0 /* vendorCode */));
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Failed to notify lockout:", e);
}
@@ -126,6 +243,15 @@
return result;
}
if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is authenticating...");
+
+ // If authenticating with system dialog, show the dialog
+ if (mBundle != null) {
+ try {
+ mStatusBarService.showFingerprintDialog(mBundle, mDialogReceiver);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to show fingerprint dialog", e);
+ }
+ }
} catch (RemoteException e) {
Slog.e(TAG, "startAuthentication failed", e);
return ERROR_ESRCH;
@@ -139,6 +265,7 @@
Slog.w(TAG, "stopAuthentication: already cancelled!");
return 0;
}
+
IBiometricsFingerprint daemon = getFingerprintDaemon();
if (daemon == null) {
Slog.w(TAG, "stopAuthentication: no fingerprint HAL!");
@@ -154,6 +281,18 @@
} catch (RemoteException e) {
Slog.e(TAG, "stopAuthentication failed", e);
return ERROR_ESRCH;
+ } finally {
+ // If the user already cancelled authentication (via some interaction with the
+ // dialog, we do not need to hide it since it's already hidden.
+ // If the device is in lockout, don't hide the dialog - it will automatically hide
+ // after FingerprintDialog.HIDE_DIALOG_DELAY
+ if (mBundle != null && !mDialogDismissed && !mInLockout) {
+ try {
+ mStatusBarService.hideFingerprintDialog();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to hide fingerprint dialog", e);
+ }
+ }
}
mAlreadyCancelled = true;
return 0; // success
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index d0d951b..b5f94b1 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -40,10 +40,12 @@
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IFingerprintClientActiveCallback;
+import android.hardware.fingerprint.IFingerprintDialogReceiver;
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.fingerprint.IFingerprintServiceLockoutResetCallback;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Environment;
@@ -55,7 +57,9 @@
import android.os.PowerManager.WakeLock;
import android.os.RemoteException;
import android.os.SELinux;
+import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.security.KeyStore;
@@ -66,6 +70,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
@@ -131,6 +136,7 @@
private SparseIntArray mFailedAttempts;
@GuardedBy("this")
private IBiometricsFingerprint mDaemon;
+ private IStatusBarService mStatusBarService;
private final PowerManager mPowerManager;
private final AlarmManager mAlarmManager;
private final UserManager mUserManager;
@@ -222,6 +228,8 @@
mUserManager = UserManager.get(mContext);
mTimedLockoutCleared = new SparseBooleanArray();
mFailedAttempts = new SparseIntArray();
+ mStatusBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
}
@Override
@@ -808,13 +816,14 @@
private void startAuthentication(IBinder token, long opId, int callingUserId, int groupId,
IFingerprintServiceReceiver receiver, int flags, boolean restricted,
- String opPackageName) {
+ String opPackageName, Bundle bundle, IFingerprintDialogReceiver dialogReceiver) {
updateActiveGroup(groupId, opPackageName);
if (DEBUG) Slog.v(TAG, "startAuthentication(" + opPackageName + ")");
AuthenticationClient client = new AuthenticationClient(getContext(), mHalDeviceId, token,
- receiver, mCurrentUserId, groupId, opId, restricted, opPackageName) {
+ receiver, mCurrentUserId, groupId, opId, restricted, opPackageName, bundle,
+ dialogReceiver, mStatusBarService) {
@Override
public int handleFailedAttempt() {
final int currentUser = ActivityManager.getCurrentUser();
@@ -1037,7 +1046,7 @@
final IFingerprintServiceReceiver receiver, final int flags,
final String opPackageName) {
checkPermission(MANAGE_FINGERPRINT);
- final int limit = mContext.getResources().getInteger(
+ final int limit = mContext.getResources().getInteger(
com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
final int enrolled = FingerprintService.this.getEnrolledFingerprints(userId).size();
@@ -1085,7 +1094,8 @@
@Override // Binder call
public void authenticate(final IBinder token, final long opId, final int groupId,
final IFingerprintServiceReceiver receiver, final int flags,
- final String opPackageName) {
+ final String opPackageName, final Bundle bundle,
+ final IFingerprintDialogReceiver dialogReceiver) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
@@ -1113,7 +1123,7 @@
mPerformanceStats = stats;
startAuthentication(token, opId, callingUserId, groupId, receiver,
- flags, restricted, opPackageName);
+ flags, restricted, opPackageName, bundle, dialogReceiver);
}
});
}
@@ -1411,8 +1421,17 @@
try {
userId = getUserOrWorkProfileId(clientPackage, userId);
if (userId != mCurrentUserId) {
- final File systemDir = Environment.getUserSystemDirectory(userId);
- final File fpDir = new File(systemDir, FP_DATA_DIR);
+ File baseDir;
+ if (Build.VERSION.FIRST_SDK_INT <= Build.VERSION_CODES.O_MR1
+ && !SystemProperties.getBoolean(
+ "ro.treble.supports_vendor_data", false)) {
+ // TODO(b/72405644) remove the override when possible.
+ baseDir = Environment.getUserSystemDirectory(userId);
+ } else {
+ baseDir = Environment.getDataVendorDeDirectory(userId);
+ }
+
+ File fpDir = new File(baseDir, FP_DATA_DIR);
if (!fpDir.exists()) {
if (!fpDir.mkdir()) {
Slog.v(TAG, "Cannot make directory: " + fpDir.getAbsolutePath());
@@ -1426,6 +1445,7 @@
return;
}
}
+
daemon.setActiveGroup(userId, fpDir.getAbsolutePath());
mCurrentUserId = userId;
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index fa5fdf5..5da470e 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -21,6 +21,7 @@
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.IUidObserver;
import android.app.job.IJobScheduler;
@@ -73,8 +74,10 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
import com.android.server.DeviceIdleController;
import com.android.server.FgThread;
+import com.android.server.ForceAppStandbyTracker;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
@@ -102,6 +105,7 @@
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
+import java.util.function.Predicate;
/**
* Responsible for taking jobs representing work to be performed by a client app, and determining
@@ -175,8 +179,10 @@
final JobSchedulerStub mJobSchedulerStub;
PackageManagerInternal mLocalPM;
+ ActivityManagerInternal mActivityManagerInternal;
IBatteryStats mBatteryStats;
DeviceIdleController.LocalService mLocalDeviceIdleController;
+ final ForceAppStandbyTracker mForceAppStandbyTracker;
/**
* Set to true once we are allowed to run third party apps.
@@ -778,6 +784,22 @@
mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
}
+ /**
+ * Return whether an UID is in the foreground or not.
+ */
+ private boolean isUidInForeground(int uid) {
+ synchronized (mLock) {
+ if (mUidPriorityOverride.get(uid, 0) > 0) {
+ return true;
+ }
+ }
+ // Note UID observer may not be called in time, so we always check with the AM.
+ return mActivityManagerInternal.getUidProcessState(uid)
+ <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ }
+
+ private final Predicate<Integer> mIsUidInForegroundPredicate = this::isUidInForeground;
+
public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
int userId, String tag) {
try {
@@ -797,12 +819,25 @@
// Fast path: we are adding work to an existing job, and the JobInfo is not
// changing. We can just directly enqueue this work in to the job.
if (toCancel.getJob().equals(job)) {
+
toCancel.enqueueWorkLocked(ActivityManager.getService(), work);
+
+ // If any of work item is enqueued when the source is in the foreground,
+ // exempt the entire job.
+ toCancel.maybeAddForegroundExemption(mIsUidInForegroundPredicate);
+
return JobScheduler.RESULT_SUCCESS;
}
}
JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
+
+ // Give exemption if the source is in the foreground just now.
+ // Note if it's a sync job, this method is called on the handler so it's not exactly
+ // the state when requestSync() was called, but that should be fine because of the
+ // 1 minute foreground grace period.
+ jobStatus.maybeAddForegroundExemption(mIsUidInForegroundPredicate);
+
if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
// Jobs on behalf of others don't apply to the per-app job cap
if (ENFORCE_MAX_JOBS && packageName == null) {
@@ -1047,6 +1082,8 @@
super(context);
mLocalPM = LocalServices.getService(PackageManagerInternal.class);
+ mActivityManagerInternal = Preconditions.checkNotNull(
+ LocalServices.getService(ActivityManagerInternal.class));
mHandler = new JobHandler(context.getMainLooper());
mConstants = new Constants(mHandler);
@@ -1078,6 +1115,8 @@
mDeviceIdleJobsController = DeviceIdleJobsController.get(this);
mControllers.add(mDeviceIdleJobsController);
+ mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
+
// If the job store determined that it can't yet reschedule persisted jobs,
// we need to start watching the clock.
if (!mJobs.jobTimesInflatedValid()) {
@@ -1137,6 +1176,9 @@
public void onBootPhase(int phase) {
if (PHASE_SYSTEM_SERVICES_READY == phase) {
mConstants.start(getContext().getContentResolver());
+
+ mForceAppStandbyTracker.start();
+
// Register br for package removals and user removals.
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 36cacd7a..6da783c 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -72,6 +72,9 @@
* This is important b/c {@link com.android.server.job.JobStore.WriteJobsMapToDiskRunnable}
* and {@link com.android.server.job.JobStore.ReadJobMapFromDiskRunnable} lock on that
* object.
+ *
+ * Test:
+ * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
*/
public final class JobStore {
private static final String TAG = "JobStore";
@@ -427,6 +430,9 @@
out.attribute(null, "uid", Integer.toString(jobStatus.getUid()));
out.attribute(null, "priority", String.valueOf(jobStatus.getPriority()));
out.attribute(null, "flags", String.valueOf(jobStatus.getFlags()));
+ if (jobStatus.getInternalFlags() != 0) {
+ out.attribute(null, "internalFlags", String.valueOf(jobStatus.getInternalFlags()));
+ }
out.attribute(null, "lastSuccessfulRunTime",
String.valueOf(jobStatus.getLastSuccessfulRunTime()));
@@ -689,6 +695,7 @@
int uid, sourceUserId;
long lastSuccessfulRunTime;
long lastFailedRunTime;
+ int internalFlags = 0;
// Read out job identifier attributes and priority.
try {
@@ -704,6 +711,10 @@
if (val != null) {
jobBuilder.setFlags(Integer.parseInt(val));
}
+ val = parser.getAttributeValue(null, "internalFlags");
+ if (val != null) {
+ internalFlags = Integer.parseInt(val);
+ }
val = parser.getAttributeValue(null, "sourceUserId");
sourceUserId = val == null ? -1 : Integer.parseInt(val);
@@ -718,7 +729,6 @@
}
String sourcePackageName = parser.getAttributeValue(null, "sourcePackageName");
-
final String sourceTag = parser.getAttributeValue(null, "sourceTag");
int eventType;
@@ -857,7 +867,7 @@
appBucket, currentHeartbeat, sourceTag,
elapsedRuntimes.first, elapsedRuntimes.second,
lastSuccessfulRunTime, lastFailedRunTime,
- (rtcIsGood) ? null : rtcRuntimes);
+ (rtcIsGood) ? null : rtcRuntimes, internalFlags);
return js;
}
diff --git a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
index 1d053a5..2e4567a 100644
--- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -197,7 +197,9 @@
final int uid = jobStatus.getSourceUid();
final String packageName = jobStatus.getSourcePackageName();
- final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName);
+ final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName,
+ (jobStatus.getInternalFlags() & JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION)
+ != 0);
return jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
}
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 0f5cb0a..d9a5ff6 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -46,6 +46,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.function.Predicate;
/**
* Uniquely identifies a job internally.
@@ -184,6 +185,21 @@
*/
private int trackingControllers;
+ /**
+ * Flag for {@link #mInternalFlags}: this job was scheduled when the app that owns the job
+ * service (not necessarily the caller) was in the foreground and the job has no time
+ * constraints, which makes it exempted from the battery saver job restriction.
+ *
+ * @hide
+ */
+ public static final int INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION = 1 << 0;
+
+ /**
+ * Versatile, persistable flags for a job that's updated within the system server,
+ * as opposed to {@link JobInfo#flags} that's set by callers.
+ */
+ private int mInternalFlags;
+
// These are filled in by controllers when preparing for execution.
public ArraySet<Uri> changedUris;
public ArraySet<String> changedAuthorities;
@@ -248,7 +264,7 @@
private JobStatus(JobInfo job, int callingUid, int targetSdkVersion, String sourcePackageName,
int sourceUserId, int standbyBucket, long heartbeat, String tag, int numFailures,
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
- long lastSuccessfulRunTime, long lastFailedRunTime) {
+ long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags) {
this.job = job;
this.callingUid = callingUid;
this.targetSdkVersion = targetSdkVersion;
@@ -304,6 +320,8 @@
mLastSuccessfulRunTime = lastSuccessfulRunTime;
mLastFailedRunTime = lastFailedRunTime;
+ mInternalFlags = internalFlags;
+
updateEstimatedNetworkBytesLocked();
}
@@ -315,7 +333,8 @@
jobStatus.getStandbyBucket(), jobStatus.getBaseHeartbeat(),
jobStatus.getSourceTag(), jobStatus.getNumFailures(),
jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
- jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime());
+ jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
+ jobStatus.getInternalFlags());
mPersistedUtcTimes = jobStatus.mPersistedUtcTimes;
if (jobStatus.mPersistedUtcTimes != null) {
if (DEBUG) {
@@ -336,12 +355,13 @@
int standbyBucket, long baseHeartbeat, String sourceTag,
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
long lastSuccessfulRunTime, long lastFailedRunTime,
- Pair<Long, Long> persistedExecutionTimesUTC) {
+ Pair<Long, Long> persistedExecutionTimesUTC,
+ int innerFlags) {
this(job, callingUid, resolveTargetSdkVersion(job), sourcePkgName, sourceUserId,
standbyBucket, baseHeartbeat,
sourceTag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
- lastSuccessfulRunTime, lastFailedRunTime);
+ lastSuccessfulRunTime, lastFailedRunTime, innerFlags);
// Only during initial inflation do we record the UTC-timebase execution bounds
// read from the persistent store. If we ever have to recreate the JobStatus on
@@ -365,7 +385,7 @@
rescheduling.getStandbyBucket(), newBaseHeartbeat,
rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis,
newLatestRuntimeElapsedMillis,
- lastSuccessfulRunTime, lastFailedRunTime);
+ lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags());
}
/**
@@ -395,10 +415,12 @@
sourceUserId, elapsedNow);
JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
long currentHeartbeat = js != null ? js.currentHeartbeat() : 0;
+
return new JobStatus(job, callingUid, resolveTargetSdkVersion(job), sourcePkg, sourceUserId,
standbyBucket, currentHeartbeat, tag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
- 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */);
+ 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
+ /*innerFlags=*/ 0);
}
public void enqueueWorkLocked(IActivityManager am, JobWorkItem work) {
@@ -623,6 +645,28 @@
return job.getFlags();
}
+ public int getInternalFlags() {
+ return mInternalFlags;
+ }
+
+ public void addInternalFlags(int flags) {
+ mInternalFlags |= flags;
+ }
+
+ public void maybeAddForegroundExemption(Predicate<Integer> uidForegroundChecker) {
+ // Jobs with time constraints shouldn't be exempted.
+ if (job.hasEarlyConstraint() || job.hasLateConstraint()) {
+ return;
+ }
+ // Already exempted, skip the foreground check.
+ if ((mInternalFlags & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0) {
+ return;
+ }
+ if (uidForegroundChecker.test(getSourceUid())) {
+ addInternalFlags(INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION);
+ }
+ }
+
private void updateEstimatedNetworkBytesLocked() {
totalNetworkBytes = computeEstimatedNetworkBytesLocked();
}
@@ -1054,6 +1098,14 @@
if ((constraints&CONSTRAINT_DEVICE_NOT_DOZING) != 0) {
pw.print(" DEVICE_NOT_DOZING");
}
+ if ((constraints&CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
+ pw.print(" BACKGROUND_NOT_RESTRICTED");
+ }
+ if (constraints != 0) {
+ pw.print(" [0x");
+ pw.print(Integer.toHexString(constraints));
+ pw.print("]");
+ }
}
/** Writes constraints to the given repeating proto field. */
@@ -1162,6 +1214,15 @@
pw.print(prefix); pw.print(" Flags: ");
pw.println(Integer.toHexString(job.getFlags()));
}
+ if (getInternalFlags() != 0) {
+ pw.print(prefix); pw.print(" Internal flags: ");
+ pw.print(Integer.toHexString(getInternalFlags()));
+
+ if ((getInternalFlags()&INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0) {
+ pw.print(" HAS_FOREGROUND_EXEMPTION");
+ }
+ pw.println();
+ }
pw.print(prefix); pw.print(" Requires: charging=");
pw.print(job.isRequireCharging()); pw.print(" batteryNotLow=");
pw.print(job.isRequireBatteryNotLow()); pw.print(" deviceIdle=");
@@ -1317,6 +1378,7 @@
proto.write(JobStatusDumpProto.SOURCE_UID, getSourceUid());
proto.write(JobStatusDumpProto.SOURCE_USER_ID, getSourceUserId());
proto.write(JobStatusDumpProto.SOURCE_PACKAGE_NAME, getSourcePackageName());
+ proto.write(JobStatusDumpProto.INTERNAL_FLAGS, getInternalFlags());
if (full) {
final long jiToken = proto.start(JobStatusDumpProto.JOB_INFO);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index a6753ab..31c20cb 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -79,9 +79,9 @@
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.security.keystore.UserNotAuthenticatedException;
-import android.security.keystore.recovery.KeychainProtectionParams;
+import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.WrappedApplicationKey;
-import android.security.keystore.recovery.KeychainSnapshot;
+import android.security.keystore.recovery.KeyChainSnapshot;
import android.service.gatekeeper.GateKeeperResponse;
import android.service.gatekeeper.IGateKeeperService;
import android.text.TextUtils;
@@ -1598,8 +1598,10 @@
userId, progressCallback);
// The user employs synthetic password based credential.
if (response != null) {
- mRecoverableKeyStoreManager.lockScreenSecretAvailable(credentialType, credential,
- userId);
+ if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+ mRecoverableKeyStoreManager.lockScreenSecretAvailable(credentialType, credential,
+ userId);
+ }
return response;
}
@@ -1980,7 +1982,7 @@
}
@Override
- public KeychainSnapshot getRecoveryData(@NonNull byte[] account) throws RemoteException {
+ public KeyChainSnapshot getRecoveryData(@NonNull byte[] account) throws RemoteException {
return mRecoverableKeyStoreManager.getRecoveryData(account);
}
@@ -2009,7 +2011,7 @@
}
@Override
- public void setRecoverySecretTypes(@NonNull @KeychainProtectionParams.UserSecretType
+ public void setRecoverySecretTypes(@NonNull @KeyChainProtectionParams.UserSecretType
int[] secretTypes) throws RemoteException {
mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes);
}
@@ -2026,7 +2028,7 @@
}
@Override
- public void recoverySecretAvailable(@NonNull KeychainProtectionParams recoverySecret)
+ public void recoverySecretAvailable(@NonNull KeyChainProtectionParams recoverySecret)
throws RemoteException {
mRecoverableKeyStoreManager.recoverySecretAvailable(recoverySecret);
}
@@ -2034,7 +2036,7 @@
@Override
public byte[] startRecoverySession(@NonNull String sessionId,
@NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams,
- @NonNull byte[] vaultChallenge, @NonNull List<KeychainProtectionParams> secrets)
+ @NonNull byte[] vaultChallenge, @NonNull List<KeyChainProtectionParams> secrets)
throws RemoteException {
return mRecoverableKeyStoreManager.startRecoverySession(sessionId, verifierPublicKey,
vaultParams, vaultChallenge, secrets);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 70d6072..f62e8a9 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -627,7 +627,12 @@
if (persistentDataBlock == null) {
return PersistentData.NONE;
}
- return PersistentData.fromBytes(persistentDataBlock.getFrpCredentialHandle());
+ try {
+ return PersistentData.fromBytes(persistentDataBlock.getFrpCredentialHandle());
+ } catch (IllegalStateException e) {
+ Slog.e(TAG, "Error reading persistent data block", e);
+ return PersistentData.NONE;
+ }
}
public static class PersistentData {
@@ -670,11 +675,11 @@
return new PersistentData(type, userId, qualityForUi, payload);
} else {
Slog.wtf(TAG, "Unknown PersistentData version code: " + version);
- return null;
+ return NONE;
}
} catch (IOException e) {
Slog.wtf(TAG, "Could not parse PersistentData", e);
- return null;
+ return NONE;
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index 6fcbcbc..e1e769c 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -16,13 +16,13 @@
package com.android.server.locksettings.recoverablekeystore;
-import static android.security.keystore.recovery.KeychainProtectionParams.TYPE_LOCKSCREEN;
+import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_LOCKSCREEN;
import android.annotation.Nullable;
import android.content.Context;
import android.security.keystore.recovery.KeyDerivationParams;
-import android.security.keystore.recovery.KeychainProtectionParams;
-import android.security.keystore.recovery.KeychainSnapshot;
+import android.security.keystore.recovery.KeyChainProtectionParams;
+import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.util.Log;
@@ -255,12 +255,12 @@
}
// TODO: store raw data in RecoveryServiceMetadataEntry and generate Parcelables later
// TODO: use Builder.
- KeychainProtectionParams metadata = new KeychainProtectionParams(
+ KeyChainProtectionParams metadata = new KeyChainProtectionParams(
/*userSecretType=*/ TYPE_LOCKSCREEN,
/*lockScreenUiFormat=*/ getUiFormat(mCredentialType, mCredential),
/*keyDerivationParams=*/ KeyDerivationParams.createSha256Params(salt),
/*secret=*/ new byte[0]);
- ArrayList<KeychainProtectionParams> metadataList = new ArrayList<>();
+ ArrayList<KeyChainProtectionParams> metadataList = new ArrayList<>();
metadataList.add(metadata);
int snapshotVersion = incrementSnapshotVersion(recoveryAgentUid);
@@ -268,13 +268,13 @@
// If application keys are not updated, snapshot will not be created on next unlock.
mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, false);
- mRecoverySnapshotStorage.put(recoveryAgentUid, new KeychainSnapshot.Builder()
+ mRecoverySnapshotStorage.put(recoveryAgentUid, new KeyChainSnapshot.Builder()
.setSnapshotVersion(snapshotVersion)
.setMaxAttempts(TRUSTED_HARDWARE_MAX_ATTEMPTS)
.setCounterId(counterId)
.setTrustedHardwarePublicKey(SecureBox.encodePublicKey(publicKey))
.setServerParams(vaultHandle)
- .setKeychainProtectionParams(metadataList)
+ .setKeyChainProtectionParams(metadataList)
.setWrappedApplicationKeys(createApplicationKeyEntries(encryptedApplicationKeys))
.setEncryptedRecoveryKeyBlob(encryptedRecoveryKey)
.build());
@@ -317,7 +317,7 @@
*/
private boolean shoudCreateSnapshot(int recoveryAgentUid) {
int[] types = mRecoverableKeyStoreDb.getRecoverySecretTypes(mUserId, recoveryAgentUid);
- if (!ArrayUtils.contains(types, KeychainProtectionParams.TYPE_LOCKSCREEN)) {
+ if (!ArrayUtils.contains(types, KeyChainProtectionParams.TYPE_LOCKSCREEN)) {
// Only lockscreen type is supported.
// We will need to pass extra argument to KeySyncTask to support custom pass phrase.
return false;
@@ -340,14 +340,14 @@
* @return The format - either pattern, pin, or password.
*/
@VisibleForTesting
- @KeychainProtectionParams.LockScreenUiFormat static int getUiFormat(
+ @KeyChainProtectionParams.LockScreenUiFormat static int getUiFormat(
int credentialType, String credential) {
if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
- return KeychainProtectionParams.TYPE_PATTERN;
+ return KeyChainProtectionParams.UI_FORMAT_PATTERN;
} else if (isPin(credential)) {
- return KeychainProtectionParams.TYPE_PIN;
+ return KeyChainProtectionParams.UI_FORMAT_PIN;
} else {
- return KeychainProtectionParams.TYPE_PASSWORD;
+ return KeyChainProtectionParams.UI_FORMAT_PASSWORD;
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index ac3cef2..b6c3c66 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -33,8 +33,8 @@
import android.os.ServiceSpecificException;
import android.os.UserHandle;
-import android.security.keystore.recovery.KeychainProtectionParams;
-import android.security.keystore.recovery.KeychainSnapshot;
+import android.security.keystore.recovery.KeyChainProtectionParams;
+import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.RecoveryController;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.util.Log;
@@ -170,11 +170,11 @@
* @hide
*/
public @NonNull
- KeychainSnapshot getRecoveryData(@NonNull byte[] account)
+ KeyChainSnapshot getRecoveryData(@NonNull byte[] account)
throws RemoteException {
checkRecoverKeyStorePermission();
int uid = Binder.getCallingUid();
- KeychainSnapshot snapshot = mSnapshotStorage.get(uid);
+ KeyChainSnapshot snapshot = mSnapshotStorage.get(uid);
if (snapshot == null) {
throw new ServiceSpecificException(ERROR_NO_SNAPSHOT_PENDING);
}
@@ -256,7 +256,7 @@
* @hide
*/
public void setRecoverySecretTypes(
- @NonNull @KeychainProtectionParams.UserSecretType int[] secretTypes)
+ @NonNull @KeyChainProtectionParams.UserSecretType int[] secretTypes)
throws RemoteException {
checkRecoverKeyStorePermission();
int userId = UserHandle.getCallingUserId();
@@ -291,9 +291,9 @@
}
public void recoverySecretAvailable(
- @NonNull KeychainProtectionParams recoverySecret) throws RemoteException {
+ @NonNull KeyChainProtectionParams recoverySecret) throws RemoteException {
int uid = Binder.getCallingUid();
- if (recoverySecret.getLockScreenUiFormat() == KeychainProtectionParams.TYPE_LOCKSCREEN) {
+ if (recoverySecret.getLockScreenUiFormat() == KeyChainProtectionParams.TYPE_LOCKSCREEN) {
throw new SecurityException(
"Caller " + uid + " is not allowed to set lock screen secret");
}
@@ -319,14 +319,14 @@
@NonNull byte[] verifierPublicKey,
@NonNull byte[] vaultParams,
@NonNull byte[] vaultChallenge,
- @NonNull List<KeychainProtectionParams> secrets)
+ @NonNull List<KeyChainProtectionParams> secrets)
throws RemoteException {
checkRecoverKeyStorePermission();
int uid = Binder.getCallingUid();
if (secrets.size() != 1) {
throw new UnsupportedOperationException(
- "Only a single KeychainProtectionParams is supported");
+ "Only a single KeyChainProtectionParams is supported");
}
PublicKey publicKey;
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
index 2b1416e..f2e71b3 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -404,7 +404,7 @@
/**
* Updates the list of user secret types used for end-to-end encryption.
* If no secret types are set, recovery snapshot will not be created.
- * See {@code KeychainProtectionParams}
+ * See {@code KeyChainProtectionParams}
*
* @param userId The userId of the profile the application is running under.
* @param uid The uid of the application.
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
index 7cde7ab..3f93cc6 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
@@ -17,7 +17,7 @@
package com.android.server.locksettings.recoverablekeystore.storage;
import android.annotation.Nullable;
-import android.security.keystore.recovery.KeychainSnapshot;
+import android.security.keystore.recovery.KeyChainSnapshot;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -34,12 +34,12 @@
*/
public class RecoverySnapshotStorage {
@GuardedBy("this")
- private final SparseArray<KeychainSnapshot> mSnapshotByUid = new SparseArray<>();
+ private final SparseArray<KeyChainSnapshot> mSnapshotByUid = new SparseArray<>();
/**
* Sets the latest {@code snapshot} for the recovery agent {@code uid}.
*/
- public synchronized void put(int uid, KeychainSnapshot snapshot) {
+ public synchronized void put(int uid, KeyChainSnapshot snapshot) {
mSnapshotByUid.put(uid, snapshot);
}
@@ -47,7 +47,7 @@
* Returns the latest snapshot for the recovery agent {@code uid}, or null if none exists.
*/
@Nullable
- public synchronized KeychainSnapshot get(int uid) {
+ public synchronized KeyChainSnapshot get(int uid) {
return mSnapshotByUid.get(uid);
}
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index b25eaa7..c59c5f6 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -81,7 +81,7 @@
public void onSessionDestroyed() {
if (mController != null) {
mControllerCallback.destroy();
- mController.release();
+ mController.close();
mController = null;
}
mSessionPid = 0;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 6812778..c9c7d04 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -38,6 +38,7 @@
import android.media.IAudioService;
import android.media.IMediaSession2;
import android.media.IRemoteVolumeController;
+import android.media.MediaLibraryService2;
import android.media.MediaSessionService2;
import android.media.SessionToken;
import android.media.session.IActiveSessionsListener;
@@ -445,9 +446,18 @@
}
// TODO(jaewan): Query per users.
- List<ResolveInfo> services = getContext().getPackageManager().queryIntentServices(
- new Intent(MediaSessionService2.SERVICE_INTERFACE),
- PackageManager.GET_META_DATA);
+ List<ResolveInfo> services = new ArrayList<>();
+ // If multiple actions are declared for a service, browser gets higher priority.
+ List<ResolveInfo> libraryServices = getContext().getPackageManager().queryIntentServices(
+ new Intent(MediaLibraryService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
+ if (libraryServices != null) {
+ services.addAll(libraryServices);
+ }
+ List<ResolveInfo> sessionServices = getContext().getPackageManager().queryIntentServices(
+ new Intent(MediaSessionService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
+ if (sessionServices != null) {
+ services.addAll(sessionServices);
+ }
synchronized (mLock) {
mSessions.clear();
if (services == null) {
@@ -470,10 +480,11 @@
+ " the same ID=" + id + ". Ignoring "
+ serviceInfo.packageName + "/" + serviceInfo.name);
} else {
+ int type = (libraryServices.contains(services.get(i)))
+ ? SessionToken.TYPE_LIBRARY_SERVICE : SessionToken.TYPE_SESSION_SERVICE;
MediaSessionService2Record record =
new MediaSessionService2Record(getContext(), mSessionDestroyedListener,
- SessionToken.TYPE_SESSION_SERVICE,
- serviceInfo.packageName, serviceInfo.name, id);
+ type, serviceInfo.packageName, serviceInfo.name, id);
mSessions.add(record);
}
}
diff --git a/services/core/java/com/android/server/media/MediaUpdateService.java b/services/core/java/com/android/server/media/MediaUpdateService.java
index 016d062..6921ccd 100644
--- a/services/core/java/com/android/server/media/MediaUpdateService.java
+++ b/services/core/java/com/android/server/media/MediaUpdateService.java
@@ -53,13 +53,10 @@
@Override
public void onStart() {
- // TODO: Uncomment below once sepolicy change is landed.
- /*
if ("userdebug".equals(android.os.Build.TYPE) || "eng".equals(android.os.Build.TYPE)) {
connect();
registerBroadcastReceiver();
}
- */
}
private void connect() {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 2bd9cab..b4bc7f5 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -37,6 +37,7 @@
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
+import java.util.Set;
public class NetworkPolicyLogger {
static final String TAG = "NetworkPolicy";
@@ -62,6 +63,7 @@
private static final int EVENT_TEMP_POWER_SAVE_WL_CHANGED = 10;
private static final int EVENT_UID_FIREWALL_RULE_CHANGED = 11;
private static final int EVENT_FIREWALL_CHAIN_ENABLED = 12;
+ private static final int EVENT_UPDATE_METERED_RESTRICTED_PKGS = 13;
static final int NTWK_BLOCKED_POWER = 0;
static final int NTWK_ALLOWED_NON_METERED = 1;
@@ -179,6 +181,14 @@
}
}
+ void meteredRestrictedPkgsChanged(Set<Integer> restrictedUids) {
+ synchronized (mLock) {
+ final String log = "Metered restricted uids: " + restrictedUids;
+ if (LOGD) Slog.d(TAG, log);
+ mEventsBuffer.event(log);
+ }
+ }
+
void dumpLogs(IndentingPrintWriter pw) {
synchronized (mLock) {
pw.println();
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 971ac8b..6490964 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -19,6 +19,8 @@
import android.net.Network;
import android.telephony.SubscriptionPlan;
+import java.util.Set;
+
/**
* Network Policy Manager local system service interface.
*
@@ -71,4 +73,21 @@
* Informs that admin data is loaded and available.
*/
public abstract void onAdminDataAvailable();
+
+ /**
+ * Sets a list of packages which are restricted by admin from accessing metered data.
+ *
+ * @param packageNames the list of restricted packages.
+ * @param userId the userId in which {@param packagesNames} are restricted.
+ */
+ public abstract void setMeteredRestrictedPackages(
+ Set<String> packageNames, int userId);
+
+
+ /**
+ * Similar to {@link #setMeteredRestrictedPackages(Set, int)} but updates the restricted
+ * packages list asynchronously.
+ */
+ public abstract void setMeteredRestrictedPackagesAsync(
+ Set<String> packageNames, int userId);
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index e406d51..0e54768 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -232,6 +232,7 @@
import java.util.Calendar;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -349,6 +350,7 @@
private static final int MSG_POLICIES_CHANGED = 13;
private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15;
private static final int MSG_SUBSCRIPTION_OVERRIDE = 16;
+ private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17;
private static final int UID_MSG_STATE_CHANGED = 100;
private static final int UID_MSG_GONE = 101;
@@ -480,6 +482,13 @@
@GuardedBy("mNetworkPoliciesSecondLock")
private final SparseIntArray mNetIdToSubId = new SparseIntArray();
+ /**
+ * Indicates the uids restricted by admin from accessing metered data. It's a mapping from
+ * userId to restricted uids which belong to that user.
+ */
+ @GuardedBy("mUidRulesFirstLock")
+ private final SparseArray<Set<Integer>> mMeteredRestrictedUids = new SparseArray<>();
+
private final RemoteCallbackList<INetworkPolicyListener>
mListeners = new RemoteCallbackList<>();
@@ -898,6 +907,9 @@
// Remove any persistable state for the given user; both cleaning up after a
// USER_REMOVED, and one last sanity check during USER_ADDED
removeUserStateUL(userId, true);
+ // Removing outside removeUserStateUL since that can also be called when
+ // user resets app preferences.
+ mMeteredRestrictedUids.remove(userId);
if (action == ACTION_USER_ADDED) {
// Add apps that are whitelisted by default.
addDefaultRestrictBackgroundWhitelistUidsUL(userId);
@@ -3137,6 +3149,15 @@
}
fout.decreaseIndent();
+ fout.println("Admin restricted uids for metered data:");
+ fout.increaseIndent();
+ size = mMeteredRestrictedUids.size();
+ for (int i = 0; i < size; ++i) {
+ fout.print("u" + mMeteredRestrictedUids.keyAt(i) + ": ");
+ fout.println(mMeteredRestrictedUids.valueAt(i));
+ }
+ fout.decreaseIndent();
+
mLogger.dumpLogs(fout);
}
}
@@ -3705,6 +3726,7 @@
final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
final int oldUidRules = mUidRules.get(uid, RULE_NONE);
final boolean isForeground = isUidForegroundOnRestrictBackgroundUL(uid);
+ final boolean isRestrictedByAdmin = isRestrictedByAdminUL(uid);
final boolean isBlacklisted = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
final boolean isWhitelisted = (uidPolicy & POLICY_ALLOW_METERED_BACKGROUND) != 0;
@@ -3712,7 +3734,9 @@
int newRule = RULE_NONE;
// First step: define the new rule based on user restrictions and foreground state.
- if (isForeground) {
+ if (isRestrictedByAdmin) {
+ newRule = RULE_REJECT_METERED;
+ } else if (isForeground) {
if (isBlacklisted || (mRestrictBackground && !isWhitelisted)) {
newRule = RULE_TEMPORARY_ALLOW_METERED;
} else if (isWhitelisted) {
@@ -3732,6 +3756,7 @@
+ ": isForeground=" +isForeground
+ ", isBlacklisted=" + isBlacklisted
+ ", isWhitelisted=" + isWhitelisted
+ + ", isRestrictedByAdmin=" + isRestrictedByAdmin
+ ", oldRule=" + uidRulesToString(oldRule)
+ ", newRule=" + uidRulesToString(newRule)
+ ", newUidRules=" + uidRulesToString(newUidRules)
@@ -3767,13 +3792,13 @@
if (!isWhitelisted) {
setMeteredNetworkWhitelist(uid, false);
}
- if (isBlacklisted) {
+ if (isBlacklisted || isRestrictedByAdmin) {
setMeteredNetworkBlacklist(uid, true);
}
} else if (hasRule(newRule, RULE_REJECT_METERED)
|| hasRule(oldRule, RULE_REJECT_METERED)) {
// Flip state because app was explicitly added or removed to blacklist.
- setMeteredNetworkBlacklist(uid, isBlacklisted);
+ setMeteredNetworkBlacklist(uid, (isBlacklisted || isRestrictedByAdmin));
if (hasRule(oldRule, RULE_REJECT_METERED) && isWhitelisted) {
// Since blacklist prevails over whitelist, we need to handle the special case
// where app is whitelisted and blacklisted at the same time (although such
@@ -3790,6 +3815,7 @@
+ ": foreground=" + isForeground
+ ", whitelisted=" + isWhitelisted
+ ", blacklisted=" + isBlacklisted
+ + ", isRestrictedByAdmin=" + isRestrictedByAdmin
+ ", newRule=" + uidRulesToString(newUidRules)
+ ", oldRule=" + uidRulesToString(oldUidRules));
}
@@ -4102,6 +4128,12 @@
mListeners.finishBroadcast();
return true;
}
+ case MSG_METERED_RESTRICTED_PACKAGES_CHANGED: {
+ final int userId = msg.arg1;
+ final Set<String> packageNames = (Set<String>) msg.obj;
+ setMeteredRestrictedPackagesInternal(packageNames, userId);
+ return true;
+ }
default: {
return false;
}
@@ -4605,6 +4637,42 @@
public void onAdminDataAvailable() {
mAdminDataAvailableLatch.countDown();
}
+
+ @Override
+ public void setMeteredRestrictedPackages(Set<String> packageNames, int userId) {
+ setMeteredRestrictedPackagesInternal(packageNames, userId);
+ }
+
+ @Override
+ public void setMeteredRestrictedPackagesAsync(Set<String> packageNames, int userId) {
+ mHandler.obtainMessage(MSG_METERED_RESTRICTED_PACKAGES_CHANGED,
+ userId, 0, packageNames).sendToTarget();
+ }
+ }
+
+ private void setMeteredRestrictedPackagesInternal(Set<String> packageNames, int userId) {
+ synchronized (mUidRulesFirstLock) {
+ final Set<Integer> newRestrictedUids = new ArraySet<>();
+ for (String packageName : packageNames) {
+ final int uid = getUidForPackage(packageName, userId);
+ if (uid >= 0) {
+ newRestrictedUids.add(uid);
+ }
+ }
+ final Set<Integer> oldRestrictedUids = mMeteredRestrictedUids.get(userId);
+ mMeteredRestrictedUids.put(userId, newRestrictedUids);
+ handleRestrictedPackagesChangeUL(oldRestrictedUids, newRestrictedUids);
+ mLogger.meteredRestrictedPkgsChanged(newRestrictedUids);
+ }
+ }
+
+ private int getUidForPackage(String packageName, int userId) {
+ try {
+ return mContext.getPackageManager().getPackageUidAsUser(packageName,
+ PackageManager.MATCH_KNOWN_PACKAGES, userId);
+ } catch (NameNotFoundException e) {
+ return -1;
+ }
}
private int parseSubId(NetworkState state) {
@@ -4642,6 +4710,32 @@
}
}
+ private void handleRestrictedPackagesChangeUL(Set<Integer> oldRestrictedUids,
+ Set<Integer> newRestrictedUids) {
+ if (oldRestrictedUids == null) {
+ for (int uid : newRestrictedUids) {
+ updateRulesForDataUsageRestrictionsUL(uid);
+ }
+ return;
+ }
+ for (int uid : oldRestrictedUids) {
+ if (!newRestrictedUids.contains(uid)) {
+ updateRulesForDataUsageRestrictionsUL(uid);
+ }
+ }
+ for (int uid : newRestrictedUids) {
+ if (!oldRestrictedUids.contains(uid)) {
+ updateRulesForDataUsageRestrictionsUL(uid);
+ }
+ }
+ }
+
+ private boolean isRestrictedByAdminUL(int uid) {
+ final Set<Integer> restrictedUids = mMeteredRestrictedUids.get(
+ UserHandle.getUserId(uid));
+ return restrictedUids != null && restrictedUids.contains(uid);
+ }
+
private static boolean hasRule(int uidRules, int rule) {
return (uidRules & rule) != 0;
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 78fd4b4..bfc150e 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -887,17 +887,21 @@
@Override
public long getUidStats(int uid, int type) {
- return nativeGetUidStat(uid, type);
+ return nativeGetUidStat(uid, type, checkBpfStatsEnable());
}
@Override
public long getIfaceStats(String iface, int type) {
- return nativeGetIfaceStat(iface, type);
+ return nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
}
@Override
public long getTotalStats(int type) {
- return nativeGetTotalStat(type);
+ return nativeGetTotalStat(type, checkBpfStatsEnable());
+ }
+
+ private boolean checkBpfStatsEnable() {
+ return new File("/sys/fs/bpf/traffic_uid_stats_map").exists();
}
/**
@@ -1668,7 +1672,7 @@
private static int TYPE_TCP_RX_PACKETS;
private static int TYPE_TCP_TX_PACKETS;
- private static native long nativeGetTotalStat(int type);
- private static native long nativeGetIfaceStat(String iface, int type);
- private static native long nativeGetUidStat(int uid, int type);
+ private static native long nativeGetTotalStat(int type, boolean useBpfStats);
+ private static native long nativeGetIfaceStat(String iface, int type, boolean useBpfStats);
+ private static native long nativeGetUidStat(int uid, int type, boolean useBpfStats);
}
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java
index ee0049b..3b6d59e 100644
--- a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java
@@ -186,6 +186,10 @@
System.currentTimeMillis());
final WatchlistReportDbHelper.AggregatedResult aggregatedResult =
mDbHelper.getAggregatedRecords();
+ if (aggregatedResult == null) {
+ Slog.i(TAG, "Cannot get result from database");
+ return;
+ }
// Get all digests for watchlist report, it should include all installed
// application digests and previously recorded app digests.
final List<String> digestsForReport = getAllDigestsForReport(aggregatedResult);
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
index 9559685..c73b0cf 100644
--- a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
@@ -16,6 +16,7 @@
package com.android.server.net.watchlist;
+import android.annotation.Nullable;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -79,7 +80,7 @@
final Set<String> appDigestList;
// The c&c domain or ip visited before.
- final String cncDomainVisited;
+ @Nullable final String cncDomainVisited;
// A list of app digests and c&c domain visited.
final HashMap<String, String> appDigestCNCList;
@@ -155,7 +156,7 @@
WhiteListReportContract.TABLE, DIGEST_DOMAIN_PROJECTION, selectStatement,
new String[]{"" + twoDaysBefore, "" + yesterday}, null, null,
null, null);
- if (c == null || c.getCount() == 0) {
+ if (c == null) {
return null;
}
final HashSet<String> appDigestList = new HashSet<>();
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 42093e8..502760a 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -254,13 +254,11 @@
for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
if (filter != null && !filter.matches(cmpt)) continue;
-
cmpt.writeToProto(proto, ManagedServicesProto.ENABLED);
}
for (ManagedServiceInfo info : mServices) {
if (filter != null && !filter.matches(info.component)) continue;
-
info.writeToProto(proto, ManagedServicesProto.LIVE_SERVICES, this);
}
@@ -1145,13 +1143,11 @@
public void writeToProto(ProtoOutputStream proto, long fieldId, ManagedServices host) {
final long token = proto.start(fieldId);
-
component.writeToProto(proto, ManagedServiceInfoProto.COMPONENT);
proto.write(ManagedServiceInfoProto.USER_ID, userid);
proto.write(ManagedServiceInfoProto.SERVICE, service.getClass().getName());
proto.write(ManagedServiceInfoProto.IS_SYSTEM, isSystem);
proto.write(ManagedServiceInfoProto.IS_GUEST, isGuest(host));
-
proto.end(token);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 39b7c7c..4c9da89 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
+import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -1881,6 +1882,18 @@
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
}
+
+ try {
+ getContext().sendBroadcastAsUser(
+ new Intent(ACTION_APP_BLOCK_STATE_CHANGED)
+ .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, !enabled)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .setPackage(pkg),
+ UserHandle.of(UserHandle.getUserId(uid)), null);
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Can't notify app about app block change", e);
+ }
+
savePolicyFile();
}
@@ -3382,36 +3395,28 @@
private void dumpProto(FileDescriptor fd, @NonNull DumpFilter filter) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
synchronized (mNotificationLock) {
- long records = proto.start(NotificationServiceDumpProto.RECORDS);
int N = mNotificationList.size();
- if (N > 0) {
- for (int i = 0; i < N; i++) {
- final NotificationRecord nr = mNotificationList.get(i);
- if (filter.filtered && !filter.matches(nr.sbn)) continue;
- nr.dump(proto, filter.redact);
- proto.write(NotificationRecordProto.STATE, NotificationRecordProto.POSTED);
- }
+ for (int i = 0; i < N; i++) {
+ final NotificationRecord nr = mNotificationList.get(i);
+ if (filter.filtered && !filter.matches(nr.sbn)) continue;
+ nr.dump(proto, NotificationServiceDumpProto.RECORDS, filter.redact,
+ NotificationRecordProto.POSTED);
}
N = mEnqueuedNotifications.size();
- if (N > 0) {
- for (int i = 0; i < N; i++) {
- final NotificationRecord nr = mEnqueuedNotifications.get(i);
- if (filter.filtered && !filter.matches(nr.sbn)) continue;
- nr.dump(proto, filter.redact);
- proto.write(NotificationRecordProto.STATE, NotificationRecordProto.ENQUEUED);
- }
+ for (int i = 0; i < N; i++) {
+ final NotificationRecord nr = mEnqueuedNotifications.get(i);
+ if (filter.filtered && !filter.matches(nr.sbn)) continue;
+ nr.dump(proto, NotificationServiceDumpProto.RECORDS, filter.redact,
+ NotificationRecordProto.ENQUEUED);
}
List<NotificationRecord> snoozed = mSnoozeHelper.getSnoozed();
N = snoozed.size();
- if (N > 0) {
- for (int i = 0; i < N; i++) {
- final NotificationRecord nr = snoozed.get(i);
- if (filter.filtered && !filter.matches(nr.sbn)) continue;
- nr.dump(proto, filter.redact);
- proto.write(NotificationRecordProto.STATE, NotificationRecordProto.SNOOZED);
- }
+ for (int i = 0; i < N; i++) {
+ final NotificationRecord nr = snoozed.get(i);
+ if (filter.filtered && !filter.matches(nr.sbn)) continue;
+ nr.dump(proto, NotificationServiceDumpProto.RECORDS, filter.redact,
+ NotificationRecordProto.SNOOZED);
}
- proto.end(records);
long zenLog = proto.start(NotificationServiceDumpProto.ZEN);
mZenModeHelper.dump(proto);
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index faa300f2..23b9743 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -361,8 +361,11 @@
/** @deprecated Use {@link #getUser()} instead. */
public int getUserId() { return sbn.getUserId(); }
- void dump(ProtoOutputStream proto, boolean redact) {
+ void dump(ProtoOutputStream proto, long fieldId, boolean redact, int state) {
+ final long token = proto.start(fieldId);
+
proto.write(NotificationRecordProto.KEY, sbn.getKey());
+ proto.write(NotificationRecordProto.STATE, state);
if (getChannel() != null) {
proto.write(NotificationRecordProto.CHANNEL_ID, getChannel().getId());
}
@@ -375,8 +378,10 @@
proto.write(NotificationRecordProto.SOUND, getSound().toString());
}
if (getAudioAttributes() != null) {
- proto.write(NotificationRecordProto.SOUND_USAGE, getAudioAttributes().getUsage());
+ getAudioAttributes().writeToProto(proto, NotificationRecordProto.AUDIO_ATTRIBUTES);
}
+
+ proto.end(token);
}
String formatRemoteViews(RemoteViews rv) {
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index c0dccb5..b0e3820 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -973,16 +973,11 @@
proto.write(RecordProto.VISIBILITY, r.visibility);
proto.write(RecordProto.SHOW_BADGE, r.showBadge);
- long token;
for (NotificationChannel channel : r.channels.values()) {
- token = proto.start(RecordProto.CHANNELS);
- channel.toProto(proto);
- proto.end(token);
+ channel.writeToProto(proto, RecordProto.CHANNELS);
}
for (NotificationChannelGroup group : r.groups.values()) {
- token = proto.start(RecordProto.CHANNEL_GROUPS);
- group.toProto(proto);
- proto.end(token);
+ group.writeToProto(proto, RecordProto.CHANNEL_GROUPS);
}
proto.end(fToken);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 8f672b5..932e4f9 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -565,7 +565,7 @@
proto.write(ZenModeProto.ENABLED_ACTIVE_CONDITIONS, rule.toString());
}
}
- mConfig.toNotificationPolicy().toProto(proto, ZenModeProto.POLICY);
+ mConfig.toNotificationPolicy().writeToProto(proto, ZenModeProto.POLICY);
proto.write(ZenModeProto.SUPPRESSED_EFFECTS, mSuppressedEffects);
}
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 253d4f5..321af43 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -169,8 +169,9 @@
}
final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId);
- updateAllOverlaysForTarget(packageName, userId, targetPackage);
- mListener.onOverlaysChanged(packageName, userId);
+ if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) {
+ mListener.onOverlaysChanged(packageName, userId);
+ }
}
void onTargetPackageChanged(@NonNull final String packageName, final int userId) {
@@ -210,7 +211,9 @@
Slog.d(TAG, "onTargetPackageRemoved packageName=" + packageName + " userId=" + userId);
}
- updateAllOverlaysForTarget(packageName, userId, null);
+ if (updateAllOverlaysForTarget(packageName, userId, null)) {
+ mListener.onOverlaysChanged(packageName, userId);
+ }
}
/**
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index c059b37..7d00423 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -230,7 +230,7 @@
}
mItems.remove(moveIdx);
- final int newParentIdx = select(newParentPackageName, userId);
+ final int newParentIdx = select(newParentPackageName, userId) + 1;
mItems.add(newParentIdx, itemToMove);
return moveIdx != newParentIdx;
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 4b3758d..cdc79c7 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -16,7 +16,9 @@
package com.android.server.pm;
+import android.annotation.AppIdInt;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.PackageStats;
import android.os.Build;
@@ -58,6 +60,9 @@
public static final int DEXOPT_STORAGE_DE = 1 << 8;
/** Indicates that dexopt is invoked from the background service. */
public static final int DEXOPT_IDLE_BACKGROUND_JOB = 1 << 9;
+ /* Indicates that dexopt should not restrict access to private APIs.
+ * Must be kept in sync with com.android.internal.os.ZygoteInit. */
+ public static final int DEXOPT_DISABLE_HIDDEN_API_CHECKS = 1 << 10;
// NOTE: keep in sync with installd
public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
@@ -535,6 +540,17 @@
}
}
+ public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId,
+ String profileName, String codePath, String dexMetadataPath) throws InstallerException {
+ if (!checkBeforeRemote()) return false;
+ try {
+ return mInstalld.prepareAppProfile(pkg, userId, appId, profileName, codePath,
+ dexMetadataPath);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
private static void assertValidInstructionSet(String instructionSet)
throws InstallerException {
for (String abi : Build.SUPPORTED_ABIS) {
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 91df87b..6a08e1b 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -55,6 +55,7 @@
import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE;
import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE;
import static com.android.server.pm.Installer.DEXOPT_IDLE_BACKGROUND_JOB;
+import static com.android.server.pm.Installer.DEXOPT_DISABLE_HIDDEN_API_CHECKS;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
@@ -509,12 +510,18 @@
boolean isProfileGuidedFilter = isProfileGuidedCompilerFilter(compilerFilter);
boolean isPublic = !info.isForwardLocked() && !isProfileGuidedFilter;
int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
+ // System apps are invoked with a runtime flag which exempts them from
+ // restrictions on hidden API usage. We dexopt with the same runtime flag
+ // otherwise offending methods would have to be re-verified at runtime
+ // and we want to avoid the performance overhead of that.
+ int hiddenApiFlag = info.isAllowedToUseHiddenApi() ? DEXOPT_DISABLE_HIDDEN_API_CHECKS : 0;
int dexFlags =
(isPublic ? DEXOPT_PUBLIC : 0)
| (debuggable ? DEXOPT_DEBUGGABLE : 0)
| profileFlag
| (options.isBootComplete() ? DEXOPT_BOOTCOMPLETE : 0)
- | (options.isDexoptIdleBackgroundJob() ? DEXOPT_IDLE_BACKGROUND_JOB : 0);
+ | (options.isDexoptIdleBackgroundJob() ? DEXOPT_IDLE_BACKGROUND_JOB : 0)
+ | hiddenApiFlag;
return adjustDexoptFlags(dexFlags);
}
@@ -629,6 +636,9 @@
if ((flags & DEXOPT_IDLE_BACKGROUND_JOB) == DEXOPT_IDLE_BACKGROUND_JOB) {
flagsList.add("idle_background_job");
}
+ if ((flags & DEXOPT_DISABLE_HIDDEN_API_CHECKS) == DEXOPT_DISABLE_HIDDEN_API_CHECKS) {
+ flagsList.add("disable_hidden_api_checks");
+ }
return String.join(",", flagsList);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index faf6114..a73ad8d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -24,6 +24,8 @@
import static android.Manifest.permission.REQUEST_DELETE_PACKAGES;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
+import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
+import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
@@ -108,6 +110,8 @@
import static com.android.server.pm.PackageManagerServiceUtils.getCompressedFiles;
import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTime;
import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+import static com.android.server.pm.PackageManagerServiceUtils.signingDetailsHasCertificate;
+import static com.android.server.pm.PackageManagerServiceUtils.signingDetailsHasSha256Certificate;
import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE;
import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS;
@@ -2426,6 +2430,7 @@
installer, mInstallLock);
mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock,
dexManagerListener);
+ mArtManagerService = new ArtManagerService(this, installer, mInstallLock);
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -3082,7 +3087,6 @@
}
mInstallerService = new PackageInstallerService(context, this);
- mArtManagerService = new ArtManagerService(this, mInstaller, mInstallLock);
final Pair<ComponentName, String> instantAppResolverComponent =
getInstantAppResolverLPr();
if (instantAppResolverComponent != null) {
@@ -5421,13 +5425,13 @@
if (isCallerInstantApp) {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
- s1 = ((SharedUserSetting)obj).signatures.mSignatures;
+ s1 = ((SharedUserSetting)obj).signatures.mSigningDetails.signatures;
} else if (obj instanceof PackageSetting) {
final PackageSetting ps = (PackageSetting) obj;
if (filterAppAccessLPr(ps, callingUid, callingUserId)) {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
- s1 = ps.signatures.mSignatures;
+ s1 = ps.signatures.mSigningDetails.signatures;
} else {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
@@ -5440,13 +5444,13 @@
if (isCallerInstantApp) {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
- s2 = ((SharedUserSetting)obj).signatures.mSignatures;
+ s2 = ((SharedUserSetting)obj).signatures.mSigningDetails.signatures;
} else if (obj instanceof PackageSetting) {
final PackageSetting ps = (PackageSetting) obj;
if (filterAppAccessLPr(ps, callingUid, callingUserId)) {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
- s2 = ps.signatures.mSignatures;
+ s2 = ps.signatures.mSigningDetails.signatures;
} else {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
@@ -5457,6 +5461,73 @@
}
}
+ @Override
+ public boolean hasSigningCertificate(
+ String packageName, byte[] certificate, @PackageManager.CertificateInputType int type) {
+
+ synchronized (mPackages) {
+ final PackageParser.Package p = mPackages.get(packageName);
+ if (p == null || p.mExtras == null) {
+ return false;
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ final PackageSetting ps = (PackageSetting) p.mExtras;
+ if (filterAppAccessLPr(ps, callingUid, callingUserId)) {
+ return false;
+ }
+ switch (type) {
+ case CERT_INPUT_RAW_X509:
+ return signingDetailsHasCertificate(certificate, p.mSigningDetails);
+ case CERT_INPUT_SHA256:
+ return signingDetailsHasSha256Certificate(certificate, p.mSigningDetails);
+ default:
+ return false;
+ }
+ }
+ }
+
+ @Override
+ public boolean hasUidSigningCertificate(
+ int uid, byte[] certificate, @PackageManager.CertificateInputType int type) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ // Map to base uids.
+ uid = UserHandle.getAppId(uid);
+ // reader
+ synchronized (mPackages) {
+ final PackageParser.SigningDetails signingDetails;
+ final Object obj = mSettings.getUserIdLPr(uid);
+ if (obj != null) {
+ if (obj instanceof SharedUserSetting) {
+ final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
+ if (isCallerInstantApp) {
+ return false;
+ }
+ signingDetails = ((SharedUserSetting)obj).signatures.mSigningDetails;
+ } else if (obj instanceof PackageSetting) {
+ final PackageSetting ps = (PackageSetting) obj;
+ if (filterAppAccessLPr(ps, callingUid, callingUserId)) {
+ return false;
+ }
+ signingDetails = ps.signatures.mSigningDetails;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ switch (type) {
+ case CERT_INPUT_RAW_X509:
+ return signingDetailsHasCertificate(certificate, signingDetails);
+ case CERT_INPUT_SHA256:
+ return signingDetailsHasSha256Certificate(certificate, signingDetails);
+ default:
+ return false;
+ }
+ }
+ }
+
/**
* This method should typically only be used when granting or revoking
* permissions, since the app may immediately restart after this call.
@@ -8233,19 +8304,15 @@
&& ps.timeStamp == lastModifiedTime
&& !isCompatSignatureUpdateNeeded(pkg)
&& !isRecoverSignatureUpdateNeeded(pkg)) {
- if (ps.signatures.mSignatures != null
- && ps.signatures.mSignatures.length != 0
- && ps.signatures.mSignatureSchemeVersion != SignatureSchemeVersion.UNKNOWN) {
+ if (ps.signatures.mSigningDetails.signatures != null
+ && ps.signatures.mSigningDetails.signatures.length != 0
+ && ps.signatures.mSigningDetails.signatureSchemeVersion
+ != SignatureSchemeVersion.UNKNOWN) {
// Optimization: reuse the existing cached signing data
// if the package appears to be unchanged.
- try {
- pkg.mSigningDetails = new PackageParser.SigningDetails(ps.signatures.mSignatures,
- ps.signatures.mSignatureSchemeVersion);
- return;
- } catch (CertificateException e) {
- Slog.e(TAG, "Attempt to read public keys from persisted signatures failed for "
- + ps.name, e);
- }
+ pkg.mSigningDetails =
+ new PackageParser.SigningDetails(ps.signatures.mSigningDetails);
+ return;
}
Slog.w(TAG, "PackageSetting for " + ps.name
@@ -8573,8 +8640,9 @@
if (scanSystemPartition && !isSystemPkgUpdated && pkgAlreadyExists
&& !pkgSetting.isSystem()) {
// if the signatures don't match, wipe the installed application and its data
- if (compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSigningDetails.signatures)
- != PackageManager.SIGNATURE_MATCH) {
+ if (compareSignatures(pkgSetting.signatures.mSigningDetails.signatures,
+ pkg.mSigningDetails.signatures)
+ != PackageManager.SIGNATURE_MATCH) {
logCriticalInfo(Log.WARN,
"System package signature mismatch;"
+ " name: " + pkgSetting.name);
@@ -9765,8 +9833,9 @@
* <li>{@link #SCAN_AS_VIRTUAL_PRELOAD}</li>
* </ul>
*/
- private static @ScanFlags int adjustScanFlags(@ScanFlags int scanFlags,
- PackageSetting pkgSetting, PackageSetting disabledPkgSetting, UserHandle user) {
+ private @ScanFlags int adjustScanFlags(@ScanFlags int scanFlags,
+ PackageSetting pkgSetting, PackageSetting disabledPkgSetting, UserHandle user,
+ PackageParser.Package pkg) {
if (disabledPkgSetting != null) {
// updated system application, must at least have SCAN_AS_SYSTEM
scanFlags |= SCAN_AS_SYSTEM;
@@ -9792,6 +9861,30 @@
scanFlags |= SCAN_AS_VIRTUAL_PRELOAD;
}
}
+
+ // Scan as privileged apps that share a user with a priv-app.
+ if (((scanFlags & SCAN_AS_PRIVILEGED) == 0) && !pkg.isPrivileged()
+ && (pkg.mSharedUserId != null)) {
+ SharedUserSetting sharedUserSetting = null;
+ try {
+ sharedUserSetting = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, 0, false);
+ } catch (PackageManagerException ignore) {}
+ if (sharedUserSetting != null && sharedUserSetting.isPrivileged()) {
+ // Exempt SharedUsers signed with the platform key.
+ // TODO(b/72378145) Fix this exemption. Force signature apps
+ // to whitelist their privileged permissions just like other
+ // priv-apps.
+ synchronized (mPackages) {
+ PackageSetting platformPkgSetting = mSettings.mPackages.get("android");
+ if (!pkg.packageName.equals("android")
+ && (compareSignatures(platformPkgSetting.signatures.mSigningDetails.signatures,
+ pkg.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH)) {
+ scanFlags |= SCAN_AS_PRIVILEGED;
+ }
+ }
+ }
+ }
+
return scanFlags;
}
@@ -9815,7 +9908,7 @@
+ " was transferred to another, but its .apk remains");
}
- scanFlags = adjustScanFlags(scanFlags, pkgSetting, disabledPkgSetting, user);
+ scanFlags = adjustScanFlags(scanFlags, pkgSetting, disabledPkgSetting, user, pkg);
synchronized (mPackages) {
applyPolicy(pkg, parseFlags, scanFlags);
assertPackageIsValid(pkg, parseFlags, scanFlags);
@@ -9936,14 +10029,14 @@
if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, pkg)) {
// We just determined the app is signed correctly, so bring
// over the latest parsed certs.
- pkgSetting.signatures.mSignatures = pkg.mSigningDetails.signatures;
+ pkgSetting.signatures.mSigningDetails = pkg.mSigningDetails;
} else {
if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"Package " + pkg.packageName + " upgrade keys do not match the "
+ "previously installed version");
} else {
- pkgSetting.signatures.mSignatures = pkg.mSigningDetails.signatures;
+ pkgSetting.signatures.mSigningDetails = pkg.mSigningDetails;
String msg = "System package " + pkg.packageName
+ " signature changed; retaining data.";
reportSettingsProblem(Log.WARN, msg);
@@ -9963,21 +10056,22 @@
}
// We just determined the app is signed correctly, so bring
// over the latest parsed certs.
- pkgSetting.signatures.mSignatures = pkg.mSigningDetails.signatures;
+ pkgSetting.signatures.mSigningDetails = pkg.mSigningDetails;
} catch (PackageManagerException e) {
if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
throw e;
}
// The signature has changed, but this package is in the system
// image... let's recover!
- pkgSetting.signatures.mSignatures = pkg.mSigningDetails.signatures;
+ pkgSetting.signatures.mSigningDetails = pkg.mSigningDetails;
// However... if this package is part of a shared user, but it
// doesn't match the signature of the shared user, let's fail.
// What this means is that you can't change the signatures
// associated with an overall shared user, which doesn't seem all
// that unreasonable.
if (signatureCheckPs.sharedUser != null) {
- if (compareSignatures(signatureCheckPs.sharedUser.signatures.mSignatures,
+ if (compareSignatures(
+ signatureCheckPs.sharedUser.signatures.mSigningDetails.signatures,
pkg.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH) {
throw new PackageManagerException(
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
@@ -10804,9 +10898,12 @@
if (sharedUserSetting != null && sharedUserSetting.isPrivileged()) {
// Exempt SharedUsers signed with the platform key.
PackageSetting platformPkgSetting = mSettings.mPackages.get("android");
- if ((platformPkgSetting.signatures.mSignatures != null) &&
- (compareSignatures(platformPkgSetting.signatures.mSignatures,
- pkg.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH)) {
+ if ((platformPkgSetting.signatures.mSigningDetails
+ != PackageParser.SigningDetails.UNKNOWN)
+ && (compareSignatures(
+ platformPkgSetting.signatures.mSigningDetails.signatures,
+ pkg.mSigningDetails.signatures)
+ != PackageManager.SIGNATURE_MATCH)) {
throw new PackageManagerException("Apps that share a user with a " +
"privileged app must themselves be marked as privileged. " +
pkg.packageName + " shares privileged user " +
@@ -14248,9 +14345,10 @@
Object obj = mSettings.getUserIdLPr(callingUid);
if (obj != null) {
if (obj instanceof SharedUserSetting) {
- callerSignature = ((SharedUserSetting)obj).signatures.mSignatures;
+ callerSignature =
+ ((SharedUserSetting)obj).signatures.mSigningDetails.signatures;
} else if (obj instanceof PackageSetting) {
- callerSignature = ((PackageSetting)obj).signatures.mSignatures;
+ callerSignature = ((PackageSetting)obj).signatures.mSigningDetails.signatures;
} else {
throw new SecurityException("Bad object " + obj + " for uid " + callingUid);
}
@@ -14262,7 +14360,7 @@
// not signed with the same cert as the caller.
if (installerPackageSetting != null) {
if (compareSignatures(callerSignature,
- installerPackageSetting.signatures.mSignatures)
+ installerPackageSetting.signatures.mSigningDetails.signatures)
!= PackageManager.SIGNATURE_MATCH) {
throw new SecurityException(
"Caller does not have same cert as new installer package "
@@ -14279,7 +14377,7 @@
// okay to change it.
if (setting != null) {
if (compareSignatures(callerSignature,
- setting.signatures.mSignatures)
+ setting.signatures.mSigningDetails.signatures)
!= PackageManager.SIGNATURE_MATCH) {
throw new SecurityException(
"Caller does not have same cert as old installer package "
@@ -16787,7 +16885,8 @@
sourcePackageSetting, scanFlags))) {
sigsOk = ksms.checkUpgradeKeySetLocked(sourcePackageSetting, pkg);
} else {
- sigsOk = compareSignatures(sourcePackageSetting.signatures.mSignatures,
+ sigsOk = compareSignatures(
+ sourcePackageSetting.signatures.mSigningDetails.signatures,
pkg.mSigningDetails.signatures) == PackageManager.SIGNATURE_MATCH;
}
if (!sigsOk) {
@@ -16920,6 +17019,11 @@
}
}
+ // Prepare the application profiles for the new code paths.
+ // This needs to be done before invoking dexopt so that any install-time profile
+ // can be used for optimizations.
+ mArtManagerService.prepareAppProfiles(pkg, args.user.getIdentifier());
+
// Check whether we need to dexopt the app.
//
// NOTE: it is IMPORTANT to call dexopt:
@@ -21934,6 +22038,8 @@
Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
}
}
+ // Prepare the application profiles.
+ mArtManagerService.prepareAppProfiles(pkg, userId);
if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
// TODO: mark this structure as dirty so we persist it!
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 7b96ca6..021c4b8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -54,6 +54,7 @@
import android.system.Os;
import android.util.ArraySet;
import android.util.Log;
+import android.util.PackageUtils;
import android.util.Slog;
import android.util.jar.StrictJarFile;
import android.util.proto.ProtoOutputStream;
@@ -74,6 +75,8 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
@@ -509,7 +512,7 @@
private static boolean matchSignaturesCompat(String packageName,
PackageSignatures packageSignatures, PackageParser.SigningDetails parsedSignatures) {
ArraySet<Signature> existingSet = new ArraySet<Signature>();
- for (Signature sig : packageSignatures.mSignatures) {
+ for (Signature sig : packageSignatures.mSigningDetails.signatures) {
existingSet.add(sig);
}
ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
@@ -526,7 +529,7 @@
// make sure the expanded scanned set contains all signatures in the existing one
if (scannedCompatSet.equals(existingSet)) {
// migrate the old signatures to the new scheme
- packageSignatures.assignSignatures(parsedSignatures);
+ packageSignatures.mSigningDetails = parsedSignatures;
return true;
}
return false;
@@ -561,8 +564,8 @@
try {
PackageParser.collectCertificates(disabledPkgSetting.pkg,
PackageParser.PARSE_IS_SYSTEM_DIR);
- if (compareSignatures(pkgSetting.signatures.mSignatures,
- disabledPkgSetting.signatures.mSignatures)
+ if (compareSignatures(pkgSetting.signatures.mSigningDetails.signatures,
+ disabledPkgSetting.signatures.mSigningDetails.signatures)
!= PackageManager.SIGNATURE_MATCH) {
logCriticalInfo(Log.ERROR, "Updated system app mismatches cert on /system: " +
pkgSetting.name);
@@ -576,6 +579,69 @@
return true;
}
+
+ /**
+ * Checks the signing certificates to see if the provided certificate is a member. Invalid for
+ * {@code SigningDetails} with multiple signing certificates.
+ * @param certificate certificate to check for membership
+ * @param signingDetails signing certificates record whose members are to be searched
+ * @return true if {@code certificate} is in {@code signingDetails}
+ */
+ public static boolean signingDetailsHasCertificate(
+ byte[] certificate, PackageParser.SigningDetails signingDetails) {
+ if (signingDetails == PackageParser.SigningDetails.UNKNOWN) {
+ return false;
+ }
+ Signature signature = new Signature(certificate);
+ if (signingDetails.hasPastSigningCertificates()) {
+ for (int i = 0; i < signingDetails.pastSigningCertificates.length; i++) {
+ if (signingDetails.pastSigningCertificates[i].equals(signature)) {
+ return true;
+ }
+ }
+ } else {
+ // no signing history, just check the current signer
+ if (signingDetails.signatures.length == 1
+ && signingDetails.signatures[0].equals(signature)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks the signing certificates to see if the provided certificate is a member. Invalid for
+ * {@code SigningDetails} with multiple signing certificaes.
+ * @param sha256Certificate certificate to check for membership
+ * @param signingDetails signing certificates record whose members are to be searched
+ * @return true if {@code certificate} is in {@code signingDetails}
+ */
+ public static boolean signingDetailsHasSha256Certificate(
+ byte[] sha256Certificate, PackageParser.SigningDetails signingDetails ) {
+ if (signingDetails == PackageParser.SigningDetails.UNKNOWN) {
+ return false;
+ }
+ if (signingDetails.hasPastSigningCertificates()) {
+ for (int i = 0; i < signingDetails.pastSigningCertificates.length; i++) {
+ byte[] digest = PackageUtils.computeSha256DigestBytes(
+ signingDetails.pastSigningCertificates[i].toByteArray());
+ if (Arrays.equals(sha256Certificate, digest)) {
+ return true;
+ }
+ }
+ } else {
+ // no signing history, just check the current signer
+ if (signingDetails.signatures.length == 1) {
+ byte[] digest = PackageUtils.computeSha256DigestBytes(
+ signingDetails.signatures[0].toByteArray());
+ if (Arrays.equals(sha256Certificate, digest)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
/** Returns true to force apk verification if the updated package (in /data) is a priv app. */
static boolean isApkVerificationForced(@Nullable PackageSetting disabledPs) {
return disabledPs != null && disabledPs.isPrivileged() &&
@@ -593,9 +659,9 @@
throws PackageManagerException {
final String packageName = pkgSetting.name;
boolean compatMatch = false;
- if (pkgSetting.signatures.mSignatures != null) {
+ if (pkgSetting.signatures.mSigningDetails.signatures != null) {
// Already existing package. Make sure signatures match
- boolean match = compareSignatures(pkgSetting.signatures.mSignatures,
+ boolean match = compareSignatures(pkgSetting.signatures.mSigningDetails.signatures,
parsedSignatures.signatures)
== PackageManager.SIGNATURE_MATCH;
if (!match && compareCompat) {
@@ -605,7 +671,7 @@
}
if (!match && compareRecover) {
match = matchSignaturesRecover(
- packageName, pkgSetting.signatures.mSignatures,
+ packageName, pkgSetting.signatures.mSigningDetails.signatures,
parsedSignatures.signatures);
}
@@ -620,17 +686,21 @@
}
}
// Check for shared user signatures
- if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
+ if (pkgSetting.sharedUser != null
+ && pkgSetting.sharedUser.signatures.mSigningDetails.signatures != null) {
// Already existing package. Make sure signatures match
- boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
- parsedSignatures.signatures) == PackageManager.SIGNATURE_MATCH;
+ boolean match =
+ compareSignatures(
+ pkgSetting.sharedUser.signatures.mSigningDetails.signatures,
+ parsedSignatures.signatures) == PackageManager.SIGNATURE_MATCH;
if (!match && compareCompat) {
match = matchSignaturesCompat(
packageName, pkgSetting.sharedUser.signatures, parsedSignatures);
}
if (!match && compareRecover) {
match = matchSignaturesRecover(packageName,
- pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures.signatures);
+ pkgSetting.sharedUser.signatures.mSigningDetails.signatures,
+ parsedSignatures.signatures);
compatMatch |= match;
}
if (!match) {
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index e3c4c43..18356c5 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -233,7 +233,7 @@
}
public Signature[] getSignatures() {
- return signatures.mSignatures;
+ return signatures.mSigningDetails.signatures;
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageSignatures.java b/services/core/java/com/android/server/pm/PackageSignatures.java
index d567d5c..d471fc8 100644
--- a/services/core/java/com/android/server/pm/PackageSignatures.java
+++ b/services/core/java/com/android/server/pm/PackageSignatures.java
@@ -22,91 +22,148 @@
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import android.annotation.NonNull;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.Signature;
import android.util.Log;
import java.io.IOException;
+import java.security.cert.CertificateException;
import java.util.ArrayList;
class PackageSignatures {
- Signature[] mSignatures;
- @SignatureSchemeVersion int mSignatureSchemeVersion;
+
+ @NonNull PackageParser.SigningDetails mSigningDetails;
PackageSignatures(PackageSignatures orig) {
- if (orig != null && orig.mSignatures != null) {
- mSignatures = orig.mSignatures.clone();
- mSignatureSchemeVersion = orig.mSignatureSchemeVersion;
+ if (orig != null && orig.mSigningDetails != PackageParser.SigningDetails.UNKNOWN) {
+ mSigningDetails = new PackageParser.SigningDetails(orig.mSigningDetails);
+ } else {
+ mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
}
}
PackageSignatures(PackageParser.SigningDetails signingDetails) {
- assignSignatures(signingDetails);
+ mSigningDetails = signingDetails;
}
PackageSignatures() {
+ mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
}
void writeXml(XmlSerializer serializer, String tagName,
- ArrayList<Signature> pastSignatures) throws IOException {
- if (mSignatures == null) {
+ ArrayList<Signature> writtenSignatures) throws IOException {
+ if (mSigningDetails.signatures == null) {
return;
}
serializer.startTag(null, tagName);
- serializer.attribute(null, "count",
- Integer.toString(mSignatures.length));
- serializer.attribute(null, "schemeVersion", Integer.toString(mSignatureSchemeVersion));
- for (int i=0; i<mSignatures.length; i++) {
- serializer.startTag(null, "cert");
- final Signature sig = mSignatures[i];
- final int sigHash = sig.hashCode();
- final int numPast = pastSignatures.size();
- int j;
- for (j=0; j<numPast; j++) {
- Signature pastSig = pastSignatures.get(j);
- if (pastSig.hashCode() == sigHash && pastSig.equals(sig)) {
- serializer.attribute(null, "index", Integer.toString(j));
- break;
- }
- }
- if (j >= numPast) {
- pastSignatures.add(sig);
- serializer.attribute(null, "index", Integer.toString(numPast));
- serializer.attribute(null, "key", sig.toCharsString());
- }
- serializer.endTag(null, "cert");
+ serializer.attribute(null, "count", Integer.toString(mSigningDetails.signatures.length));
+ serializer.attribute(null, "schemeVersion",
+ Integer.toString(mSigningDetails.signatureSchemeVersion));
+ writeCertsListXml(serializer, writtenSignatures, mSigningDetails.signatures, null);
+
+ // if we have past signer certificate information, write it out
+ if (mSigningDetails.pastSigningCertificates != null) {
+ serializer.startTag(null, "pastSigs");
+ serializer.attribute(null, "count",
+ Integer.toString(mSigningDetails.pastSigningCertificates.length));
+ writeCertsListXml(
+ serializer, writtenSignatures, mSigningDetails.pastSigningCertificates,
+ mSigningDetails.pastSigningCertificatesFlags);
+ serializer.endTag(null, "pastSigs");
}
serializer.endTag(null, tagName);
}
- void readXml(XmlPullParser parser, ArrayList<Signature> pastSignatures)
+ private void writeCertsListXml(XmlSerializer serializer, ArrayList<Signature> writtenSignatures,
+ Signature[] signatures, int[] flags) throws IOException {
+ for (int i=0; i<signatures.length; i++) {
+ serializer.startTag(null, "cert");
+ final Signature sig = signatures[i];
+ final int sigHash = sig.hashCode();
+ final int numWritten = writtenSignatures.size();
+ int j;
+ for (j=0; j<numWritten; j++) {
+ Signature writtenSig = writtenSignatures.get(j);
+ if (writtenSig.hashCode() == sigHash && writtenSig.equals(sig)) {
+ serializer.attribute(null, "index", Integer.toString(j));
+ break;
+ }
+ }
+ if (j >= numWritten) {
+ writtenSignatures.add(sig);
+ serializer.attribute(null, "index", Integer.toString(numWritten));
+ serializer.attribute(null, "key", sig.toCharsString());
+ }
+ if (flags != null) {
+ serializer.attribute(null, "flags", Integer.toString(flags[i]));
+ }
+ serializer.endTag(null, "cert");
+ }
+ }
+
+ void readXml(XmlPullParser parser, ArrayList<Signature> readSignatures)
throws IOException, XmlPullParserException {
+ PackageParser.SigningDetails.Builder builder =
+ new PackageParser.SigningDetails.Builder();
+
String countStr = parser.getAttributeValue(null, "count");
if (countStr == null) {
PackageManagerService.reportSettingsProblem(Log.WARN,
- "Error in package manager settings: <signatures> has"
+ "Error in package manager settings: <sigs> has"
+ " no count at " + parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
}
+ final int count = Integer.parseInt(countStr);
+
String schemeVersionStr = parser.getAttributeValue(null, "schemeVersion");
+ int signatureSchemeVersion;
if (schemeVersionStr == null) {
PackageManagerService.reportSettingsProblem(Log.WARN,
- "Error in package manager settings: <signatures> has no schemeVersion at "
+ "Error in package manager settings: <sigs> has no schemeVersion at "
+ parser.getPositionDescription());
- mSignatureSchemeVersion = SignatureSchemeVersion.UNKNOWN;
+ signatureSchemeVersion = SignatureSchemeVersion.UNKNOWN;
} else {
- mSignatureSchemeVersion = Integer.parseInt(countStr);
+ signatureSchemeVersion = Integer.parseInt(schemeVersionStr);
}
- final int count = Integer.parseInt(countStr);
- mSignatures = new Signature[count];
+ builder.setSignatureSchemeVersion(signatureSchemeVersion);
+ Signature[] signatures = new Signature[count];
+ int pos = readCertsListXml(parser, readSignatures, signatures, null, builder);
+ builder.setSignatures(signatures);
+ if (pos < count) {
+ // Should never happen -- there is an error in the written
+ // settings -- but if it does we don't want to generate
+ // a bad array.
+ Signature[] newSigs = new Signature[pos];
+ System.arraycopy(signatures, 0, newSigs, 0, pos);
+ builder = builder.setSignatures(newSigs);
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Error in package manager settings: <sigs> count does not match number of "
+ + " <cert> entries" + parser.getPositionDescription());
+ }
+
+ try {
+ mSigningDetails = builder.build();
+ } catch (CertificateException e) {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Error in package manager settings: <sigs> "
+ + "unable to convert certificate(s) to public key(s).");
+ mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
+ }
+ }
+
+ private int readCertsListXml(XmlPullParser parser, ArrayList<Signature> readSignatures,
+ Signature[] signatures, int[] flags, PackageParser.SigningDetails.Builder builder)
+ throws IOException, XmlPullParserException {
+ int count = signatures.length;
int pos = 0;
int outerDepth = parser.getDepth();
int type;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG
- || parser.getDepth() > outerDepth)) {
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG
|| type == XmlPullParser.TEXT) {
continue;
@@ -121,83 +178,128 @@
int idx = Integer.parseInt(index);
String key = parser.getAttributeValue(null, "key");
if (key == null) {
- if (idx >= 0 && idx < pastSignatures.size()) {
- Signature sig = pastSignatures.get(idx);
+ if (idx >= 0 && idx < readSignatures.size()) {
+ Signature sig = readSignatures.get(idx);
if (sig != null) {
- mSignatures[pos] = pastSignatures.get(idx);
+ signatures[pos] = readSignatures.get(idx);
pos++;
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: <cert> "
- + "index " + index + " is not defined at "
- + parser.getPositionDescription());
+ + "index " + index + " is not defined at "
+ + parser.getPositionDescription());
}
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: <cert> "
- + "index " + index + " is out of bounds at "
- + parser.getPositionDescription());
+ + "index " + index + " is out of bounds at "
+ + parser.getPositionDescription());
}
} else {
- while (pastSignatures.size() <= idx) {
- pastSignatures.add(null);
+ while (readSignatures.size() <= idx) {
+ readSignatures.add(null);
}
Signature sig = new Signature(key);
- pastSignatures.set(idx, sig);
- mSignatures[pos] = sig;
+ readSignatures.set(idx, sig);
+ signatures[pos] = sig;
pos++;
}
} catch (NumberFormatException e) {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: <cert> "
- + "index " + index + " is not a number at "
- + parser.getPositionDescription());
+ + "index " + index + " is not a number at "
+ + parser.getPositionDescription());
} catch (IllegalArgumentException e) {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: <cert> "
- + "index " + index + " has an invalid signature at "
- + parser.getPositionDescription() + ": "
- + e.getMessage());
+ + "index " + index + " has an invalid signature at "
+ + parser.getPositionDescription() + ": "
+ + e.getMessage());
+ }
+
+ if (flags != null) {
+ String flagsStr = parser.getAttributeValue(null, "flags");
+ if (flagsStr != null) {
+ try {
+ flags[pos] = Integer.parseInt(flagsStr);
+ } catch (NumberFormatException e) {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Error in package manager settings: <cert> "
+ + "flags " + flagsStr + " is not a number at "
+ + parser.getPositionDescription());
+ }
+ } else {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Error in package manager settings: <cert> has no"
+ + " flags at " + parser.getPositionDescription());
+ }
}
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: <cert> has"
- + " no index at " + parser.getPositionDescription());
+ + " no index at " + parser.getPositionDescription());
}
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: too "
- + "many <cert> tags, expected " + count
- + " at " + parser.getPositionDescription());
+ + "many <cert> tags, expected " + count
+ + " at " + parser.getPositionDescription());
+ }
+ } else if (tagName.equals("pastSigs")) {
+ if (flags == null) {
+ // we haven't encountered pastSigs yet, go ahead
+ String countStr = parser.getAttributeValue(null, "count");
+ if (countStr == null) {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Error in package manager settings: <pastSigs> has"
+ + " no count at " + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ try {
+ final int pastSigsCount = Integer.parseInt(countStr);
+ Signature[] pastSignatures = new Signature[pastSigsCount];
+ int[] pastSignaturesFlags = new int[pastSigsCount];
+ int pastSigsPos = readCertsListXml(parser, readSignatures, pastSignatures,
+ pastSignaturesFlags, builder);
+ builder = builder
+ .setPastSigningCertificates(pastSignatures)
+ .setPastSigningCertificatesFlags(pastSignaturesFlags);
+
+ if (pastSigsPos < pastSigsCount) {
+ // Should never happen -- there is an error in the written
+ // settings -- but if it does we don't want to generate
+ // a bad array.
+ Signature[] newSigs = new Signature[pastSigsPos];
+ System.arraycopy(pastSignatures, 0, newSigs, 0, pastSigsPos);
+ int[] newFlags = new int[pastSigsPos];
+ System.arraycopy(pastSignaturesFlags, 0, newFlags, 0, pastSigsPos);
+ builder = builder
+ .setPastSigningCertificates(newSigs)
+ .setPastSigningCertificatesFlags(newFlags);
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Error in package manager settings: <pastSigs> count does not "
+ + "match number of <cert> entries "
+ + parser.getPositionDescription());
+ }
+ } catch (NumberFormatException e) {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Error in package manager settings: <pastSigs> "
+ + "count " + countStr + " is not a number at "
+ + parser.getPositionDescription());
+ }
+ } else {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "<pastSigs> encountered multiple times under the same <sigs> at "
+ + parser.getPositionDescription());
}
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
- "Unknown element under <cert>: "
- + parser.getName());
+ "Unknown element under <sigs>: "
+ + parser.getName());
}
XmlUtils.skipCurrentTag(parser);
}
-
- if (pos < count) {
- // Should never happen -- there is an error in the written
- // settings -- but if it does we don't want to generate
- // a bad array.
- Signature[] newSigs = new Signature[pos];
- System.arraycopy(mSignatures, 0, newSigs, 0, pos);
- mSignatures = newSigs;
- }
- }
-
- void assignSignatures(PackageParser.SigningDetails signingDetails) {
- mSignatureSchemeVersion = signingDetails.signatureSchemeVersion;
- if (!signingDetails.hasSignatures()) {
- mSignatures = null;
- return;
- }
- mSignatures = new Signature[signingDetails.signatures.length];
- for (int i=0; i<signingDetails.signatures.length; i++) {
- mSignatures[i] = signingDetails.signatures[i];
- }
+ return pos;
}
@Override
@@ -206,16 +308,26 @@
buf.append("PackageSignatures{");
buf.append(Integer.toHexString(System.identityHashCode(this)));
buf.append(" version:");
- buf.append(mSignatureSchemeVersion);
+ buf.append(mSigningDetails.signatureSchemeVersion);
buf.append(", signatures:[");
- if (mSignatures != null) {
- for (int i=0; i<mSignatures.length; i++) {
+ if (mSigningDetails.signatures != null) {
+ for (int i = 0; i < mSigningDetails.signatures.length; i++) {
if (i > 0) buf.append(", ");
buf.append(Integer.toHexString(
- mSignatures[i].hashCode()));
+ mSigningDetails.signatures[i].hashCode()));
}
}
buf.append("]}");
+ buf.append(", past signatures:[");
+ if (mSigningDetails.pastSigningCertificates != null) {
+ for (int i = 0; i < mSigningDetails.pastSigningCertificates.length; i++) {
+ if (i > 0) buf.append(", ");
+ buf.append(Integer.toHexString(
+ mSigningDetails.pastSigningCertificates[i].hashCode()));
+ buf.append(" flags: ");
+ buf.append(Integer.toHexString(mSigningDetails.pastSigningCertificatesFlags[i]));
+ }
+ }
return buf.toString();
}
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index ecbc452..8ce412e 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -920,13 +920,13 @@
// by that time.
void insertPackageSettingLPw(PackageSetting p, PackageParser.Package pkg) {
// Update signatures if needed.
- if (p.signatures.mSignatures == null) {
- p.signatures.assignSignatures(pkg.mSigningDetails);
+ if (p.signatures.mSigningDetails.signatures == null) {
+ p.signatures.mSigningDetails = pkg.mSigningDetails;
}
// If this app defines a shared user id initialize
// the shared user signatures as well.
- if (p.sharedUser != null && p.sharedUser.signatures.mSignatures == null) {
- p.sharedUser.signatures.assignSignatures(pkg.mSigningDetails);
+ if (p.sharedUser != null && p.sharedUser.signatures.mSigningDetails.signatures == null) {
+ p.sharedUser.signatures.mSigningDetails = pkg.mSigningDetails;
}
addPackageSettingLPw(p, p.sharedUser);
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index cc07d82..a42fcbd 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -561,6 +561,25 @@
}
}
break;
+ case UserManager.DISALLOW_AMBIENT_DISPLAY:
+ if (newValue) {
+ android.provider.Settings.Secure.putString(
+ context.getContentResolver(),
+ Settings.Secure.DOZE_ENABLED, "0");
+ android.provider.Settings.Secure.putString(
+ context.getContentResolver(),
+ Settings.Secure.DOZE_ALWAYS_ON, "0");
+ android.provider.Settings.Secure.putString(
+ context.getContentResolver(),
+ Settings.Secure.DOZE_PULSE_ON_PICK_UP, "0");
+ android.provider.Settings.Secure.putString(
+ context.getContentResolver(),
+ Settings.Secure.DOZE_PULSE_ON_LONG_PRESS, "0");
+ android.provider.Settings.Secure.putString(
+ context.getContentResolver(),
+ Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP, "0");
+ }
+ break;
}
} finally {
Binder.restoreCallingIdentity(id);
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 2dbb34d..92d159b 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -17,9 +17,13 @@
package com.android.server.pm.dex;
import android.Manifest;
+import android.annotation.UserIdInt;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
import android.content.pm.dex.ArtManager;
+import android.content.pm.dex.DexMetadataHelper;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
@@ -29,10 +33,12 @@
import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.util.ArrayMap;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
@@ -230,4 +236,52 @@
// Should not happen.
}
}
+
+ /**
+ * Prepare the application profiles.
+ * For all code paths:
+ * - create the current primary profile to save time at app startup time.
+ * - copy the profiles from the associated dex metadata file to the reference profile.
+ */
+ public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user) {
+ final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
+ try {
+ ArrayMap<String, String> codePathsProfileNames = getPackageProfileNames(pkg);
+ for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) {
+ String codePath = codePathsProfileNames.keyAt(i);
+ String profileName = codePathsProfileNames.valueAt(i);
+ File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath));
+ String dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath();
+ synchronized (mInstaller) {
+ boolean result = mInstaller.prepareAppProfile(pkg.packageName, user, appId,
+ profileName, codePath, dexMetadataPath);
+ if (!result) {
+ Slog.e(TAG, "Failed to prepare profile for " +
+ pkg.packageName + ":" + codePath);
+ }
+ }
+ }
+ } catch (InstallerException e) {
+ Slog.e(TAG, "Failed to prepare profile for " + pkg.packageName, e);
+ }
+ }
+
+ /**
+ * Build the profiles names for all the package code paths (excluding resource only paths).
+ * Return the map [code path -> profile name].
+ */
+ private ArrayMap<String, String> getPackageProfileNames(PackageParser.Package pkg) {
+ ArrayMap<String, String> result = new ArrayMap<>();
+ if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
+ result.put(pkg.baseCodePath, ArtManager.getProfileName(null));
+ }
+ if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
+ for (int i = 0; i < pkg.splitCodePaths.length; i++) {
+ if ((pkg.splitFlags[i] & ApplicationInfo.FLAG_HAS_CODE) != 0) {
+ result.put(pkg.splitCodePaths[i], ArtManager.getProfileName(pkg.splitNames[i]));
+ }
+ }
+ }
+ return result;
+ }
}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 2a153ec..a536270 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -19,15 +19,6 @@
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
-
-import com.android.internal.app.IAppOpsService;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.server.EventLogTags;
-import com.android.server.LocalServices;
-import com.android.server.policy.WindowManagerPolicy;
-
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -54,6 +45,15 @@
import android.util.Slog;
import android.view.inputmethod.InputMethodManagerInternal;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.server.EventLogTags;
+import com.android.server.LocalServices;
+import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.policy.WindowManagerPolicy;
+
/**
* Sends broadcasts about important power state changes.
* <p>
@@ -96,6 +96,7 @@
private final ActivityManagerInternal mActivityManagerInternal;
private final InputManagerInternal mInputManagerInternal;
private final InputMethodManagerInternal mInputMethodManagerInternal;
+ private final StatusBarManagerInternal mStatusBarManagerInternal;
private final TrustManager mTrustManager;
private final NotifierHandler mHandler;
@@ -142,6 +143,7 @@
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
+ mStatusBarManagerInternal = LocalServices.getService(StatusBarManagerInternal.class);
mTrustManager = mContext.getSystemService(TrustManager.class);
mHandler = new NotifierHandler(looper);
@@ -545,9 +547,19 @@
}
/**
- * Called when wireless charging has started so as to provide user feedback.
+ * Called when profile screen lock timeout has expired.
*/
- public void onWirelessChargingStarted() {
+ public void onProfileTimeout(@UserIdInt int userId) {
+ final Message msg = mHandler.obtainMessage(MSG_PROFILE_TIMED_OUT);
+ msg.setAsynchronous(true);
+ msg.arg1 = userId;
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Called when wireless charging has started so as to provide user feedback (sound and visual).
+ */
+ public void onWirelessChargingStarted(int batteryLevel) {
if (DEBUG) {
Slog.d(TAG, "onWirelessChargingStarted");
}
@@ -555,16 +567,7 @@
mSuspendBlocker.acquire();
Message msg = mHandler.obtainMessage(MSG_WIRELESS_CHARGING_STARTED);
msg.setAsynchronous(true);
- mHandler.sendMessage(msg);
- }
-
- /**
- * Called when profile screen lock timeout has expired.
- */
- public void onProfileTimeout(@UserIdInt int userId) {
- final Message msg = mHandler.obtainMessage(MSG_PROFILE_TIMED_OUT);
- msg.setAsynchronous(true);
- msg.arg1 = userId;
+ msg.arg1 = batteryLevel;
mHandler.sendMessage(msg);
}
@@ -715,7 +718,11 @@
}
}
}
+ }
+ private void showWirelessChargingStarted(int batteryLevel) {
+ playWirelessChargingStartedSound();
+ mStatusBarManagerInternal.showChargingAnimation(batteryLevel);
mSuspendBlocker.release();
}
@@ -738,7 +745,7 @@
sendNextBroadcast();
break;
case MSG_WIRELESS_CHARGING_STARTED:
- playWirelessChargingStartedSound();
+ showWirelessChargingStarted(msg.arg1);
break;
case MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED:
sendBrightnessBoostChangedBroadcast();
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 7273f62..fbdedce 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -16,6 +16,11 @@
package com.android.server.power;
+import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
+import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
+
import android.annotation.IntDef;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -103,11 +108,6 @@
import java.util.ArrayList;
import java.util.Arrays;
-import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
-import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
-import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
-import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
-
/**
* The power manager service is responsible for coordinating power management
* functions on the device.
@@ -119,6 +119,9 @@
private static final boolean DEBUG = false;
private static final boolean DEBUG_SPEW = DEBUG && true;
+ // if DEBUG_WIRELESS=true, plays wireless charging animation w/ sound on every plug + unplug
+ private static final boolean DEBUG_WIRELESS = false;
+
// Message: Sent when a user activity timeout occurs to update the power state.
private static final int MSG_USER_ACTIVITY_TIMEOUT = 1;
// Message: Sent when the device enters or exits a dreaming or dozing state.
@@ -1794,8 +1797,8 @@
// Tell the notifier whether wireless charging has started so that
// it can provide feedback to the user.
- if (dockedOnWirelessCharger) {
- mNotifier.onWirelessChargingStarted();
+ if (dockedOnWirelessCharger || DEBUG_WIRELESS) {
+ mNotifier.onWirelessChargingStarted(mBatteryLevel);
}
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 95006ff..3ab771b 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -37,6 +37,8 @@
void dismissKeyboardShortcutsMenu();
void toggleKeyboardShortcutsMenu(int deviceId);
+ void showChargingAnimation(int batteryLevel);
+
/**
* Show picture-in-picture menu.
*/
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index c58c208..adb368b 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -23,6 +23,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Rect;
+import android.hardware.fingerprint.IFingerprintDialogReceiver;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -318,6 +319,16 @@
}
@Override
+ public void showChargingAnimation(int batteryLevel) {
+ if (mBar != null) {
+ try {
+ mBar.showChargingAnimation(batteryLevel);
+ } catch (RemoteException ex){
+ }
+ }
+ }
+
+ @Override
public void showPictureInPictureMenu() {
if (mBar != null) {
try {
@@ -514,6 +525,56 @@
}
@Override
+ public void showFingerprintDialog(Bundle bundle, IFingerprintDialogReceiver receiver) {
+ if (mBar != null) {
+ try {
+ mBar.showFingerprintDialog(bundle, receiver);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
+ @Override
+ public void onFingerprintAuthenticated() {
+ if (mBar != null) {
+ try {
+ mBar.onFingerprintAuthenticated();
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
+ @Override
+ public void onFingerprintHelp(String message) {
+ if (mBar != null) {
+ try {
+ mBar.onFingerprintHelp(message);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
+ @Override
+ public void onFingerprintError(String error) {
+ if (mBar != null) {
+ try {
+ mBar.onFingerprintError(error);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
+ @Override
+ public void hideFingerprintDialog() {
+ if (mBar != null) {
+ try {
+ mBar.hideFingerprintDialog();
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
+ @Override
public void disable(int what, IBinder token, String pkg) {
disableForUser(what, token, pkg, mCurrentUserId);
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 0bc58e0..7540e26 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -104,6 +104,8 @@
"libhwbinder",
"libutils",
"libhwui",
+ "libbpf",
+ "libnetdutils",
"android.hardware.audio.common@2.0",
"android.hardware.broadcastradio@1.0",
"android.hardware.broadcastradio@1.1",
diff --git a/services/core/jni/BroadcastRadio/convert.cpp b/services/core/jni/BroadcastRadio/convert.cpp
index be1ad72..8c38e0a 100644
--- a/services/core/jni/BroadcastRadio/convert.cpp
+++ b/services/core/jni/BroadcastRadio/convert.cpp
@@ -49,6 +49,12 @@
using V1_1::ProgramSelector;
using V1_1::VendorKeyValue;
+// HAL 2.0 flags that have equivalent HAL 1.x fields
+enum class ProgramInfoFlagsExt {
+ TUNED = 1 << 4,
+ STEREO = 1 << 5,
+};
+
static JavaRef<jobject> BandDescriptorFromHal(JNIEnv *env, const RegionalBandConfig &config);
static JavaRef<jobject> BandDescriptorFromHal(JNIEnv *env, const V1_0::BandConfig &config, Region region);
@@ -614,9 +620,14 @@
auto jVendorInfo = info11 ? VendorInfoFromHal(env, info11->vendorInfo) : nullptr;
auto jSelector = ProgramSelectorFromHal(env, selector);
+ jint flags = info11 ? info11->flags : 0;
+ if (info10.tuned) flags |= static_cast<jint>(ProgramInfoFlagsExt::TUNED);
+ if (info10.stereo) flags |= static_cast<jint>(ProgramInfoFlagsExt::STEREO);
+ // info10.digital is dropped, because it has no equivalent in the new APIs
+
return make_javaref(env, env->NewObject(gjni.ProgramInfo.clazz, gjni.ProgramInfo.cstor,
- jSelector.get(), info10.tuned, info10.stereo, info10.digital, info10.signalStrength,
- jMetadata.get(), info11 ? info11->flags : 0, jVendorInfo.get()));
+ jSelector.get(), nullptr, nullptr, nullptr, flags, info10.signalStrength,
+ jMetadata.get(), jVendorInfo.get()));
}
JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info, V1_0::Band band) {
@@ -705,9 +716,15 @@
auto programInfoClass = FindClassOrDie(env, "android/hardware/radio/RadioManager$ProgramInfo");
gjni.ProgramInfo.clazz = MakeGlobalRefOrDie(env, programInfoClass);
- gjni.ProgramInfo.cstor = GetMethodIDOrDie(env, programInfoClass, "<init>",
- "(Landroid/hardware/radio/ProgramSelector;ZZZILandroid/hardware/radio/RadioMetadata;I"
- "Ljava/util/Map;)V");
+ gjni.ProgramInfo.cstor = GetMethodIDOrDie(env, programInfoClass, "<init>", "("
+ "Landroid/hardware/radio/ProgramSelector;"
+ "Landroid/hardware/radio/ProgramSelector$Identifier;"
+ "Landroid/hardware/radio/ProgramSelector$Identifier;"
+ "Ljava/util/Collection;" // relatedContent
+ "II" // flags, signalQuality
+ "Landroid/hardware/radio/RadioMetadata;"
+ "Ljava/util/Map;" // vendorInfo
+ ")V");
auto programSelectorClass = FindClassOrDie(env, "android/hardware/radio/ProgramSelector");
gjni.ProgramSelector.clazz = MakeGlobalRefOrDie(env, programSelectorClass);
diff --git a/services/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
index 8de24e5..3302dea 100644
--- a/services/core/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
@@ -29,6 +29,15 @@
#include <utils/misc.h>
#include <utils/Log.h>
+#include "android-base/unique_fd.h"
+#include "bpf/BpfNetworkStats.h"
+#include "bpf/BpfUtils.h"
+
+using android::bpf::Stats;
+using android::bpf::hasBpfSupport;
+using android::bpf::bpfGetUidStats;
+using android::bpf::bpfGetIfaceStats;
+
namespace android {
static const char* QTAGUID_IFACE_STATS = "/proc/net/xt_qtaguid/iface_stat_fmt";
@@ -46,15 +55,6 @@
TCP_TX_PACKETS = 5
};
-struct Stats {
- uint64_t rxBytes;
- uint64_t rxPackets;
- uint64_t txBytes;
- uint64_t txPackets;
- uint64_t tcpRxPackets;
- uint64_t tcpTxPackets;
-};
-
static uint64_t getStatsType(struct Stats* stats, StatsType type) {
switch (type) {
case RX_BYTES:
@@ -150,9 +150,18 @@
return 0;
}
-static jlong getTotalStat(JNIEnv* env, jclass clazz, jint type) {
+static jlong getTotalStat(JNIEnv* env, jclass clazz, jint type, jboolean useBpfStats) {
struct Stats stats;
memset(&stats, 0, sizeof(Stats));
+
+ if (useBpfStats) {
+ if (bpfGetIfaceStats(NULL, &stats) == 0) {
+ return getStatsType(&stats, (StatsType) type);
+ } else {
+ return UNKNOWN;
+ }
+ }
+
if (parseIfaceStats(NULL, &stats) == 0) {
return getStatsType(&stats, (StatsType) type);
} else {
@@ -160,7 +169,8 @@
}
}
-static jlong getIfaceStat(JNIEnv* env, jclass clazz, jstring iface, jint type) {
+static jlong getIfaceStat(JNIEnv* env, jclass clazz, jstring iface, jint type,
+ jboolean useBpfStats) {
ScopedUtfChars iface8(env, iface);
if (iface8.c_str() == NULL) {
return UNKNOWN;
@@ -168,6 +178,15 @@
struct Stats stats;
memset(&stats, 0, sizeof(Stats));
+
+ if (useBpfStats) {
+ if (bpfGetIfaceStats(iface8.c_str(), &stats) == 0) {
+ return getStatsType(&stats, (StatsType) type);
+ } else {
+ return UNKNOWN;
+ }
+ }
+
if (parseIfaceStats(iface8.c_str(), &stats) == 0) {
return getStatsType(&stats, (StatsType) type);
} else {
@@ -175,9 +194,18 @@
}
}
-static jlong getUidStat(JNIEnv* env, jclass clazz, jint uid, jint type) {
+static jlong getUidStat(JNIEnv* env, jclass clazz, jint uid, jint type, jboolean useBpfStats) {
struct Stats stats;
memset(&stats, 0, sizeof(Stats));
+
+ if (useBpfStats) {
+ if (bpfGetUidStats(uid, &stats) == 0) {
+ return getStatsType(&stats, (StatsType) type);
+ } else {
+ return UNKNOWN;
+ }
+ }
+
if (parseUidStats(uid, &stats) == 0) {
return getStatsType(&stats, (StatsType) type);
} else {
@@ -186,9 +214,9 @@
}
static const JNINativeMethod gMethods[] = {
- {"nativeGetTotalStat", "(I)J", (void*) getTotalStat},
- {"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*) getIfaceStat},
- {"nativeGetUidStat", "(II)J", (void*) getUidStat},
+ {"nativeGetTotalStat", "(IZ)J", (void*) getTotalStat},
+ {"nativeGetIfaceStat", "(Ljava/lang/String;IZ)J", (void*) getIfaceStat},
+ {"nativeGetUidStat", "(IIZ)J", (void*) getUidStat},
};
int register_android_server_net_NetworkStatsService(JNIEnv* env) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 7a0b1bf..a8e8237 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -22,10 +22,13 @@
import android.os.UserHandle;
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keystore.ParcelableKeyGenParameterSpec;
+import android.telephony.data.ApnSetting;
import com.android.internal.R;
import com.android.server.SystemService;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -67,6 +70,10 @@
public void transferOwnership(ComponentName admin, ComponentName target, PersistableBundle bundle) {}
+ public PersistableBundle getTransferOwnershipBundle() {
+ return null;
+ }
+
public boolean generateKeyPair(ComponentName who, String callerPackage, String algorithm,
ParcelableKeyGenParameterSpec keySpec, int idAttestationFlags,
KeymasterCertificateChain attestationChain) {
@@ -132,7 +139,40 @@
}
@Override
- public CharSequence getPrintingDisabledReason() {
- return null;
+ public List<String> setMeteredDataDisabled(ComponentName admin, List<String> packageNames) {
+ return packageNames;
+ }
+
+ @Override
+ public List<String> getMeteredDataDisabled(ComponentName admin) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public int addOverrideApn(ComponentName admin, ApnSetting apnSetting) {
+ return -1;
+ }
+
+ @Override
+ public boolean updateOverrideApn(ComponentName admin, int apnId, ApnSetting apnSetting) {
+ return false;
+ }
+
+ @Override
+ public boolean removeOverrideApn(ComponentName admin, int apnId) {
+ return false;
+ }
+
+ @Override
+ public List<ApnSetting> getOverrideApns(ComponentName admin) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void setOverrideApnsEnabled(ComponentName admin, boolean enabled) {}
+
+ @Override
+ public boolean isOverrideApnEnabled(ComponentName admin) {
+ return false;
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6bee9d6..99712a5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -20,7 +20,7 @@
import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.USER_OP_SUCCESS;
-import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE;
+import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY;
import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED;
@@ -58,8 +58,22 @@
import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
+
+import static android.provider.Telephony.Carriers.DPC_URI;
+import static android.provider.Telephony.Carriers.ENFORCE_KEY;
+import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent
+ .PROVISIONING_ENTRY_POINT_ADB;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
+
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
+
+import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER;
+import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER;
+
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.TEXT;
@@ -99,6 +113,7 @@
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -108,8 +123,8 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
@@ -118,6 +133,7 @@
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.media.AudioManager;
@@ -161,16 +177,18 @@
import android.security.IKeyChainService;
import android.security.KeyChain;
import android.security.KeyChain.KeyChainConnection;
+import android.security.KeyStore;
import android.security.keymaster.KeymasterCertificateChain;
+import android.security.keystore.AttestationUtils;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.ParcelableKeyGenParameterSpec;
-import android.security.KeyStore;
-import android.security.keystore.AttestationUtils;
import android.service.persistentdata.PersistentDataBlockManager;
import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.AtomicFile;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -217,7 +235,6 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
-import java.lang.IllegalStateException;
import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
@@ -228,9 +245,10 @@
import java.util.Date;
import java.util.HashMap;
import java.util.List;
-import java.util.Map.Entry;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -247,6 +265,9 @@
private static final String DEVICE_POLICIES_XML = "device_policies.xml";
+ private static final String TRANSFER_OWNERSHIP_PARAMETERS_XML =
+ "transfer-ownership-parameters.xml";
+
private static final String TAG_ACCEPTED_CA_CERTIFICATES = "accepted-ca-certificate";
private static final String TAG_LOCK_TASK_COMPONENTS = "lock-task-component";
@@ -451,6 +472,9 @@
private SetupContentObserver mSetupContentObserver;
+ @VisibleForTesting
+ final TransferOwnershipMetadataManager mTransferOwnershipMetadataManager;
+
private final Runnable mRemoteBugreportTimeoutRunnable = new Runnable() {
@Override
public void run() {
@@ -806,6 +830,8 @@
private static final String TAG_MANDATORY_BACKUP_TRANSPORT = "mandatory_backup_transport";
private static final String TAG_START_USER_SESSION_MESSAGE = "start_user_session_message";
private static final String TAG_END_USER_SESSION_MESSAGE = "end_user_session_message";
+ private static final String TAG_METERED_DATA_DISABLED_PACKAGES
+ = "metered_data_disabled_packages";
DeviceAdminInfo info;
@@ -872,6 +898,9 @@
}
}
+ // The list of packages which are not allowed to use metered data.
+ List<String> meteredDisabledPackages;
+
final Set<String> accountTypesWithManagementDisabled = new ArraySet<>();
// The list of permitted accessibility services package namesas set by a profile
@@ -1153,6 +1182,7 @@
writePackageListToXml(out, TAG_PERMITTED_NOTIFICATION_LISTENERS,
permittedNotificationListeners);
writePackageListToXml(out, TAG_KEEP_UNINSTALLED_PACKAGES, keepUninstalledPackages);
+ writePackageListToXml(out, TAG_METERED_DATA_DISABLED_PACKAGES, meteredDisabledPackages);
if (hasUserRestrictions()) {
UserRestrictionsUtils.writeRestrictions(
out, userRestrictions, TAG_USER_RESTRICTIONS);
@@ -1349,6 +1379,8 @@
permittedNotificationListeners = readPackageList(parser, tag);
} else if (TAG_KEEP_UNINSTALLED_PACKAGES.equals(tag)) {
keepUninstalledPackages = readPackageList(parser, tag);
+ } else if (TAG_METERED_DATA_DISABLED_PACKAGES.equals(tag)) {
+ meteredDisabledPackages = readPackageList(parser, tag);
} else if (TAG_USER_RESTRICTIONS.equals(tag)) {
userRestrictions = UserRestrictionsUtils.readRestrictions(parser);
} else if (TAG_DEFAULT_ENABLED_USER_RESTRICTIONS.equals(tag)) {
@@ -1647,6 +1679,7 @@
policy.mAdminList.remove(i);
policy.mAdminMap.remove(aa.info.getComponent());
pushActiveAdminPackagesLocked(userHandle);
+ pushMeteredDisabledPackagesLocked(userHandle);
}
}
} catch (RemoteException re) {
@@ -2005,6 +2038,10 @@
void postOnSystemServerInitThreadPool(Runnable runnable) {
SystemServerInitThreadPool.get().submit(runnable, LOG_TAG);
}
+
+ public TransferOwnershipMetadataManager newTransferOwnershipMetadataManager() {
+ return new TransferOwnershipMetadataManager();
+ }
}
/**
@@ -2049,6 +2086,8 @@
mOverlayPackagesProvider = new OverlayPackagesProvider(mContext);
+ mTransferOwnershipMetadataManager = mInjector.newTransferOwnershipMetadataManager();
+
if (!mHasFeature) {
// Skip the rest of the initialization
return;
@@ -3290,6 +3329,29 @@
activityManagerInternal.setSwitchingToSystemUserMessage(
deviceOwner.endUserSessionMessage);
}
+
+ revertTransferOwnershipIfNecessaryLocked();
+ }
+ }
+
+ private void revertTransferOwnershipIfNecessaryLocked() {
+ if (!mTransferOwnershipMetadataManager.metadataFileExists()) {
+ return;
+ }
+ Slog.e(LOG_TAG, "Owner transfer metadata file exists! Reverting transfer.");
+ final TransferOwnershipMetadataManager.Metadata metadata =
+ mTransferOwnershipMetadataManager.loadMetadataFile();
+ // Revert transfer
+ if (metadata.adminType.equals(ADMIN_TYPE_PROFILE_OWNER)) {
+ transferProfileOwnershipLocked(metadata.targetComponent, metadata.sourceComponent,
+ metadata.userId);
+ deleteTransferOwnershipMetadataFileLocked();
+ deleteTransferOwnershipBundleLocked(metadata.userId);
+ } else if (metadata.adminType.equals(ADMIN_TYPE_DEVICE_OWNER)) {
+ transferDeviceOwnershipLocked(metadata.targetComponent, metadata.sourceComponent,
+ metadata.userId);
+ deleteTransferOwnershipMetadataFileLocked();
+ deleteTransferOwnershipBundleLocked(metadata.userId);
}
}
@@ -3502,6 +3564,7 @@
mInjector.postOnSystemServerInitThreadPool(() -> {
pushActiveAdminPackages();
mUsageStatsManagerInternal.onAdminDataAvailable();
+ pushAllMeteredRestrictedPackages();
mInjector.getNetworkPolicyManagerInternal().onAdminDataAvailable();
});
}
@@ -3517,6 +3580,17 @@
}
}
+ private void pushAllMeteredRestrictedPackages() {
+ synchronized (this) {
+ final List<UserInfo> users = mUserManager.getUsers();
+ for (int i = users.size() - 1; i >= 0; --i) {
+ final int userId = users.get(i).id;
+ mInjector.getNetworkPolicyManagerInternal().setMeteredRestrictedPackagesAsync(
+ getMeteredDisabledPackagesLocked(userId), userId);
+ }
+ }
+ }
+
private void pushActiveAdminPackagesLocked(int userId) {
mUsageStatsManagerInternal.setActiveAdminApps(
getActiveAdminPackagesLocked(userId), userId);
@@ -3538,6 +3612,11 @@
private void transferActiveAdminUncheckedLocked(ComponentName incomingReceiver,
ComponentName outgoingReceiver, int userHandle) {
final DevicePolicyData policy = getUserData(userHandle);
+ if (!policy.mAdminMap.containsKey(outgoingReceiver)
+ && policy.mAdminMap.containsKey(incomingReceiver)) {
+ // Nothing to transfer - the incoming receiver is already the active admin.
+ return;
+ }
final DeviceAdminInfo incomingDeviceInfo = findAdmin(incomingReceiver, userHandle,
/* throwForMissingPermission= */ true);
final ActiveAdmin adminToTransfer = policy.mAdminMap.get(outgoingReceiver);
@@ -3551,7 +3630,6 @@
}
saveSettingsLocked(userHandle);
- //TODO: Make sure we revert back when we detect a failure.
sendAdminCommandLocked(adminToTransfer, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED,
null, null);
}
@@ -7262,6 +7340,15 @@
}
}
+ private void clearOverrideApnUnchecked() {
+ // Disable Override APNs and remove them from database.
+ setOverrideApnsEnabledUnchecked(false);
+ final List<ApnSetting> apns = getOverrideApnsUnchecked();
+ for (int i = 0; i < apns.size(); i ++) {
+ removeOverrideApnUnchecked(apns.get(i).getId());
+ }
+ }
+
private void clearDeviceOwnerLocked(ActiveAdmin admin, int userId) {
mDeviceAdminServiceController.stopServiceForOwner(userId, "clear-device-owner");
@@ -7282,6 +7369,7 @@
systemPolicyData.mLastNetworkLogsRetrievalTime = -1;
saveSettingsLocked(UserHandle.USER_SYSTEM);
clearUserPoliciesLocked(userId);
+ clearOverrideApnUnchecked();
mOwners.clearDeviceOwner();
mOwners.writeDeviceOwner();
@@ -7291,6 +7379,7 @@
mInjector.securityLogSetLoggingEnabledProperty(false);
mSecurityLogMonitor.stop();
setNetworkLoggingActiveInternal(false);
+ deleteTransferOwnershipBundleLocked(userId);
try {
if (mInjector.getIBackupManager() != null) {
@@ -7393,6 +7482,7 @@
clearUserPoliciesLocked(userId);
mOwners.removeProfileOwner(userId);
mOwners.writeProfileOwner(userId);
+ deleteTransferOwnershipBundleLocked(userId);
}
@Override
@@ -7862,6 +7952,37 @@
enforceSystemUserOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
}
+ private boolean canUserUseLockTaskLocked(int userId) {
+ if (isUserAffiliatedWithDeviceLocked(userId)) {
+ return true;
+ }
+
+ // Unaffiliated profile owners are not allowed to use lock when there is a device owner.
+ if (mOwners.hasDeviceOwner()) {
+ return false;
+ }
+
+ final ComponentName profileOwner = getProfileOwner(userId);
+ if (profileOwner == null) {
+ return false;
+ }
+
+ // Managed profiles are not allowed to use lock task
+ if (isManagedProfile(userId)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void enforceCanCallLockTaskLocked(ComponentName who) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ final int userId = mInjector.userHandleGetCallingUserId();
+ if (!canUserUseLockTaskLocked(userId)) {
+ throw new SecurityException("User " + userId + " is not allowed to use lock task");
+ }
+ }
+
private void ensureCallerPackage(@Nullable String packageName) {
if (packageName == null) {
Preconditions.checkState(isCallerWithSystemUid(),
@@ -9568,14 +9689,9 @@
Preconditions.checkNotNull(packages, "packages is null");
synchronized (this) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ enforceCanCallLockTaskLocked(who);
final int userHandle = mInjector.userHandleGetCallingUserId();
- if (isUserAffiliatedWithDeviceLocked(userHandle)) {
- setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
- } else {
- throw new SecurityException("Admin " + who +
- " is neither the device owner or affiliated user's profile owner.");
- }
+ setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
}
}
@@ -9594,12 +9710,7 @@
final int userHandle = mInjector.binderGetCallingUserHandle().getIdentifier();
synchronized (this) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- if (!isUserAffiliatedWithDeviceLocked(userHandle)) {
- throw new SecurityException("Admin " + who +
- " is neither the device owner or affiliated user's profile owner.");
- }
-
+ enforceCanCallLockTaskLocked(who);
final List<String> packages = getUserData(userHandle).mLockTaskPackages;
return packages.toArray(new String[packages.size()]);
}
@@ -9618,11 +9729,7 @@
Preconditions.checkNotNull(who, "ComponentName is null");
final int userHandle = mInjector.userHandleGetCallingUserId();
synchronized (this) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- if (!isUserAffiliatedWithDeviceLocked(userHandle)) {
- throw new SecurityException("Admin " + who +
- " is neither the device owner or affiliated user's profile owner.");
- }
+ enforceCanCallLockTaskLocked(who);
setLockTaskFeaturesLocked(userHandle, flags);
}
}
@@ -9639,11 +9746,7 @@
Preconditions.checkNotNull(who, "ComponentName is null");
final int userHandle = mInjector.userHandleGetCallingUserId();
synchronized (this) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- if (!isUserAffiliatedWithDeviceLocked(userHandle)) {
- throw new SecurityException("Admin " + who +
- " is neither the device owner or affiliated user's profile owner.");
- }
+ enforceCanCallLockTaskLocked(who);
return getUserData(userHandle).mLockTaskFeatures;
}
}
@@ -9654,7 +9757,7 @@
final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
for (int i = userInfos.size() - 1; i >= 0; i--) {
int userId = userInfos.get(i).id;
- if (isUserAffiliatedWithDeviceLocked(userId)) {
+ if (canUserUseLockTaskLocked(userId)) {
continue;
}
@@ -10211,6 +10314,45 @@
public boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId) {
return DevicePolicyManagerService.this.canUserHaveUntrustedCredentialReset(userId);
}
+
+ @Override
+ public CharSequence getPrintingDisabledReasonForUser(@UserIdInt int userId) {
+ synchronized (DevicePolicyManagerService.this) {
+ DevicePolicyData policy = getUserData(userId);
+ if (policy.mPrintingEnabled) {
+ Log.e(LOG_TAG, "printing is enabled");
+ return null;
+ }
+ String ownerPackage = mOwners.getProfileOwnerPackage(userId);
+ if (ownerPackage == null) {
+ ownerPackage = mOwners.getDeviceOwnerPackageName();
+ }
+ PackageManager pm = mInjector.getPackageManager();
+ PackageInfo packageInfo;
+ try {
+ packageInfo = pm.getPackageInfo(ownerPackage, 0);
+ } catch (NameNotFoundException e) {
+ Log.e(LOG_TAG, "getPackageInfo error", e);
+ return null;
+ }
+ if (packageInfo == null) {
+ Log.e(LOG_TAG, "packageInfo is inexplicably null");
+ return null;
+ }
+ ApplicationInfo appInfo = packageInfo.applicationInfo;
+ if (appInfo == null) {
+ Log.e(LOG_TAG, "appInfo is inexplicably null");
+ return null;
+ }
+ CharSequence appLabel = pm.getApplicationLabel(appInfo);
+ if (appLabel == null) {
+ Log.e(LOG_TAG, "appLabel is inexplicably null");
+ return null;
+ }
+ return ((Context) ActivityThread.currentActivityThread().getSystemUiContext())
+ .getResources().getString(R.string.printing_disabled_by, appLabel);
+ }
+ }
}
private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
@@ -11031,6 +11173,93 @@
}
@Override
+ public List<String> setMeteredDataDisabled(ComponentName who, List<String> packageNames) {
+ Preconditions.checkNotNull(who);
+ Preconditions.checkNotNull(packageNames);
+
+ if (!mHasFeature) {
+ return packageNames;
+ }
+ synchronized (this) {
+ final ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
+ final long identity = mInjector.binderClearCallingIdentity();
+ try {
+ final List<String> excludedPkgs
+ = removeInvalidPkgsForMeteredDataRestriction(callingUserId, packageNames);
+ admin.meteredDisabledPackages = packageNames;
+ pushMeteredDisabledPackagesLocked(callingUserId);
+ saveSettingsLocked(callingUserId);
+ return excludedPkgs;
+ } finally {
+ mInjector.binderRestoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ private List<String> removeInvalidPkgsForMeteredDataRestriction(
+ int userId, List<String> pkgNames) {
+ final Set<String> activeAdmins = getActiveAdminPackagesLocked(userId);
+ final List<String> excludedPkgs = new ArrayList<>();
+ for (int i = pkgNames.size() - 1; i >= 0; --i) {
+ final String pkgName = pkgNames.get(i);
+ // If the package is an active admin, don't restrict it.
+ if (activeAdmins.contains(pkgName)) {
+ excludedPkgs.add(pkgName);
+ continue;
+ }
+ // If the package doesn't exist, don't restrict it.
+ try {
+ if (!mInjector.getIPackageManager().isPackageAvailable(pkgName, userId)) {
+ excludedPkgs.add(pkgName);
+ }
+ } catch (RemoteException e) {
+ // Should not happen
+ }
+ }
+ pkgNames.removeAll(excludedPkgs);
+ return excludedPkgs;
+ }
+
+ @Override
+ public List<String> getMeteredDataDisabled(ComponentName who) {
+ Preconditions.checkNotNull(who);
+
+ if (!mHasFeature) {
+ return new ArrayList<>();
+ }
+ synchronized (this) {
+ final ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ return admin.meteredDisabledPackages == null
+ ? new ArrayList<>() : admin.meteredDisabledPackages;
+ }
+ }
+
+ private void pushMeteredDisabledPackagesLocked(int userId) {
+ mInjector.getNetworkPolicyManagerInternal().setMeteredRestrictedPackages(
+ getMeteredDisabledPackagesLocked(userId), userId);
+ }
+
+ private Set<String> getMeteredDisabledPackagesLocked(int userId) {
+ final DevicePolicyData policy = getUserData(userId);
+ final Set<String> restrictedPkgs = new ArraySet<>();
+ for (int i = policy.mAdminList.size() - 1; i >= 0; --i) {
+ final ActiveAdmin admin = policy.mAdminList.get(i);
+ if (!isActiveAdminWithPolicyForUserLocked(admin,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, userId)) {
+ // Not a profile or device owner, ignore
+ continue;
+ }
+ if (admin.meteredDisabledPackages != null) {
+ restrictedPkgs.addAll(admin.meteredDisabledPackages);
+ }
+ }
+ return restrictedPkgs;
+ }
+
+ @Override
public void setAffiliationIds(ComponentName admin, List<String> ids) {
if (!mHasFeature) {
return;
@@ -11105,10 +11334,12 @@
// of a split user device.
return true;
}
+
final ComponentName profileOwner = getProfileOwner(userId);
if (profileOwner == null) {
return false;
}
+
final Set<String> userAffiliationIds = getUserData(userId).mAffiliationIds;
final Set<String> deviceAffiliationIds =
getUserData(UserHandle.USER_SYSTEM).mAffiliationIds;
@@ -11389,6 +11620,7 @@
resetGlobalProxyLocked(policy);
}
pushActiveAdminPackagesLocked(userHandle);
+ pushMeteredDisabledPackagesLocked(userHandle);
saveSettingsLocked(userHandle);
updateMaximumTimeToLockLocked(userHandle);
policy.mRemovingAdmins.remove(adminReceiver);
@@ -12143,10 +12375,9 @@
mOverlayPackagesProvider.getNonRequiredApps(admin, userId, provisioningAction));
}
- //TODO: Add callback information to the javadoc once it is completed.
- //TODO: Make transferOwnership atomic.
@Override
- public void transferOwnership(ComponentName admin, ComponentName target, PersistableBundle bundle) {
+ public void transferOwnership(@NonNull ComponentName admin, @NonNull ComponentName target,
+ @Nullable PersistableBundle bundle) {
if (!mHasFeature) {
return;
}
@@ -12179,12 +12410,41 @@
final long id = mInjector.binderClearCallingIdentity();
try {
- //STOPSHIP add support for COMP, edge cases when device is rebooted/work mode off
synchronized (this) {
+ /*
+ * We must ensure the whole process is atomic to prevent the device from ending up
+ * in an invalid state (e.g. no active admin). This could happen if the device
+ * is rebooted or work mode is turned off mid-transfer.
+ * In order to guarantee atomicity, we:
+ *
+ * 1. Save an atomic journal file describing the transfer process
+ * 2. Perform the transfer itself
+ * 3. Delete the journal file
+ *
+ * That way if the journal file exists on device boot, we know that the transfer
+ * must be reverted back to the original administrator. This logic is implemented in
+ * revertTransferOwnershipIfNecessaryLocked.
+ * */
+ if (bundle == null) {
+ bundle = new PersistableBundle();
+ }
if (isProfileOwner(admin, callingUserId)) {
- transferProfileOwnerLocked(admin, target, callingUserId, bundle);
+ prepareTransfer(admin, target, bundle, callingUserId,
+ ADMIN_TYPE_PROFILE_OWNER);
+ transferProfileOwnershipLocked(admin, target, callingUserId);
+ sendProfileOwnerCommand(DeviceAdminReceiver.ACTION_TRANSFER_OWNERSHIP_COMPLETE,
+ getTransferOwnershipAdminExtras(bundle), callingUserId);
+ postTransfer(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED, callingUserId);
+ if (isUserAffiliatedWithDeviceLocked(callingUserId)) {
+ notifyAffiliatedProfileTransferOwnershipComplete(callingUserId);
+ }
} else if (isDeviceOwner(admin, callingUserId)) {
- transferDeviceOwnerLocked(admin, target, callingUserId, bundle);
+ prepareTransfer(admin, target, bundle, callingUserId,
+ ADMIN_TYPE_DEVICE_OWNER);
+ transferDeviceOwnershipLocked(admin, target, callingUserId);
+ sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_TRANSFER_OWNERSHIP_COMPLETE,
+ getTransferOwnershipAdminExtras(bundle));
+ postTransfer(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, callingUserId);
}
}
} finally {
@@ -12192,43 +12452,55 @@
}
}
+ private void prepareTransfer(ComponentName admin, ComponentName target,
+ PersistableBundle bundle, int callingUserId, String adminType) {
+ saveTransferOwnershipBundleLocked(bundle, callingUserId);
+ mTransferOwnershipMetadataManager.saveMetadataFile(
+ new TransferOwnershipMetadataManager.Metadata(admin, target,
+ callingUserId, adminType));
+ }
+
+ private void postTransfer(String broadcast, int callingUserId) {
+ deleteTransferOwnershipMetadataFileLocked();
+ sendOwnerChangedBroadcast(broadcast, callingUserId);
+ }
+
+ private void notifyAffiliatedProfileTransferOwnershipComplete(int callingUserId) {
+ final Bundle extras = new Bundle();
+ extras.putParcelable(Intent.EXTRA_USER, UserHandle.of(callingUserId));
+ sendDeviceOwnerCommand(
+ DeviceAdminReceiver.ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE, extras);
+ }
+
/**
* Transfers the profile owner for user with id profileOwnerUserId from admin to target.
*/
- private void transferProfileOwnerLocked(ComponentName admin, ComponentName target,
- int profileOwnerUserId, PersistableBundle bundle) {
+ private void transferProfileOwnershipLocked(ComponentName admin, ComponentName target,
+ int profileOwnerUserId) {
transferActiveAdminUncheckedLocked(target, admin, profileOwnerUserId);
mOwners.transferProfileOwner(target, profileOwnerUserId);
Slog.i(LOG_TAG, "Profile owner set: " + target + " on user " + profileOwnerUserId);
mOwners.writeProfileOwner(profileOwnerUserId);
mDeviceAdminServiceController.startServiceForOwner(
target.getPackageName(), profileOwnerUserId, "transfer-profile-owner");
- sendProfileOwnerCommand(DeviceAdminReceiver.ACTION_TRANSFER_OWNERSHIP_COMPLETE,
- getTransferOwnerAdminExtras(bundle), profileOwnerUserId);
- sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED,
- profileOwnerUserId);
}
/**
* Transfers the device owner for user with id userId from admin to target.
*/
- private void transferDeviceOwnerLocked(ComponentName admin, ComponentName target, int userId,
- PersistableBundle bundle) {
+ private void transferDeviceOwnershipLocked(ComponentName admin, ComponentName target, int userId) {
transferActiveAdminUncheckedLocked(target, admin, userId);
- mOwners.transferDeviceOwner(target);
+ mOwners.transferDeviceOwnership(target);
Slog.i(LOG_TAG, "Device owner set: " + target + " on user " + userId);
mOwners.writeDeviceOwner();
mDeviceAdminServiceController.startServiceForOwner(
target.getPackageName(), userId, "transfer-device-owner");
- sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_TRANSFER_OWNERSHIP_COMPLETE,
- getTransferOwnerAdminExtras(bundle));
- sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId);
}
- private Bundle getTransferOwnerAdminExtras(PersistableBundle bundle) {
+ private Bundle getTransferOwnershipAdminExtras(PersistableBundle bundle) {
Bundle extras = new Bundle();
if (bundle != null) {
- extras.putParcelable(EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE, bundle);
+ extras.putParcelable(EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE, bundle);
}
return extras;
}
@@ -12333,6 +12605,34 @@
}
}
+ private void deleteTransferOwnershipMetadataFileLocked() {
+ mTransferOwnershipMetadataManager.deleteMetadataFile();
+ }
+
+ @Override
+ @Nullable
+ public PersistableBundle getTransferOwnershipBundle() {
+ synchronized (this) {
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
+ getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ final File bundleFile = new File(
+ mInjector.environmentGetUserSystemDirectory(callingUserId),
+ TRANSFER_OWNERSHIP_PARAMETERS_XML);
+ if (!bundleFile.exists()) {
+ return null;
+ }
+ try (FileInputStream stream = new FileInputStream(bundleFile)) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, null);
+ return PersistableBundle.restoreFromXml(parser);
+ } catch (IOException | XmlPullParserException | IllegalArgumentException e) {
+ Slog.e(LOG_TAG, "Caught exception while trying to load the "
+ + "owner transfer parameters from file " + bundleFile, e);
+ return null;
+ }
+ }
+ }
+
/**
* Returns whether printing is enabled for current user.
* @hide
@@ -12352,52 +12652,212 @@
}
}
- /**
- * Returns text of error message if printing is disabled.
- * Only to be called by Print Service.
- * @hide
- */
@Override
- public CharSequence getPrintingDisabledReason() {
- if (!hasPrinting() || !mHasFeature) {
- Log.e(LOG_TAG, "no printing or no management");
- return null;
+ public int addOverrideApn(@NonNull ComponentName who, @NonNull ApnSetting apnSetting) {
+ if (!mHasFeature) {
+ return -1;
}
+ Preconditions.checkNotNull(who, "ComponentName is null in addOverrideApn");
+ Preconditions.checkNotNull(apnSetting, "ApnSetting is null in addOverrideApn");
synchronized (this) {
- final int userHandle = mInjector.userHandleGetCallingUserId();
- DevicePolicyData policy = getUserData(userHandle);
- if (policy.mPrintingEnabled) {
- Log.e(LOG_TAG, "printing is enabled");
- return null;
- }
- String ownerPackage = mOwners.getProfileOwnerPackage(userHandle);
- if (ownerPackage == null) {
- ownerPackage = mOwners.getDeviceOwnerPackageName();
- }
- PackageManager pm = mInjector.getPackageManager();
- PackageInfo packageInfo;
- try {
- packageInfo = pm.getPackageInfo(ownerPackage, 0);
- } catch (NameNotFoundException e) {
- Log.e(LOG_TAG, "getPackageInfo error", e);
- return null;
- }
- if (packageInfo == null) {
- Log.e(LOG_TAG, "packageInfo is inexplicably null");
- return null;
- }
- ApplicationInfo appInfo = packageInfo.applicationInfo;
- if (appInfo == null) {
- Log.e(LOG_TAG, "appInfo is inexplicably null");
- return null;
- }
- CharSequence appLabel = pm.getApplicationLabel(appInfo);
- if (appLabel == null) {
- Log.e(LOG_TAG, "appLabel is inexplicably null");
- return null;
- }
- return ((Context) ActivityThread.currentActivityThread().getSystemUiContext())
- .getResources().getString(R.string.printing_disabled_by, appLabel);
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
}
+
+ int operatedId = -1;
+ Uri resultUri;
+ final long id = mInjector.binderClearCallingIdentity();
+ try {
+ resultUri = mContext.getContentResolver().insert(DPC_URI, apnSetting.toContentValues());
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+ if (resultUri != null) {
+ try {
+ operatedId = Integer.parseInt(resultUri.getLastPathSegment());
+ } catch (NumberFormatException e) {
+ Slog.e(LOG_TAG, "Failed to parse inserted override APN id.", e);
+ }
+ }
+
+ return operatedId;
+ }
+
+ @Override
+ public boolean updateOverrideApn(@NonNull ComponentName who, int apnId,
+ @NonNull ApnSetting apnSetting) {
+ if (!mHasFeature) {
+ return false;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null in updateOverrideApn");
+ Preconditions.checkNotNull(apnSetting, "ApnSetting is null in updateOverrideApn");
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
+
+ if (apnId < 0) {
+ return false;
+ }
+ final long id = mInjector.binderClearCallingIdentity();
+ try {
+ return mContext.getContentResolver().update(
+ Uri.withAppendedPath(DPC_URI, Integer.toString(apnId)),
+ apnSetting.toContentValues(), null, null) > 0;
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+ }
+
+ @Override
+ public boolean removeOverrideApn(@NonNull ComponentName who, int apnId) {
+ if (!mHasFeature) {
+ return false;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null in removeOverrideApn");
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
+
+ return removeOverrideApnUnchecked(apnId);
+ }
+
+ private boolean removeOverrideApnUnchecked(int apnId) {
+ if(apnId < 0) {
+ return false;
+ }
+ int numDeleted = 0;
+ final long id = mInjector.binderClearCallingIdentity();
+ try {
+ numDeleted = mContext.getContentResolver().delete(
+ Uri.withAppendedPath(DPC_URI, Integer.toString(apnId)), null, null);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+ return numDeleted > 0;
+ }
+
+ @Override
+ public List<ApnSetting> getOverrideApns(@NonNull ComponentName who) {
+ if (!mHasFeature) {
+ return Collections.emptyList();
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null in getOverrideApns");
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
+
+ return getOverrideApnsUnchecked();
+ }
+
+ private List<ApnSetting> getOverrideApnsUnchecked() {
+ final Cursor cursor;
+ final long id = mInjector.binderClearCallingIdentity();
+ try {
+ cursor = mContext.getContentResolver().query(DPC_URI, null, null, null, null);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+
+ if (cursor == null) {
+ return Collections.emptyList();
+ }
+ try {
+ List<ApnSetting> apnList = new ArrayList<ApnSetting>();
+ cursor.moveToPosition(-1);
+ while (cursor.moveToNext()) {
+ ApnSetting apn = ApnSetting.makeApnSetting(cursor);
+ apnList.add(apn);
+ }
+ return apnList;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ @Override
+ public void setOverrideApnsEnabled(@NonNull ComponentName who, boolean enabled) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null in setOverrideApnEnabled");
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
+
+ setOverrideApnsEnabledUnchecked(enabled);
+ }
+
+ private void setOverrideApnsEnabledUnchecked(boolean enabled) {
+ ContentValues value = new ContentValues();
+ value.put(ENFORCE_KEY, enabled);
+ final long id = mInjector.binderClearCallingIdentity();
+ try {
+ mContext.getContentResolver().update(
+ ENFORCE_MANAGED_URI, value, null, null);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+ }
+
+ @Override
+ public boolean isOverrideApnEnabled(@NonNull ComponentName who) {
+ if (!mHasFeature) {
+ return false;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null in isOverrideApnEnabled");
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
+
+ Cursor enforceCursor;
+ final long id = mInjector.binderClearCallingIdentity();
+ try {
+ enforceCursor = mContext.getContentResolver().query(
+ ENFORCE_MANAGED_URI, null, null, null, null);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+
+ if (enforceCursor == null) {
+ return false;
+ }
+ try {
+ if (enforceCursor.moveToFirst()) {
+ return enforceCursor.getInt(enforceCursor.getColumnIndex(ENFORCE_KEY)) == 1;
+ }
+ } catch (IllegalArgumentException e) {
+ Slog.e(LOG_TAG, "Cursor returned from ENFORCE_MANAGED_URI doesn't contain "
+ + "correct info.", e);
+ } finally {
+ enforceCursor.close();
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ void saveTransferOwnershipBundleLocked(PersistableBundle bundle, int userId) {
+ final File parametersFile = new File(
+ mInjector.environmentGetUserSystemDirectory(userId),
+ TRANSFER_OWNERSHIP_PARAMETERS_XML);
+ final AtomicFile atomicFile = new AtomicFile(parametersFile);
+ FileOutputStream stream = null;
+ try {
+ stream = atomicFile.startWrite();
+ final XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(stream, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ bundle.saveToXml(serializer);
+ atomicFile.finishWrite(stream);
+ } catch (IOException | XmlPullParserException e) {
+ Slog.e(LOG_TAG, "Caught exception while trying to save the "
+ + "owner transfer parameters to file " + parametersFile, e);
+ parametersFile.delete();
+ atomicFile.failWrite(stream);
+ }
+ }
+
+ void deleteTransferOwnershipBundleLocked(int userId) {
+ final File parametersFile = new File(mInjector.environmentGetUserSystemDirectory(userId),
+ TRANSFER_OWNERSHIP_PARAMETERS_XML);
+ parametersFile.delete();
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 2a23888..d2151ed 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -34,6 +34,7 @@
import android.util.SparseArray;
import android.util.Xml;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
@@ -110,13 +111,23 @@
private SystemUpdateInfo mSystemUpdateInfo;
private final Object mLock = new Object();
+ private final Injector mInjector;
public Owners(UserManager userManager,
UserManagerInternal userManagerInternal,
PackageManagerInternal packageManagerInternal) {
+ this(userManager, userManagerInternal, packageManagerInternal, new Injector());
+ }
+
+ @VisibleForTesting
+ Owners(UserManager userManager,
+ UserManagerInternal userManagerInternal,
+ PackageManagerInternal packageManagerInternal,
+ Injector injector) {
mUserManager = userManager;
mUserManagerInternal = userManagerInternal;
mPackageManagerInternal = packageManagerInternal;
+ mInjector = injector;
}
/**
@@ -125,7 +136,7 @@
void load() {
synchronized (mLock) {
// First, try to read from the legacy file.
- final File legacy = getLegacyConfigFileWithTestOverride();
+ final File legacy = getLegacyConfigFile();
final List<UserInfo> users = mUserManager.getUsers(true);
@@ -288,7 +299,7 @@
}
}
- void transferDeviceOwner(ComponentName target) {
+ void transferDeviceOwnership(ComponentName target) {
synchronized (mLock) {
// We don't set a name because it's not used anyway.
// See DevicePolicyManagerService#getDeviceOwnerName
@@ -642,7 +653,7 @@
private class DeviceOwnerReadWriter extends FileReadWriter {
protected DeviceOwnerReadWriter() {
- super(getDeviceOwnerFileWithTestOverride());
+ super(getDeviceOwnerFile());
}
@Override
@@ -713,7 +724,7 @@
private final int mUserId;
ProfileOwnerReadWriter(int userId) {
- super(getProfileOwnerFileWithTestOverride(userId));
+ super(getProfileOwnerFile(userId));
mUserId = userId;
}
@@ -870,15 +881,29 @@
}
}
- File getLegacyConfigFileWithTestOverride() {
- return new File(Environment.getDataSystemDirectory(), DEVICE_OWNER_XML_LEGACY);
+ @VisibleForTesting
+ File getLegacyConfigFile() {
+ return new File(mInjector.environmentGetDataSystemDirectory(), DEVICE_OWNER_XML_LEGACY);
}
- File getDeviceOwnerFileWithTestOverride() {
- return new File(Environment.getDataSystemDirectory(), DEVICE_OWNER_XML);
+ @VisibleForTesting
+ File getDeviceOwnerFile() {
+ return new File(mInjector.environmentGetDataSystemDirectory(), DEVICE_OWNER_XML);
}
- File getProfileOwnerFileWithTestOverride(int userId) {
- return new File(Environment.getUserSystemDirectory(userId), PROFILE_OWNER_XML);
+ @VisibleForTesting
+ File getProfileOwnerFile(int userId) {
+ return new File(mInjector.environmentGetUserSystemDirectory(userId), PROFILE_OWNER_XML);
+ }
+
+ @VisibleForTesting
+ public static class Injector {
+ File environmentGetDataSystemDirectory() {
+ return Environment.getDataSystemDirectory();
+ }
+
+ File environmentGetUserSystemDirectory(int userId) {
+ return Environment.getUserSystemDirectory(userId);
+ }
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/TransferOwnershipMetadataManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/TransferOwnershipMetadataManager.java
new file mode 100644
index 0000000..1addeb6
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/TransferOwnershipMetadataManager.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.Preconditions;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Handles reading and writing of the owner transfer metadata file.
+ *
+ * Before we perform a device or profile owner transfer, we save this xml file with information
+ * about the current admin, target admin, user id and admin type (device owner or profile owner).
+ * After {@link DevicePolicyManager#transferOwnership} completes, we delete the file. If after
+ * device boot the file is still there, this indicates that the transfer was interrupted by a
+ * reboot.
+ *
+ * Note that this class is not thread safe.
+ */
+class TransferOwnershipMetadataManager {
+ final static String ADMIN_TYPE_DEVICE_OWNER = "device-owner";
+ final static String ADMIN_TYPE_PROFILE_OWNER = "profile-owner";
+ private final static String TAG_USER_ID = "user-id";
+ private final static String TAG_SOURCE_COMPONENT = "source-component";
+ private final static String TAG_TARGET_COMPONENT = "target-component";
+ private final static String TAG_ADMIN_TYPE = "admin-type";
+ private final static String TAG = TransferOwnershipMetadataManager.class.getName();
+ public static final String OWNER_TRANSFER_METADATA_XML = "owner-transfer-metadata.xml";
+
+ private final Injector mInjector;
+
+ TransferOwnershipMetadataManager() {
+ this(new Injector());
+ }
+
+ @VisibleForTesting
+ TransferOwnershipMetadataManager(Injector injector) {
+ mInjector = injector;
+ }
+
+ boolean saveMetadataFile(Metadata params) {
+ final File transferOwnershipMetadataFile = new File(mInjector.getOwnerTransferMetadataDir(),
+ OWNER_TRANSFER_METADATA_XML);
+ final AtomicFile atomicFile = new AtomicFile(transferOwnershipMetadataFile);
+ FileOutputStream stream = null;
+ try {
+ stream = atomicFile.startWrite();
+ final XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(stream, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ insertSimpleTag(serializer, TAG_USER_ID, Integer.toString(params.userId));
+ insertSimpleTag(serializer,
+ TAG_SOURCE_COMPONENT, params.sourceComponent.flattenToString());
+ insertSimpleTag(serializer,
+ TAG_TARGET_COMPONENT, params.targetComponent.flattenToString());
+ insertSimpleTag(serializer, TAG_ADMIN_TYPE, params.adminType);
+ serializer.endDocument();
+ atomicFile.finishWrite(stream);
+ return true;
+ } catch (IOException e) {
+ Slog.e(TAG, "Caught exception while trying to save Owner Transfer "
+ + "Params to file " + transferOwnershipMetadataFile, e);
+ transferOwnershipMetadataFile.delete();
+ atomicFile.failWrite(stream);
+ }
+ return false;
+ }
+
+ private void insertSimpleTag(XmlSerializer serializer, String tagName, String value)
+ throws IOException {
+ serializer.startTag(null, tagName);
+ serializer.text(value);
+ serializer.endTag(null, tagName);
+ }
+
+ @Nullable
+ Metadata loadMetadataFile() {
+ final File transferOwnershipMetadataFile =
+ new File(mInjector.getOwnerTransferMetadataDir(), OWNER_TRANSFER_METADATA_XML);
+ if (!transferOwnershipMetadataFile.exists()) {
+ return null;
+ }
+ Slog.d(TAG, "Loading TransferOwnershipMetadataManager from "
+ + transferOwnershipMetadataFile);
+ try (FileInputStream stream = new FileInputStream(transferOwnershipMetadataFile)) {
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, null);
+ return parseMetadataFile(parser);
+ } catch (IOException | XmlPullParserException | IllegalArgumentException e) {
+ Slog.e(TAG, "Caught exception while trying to load the "
+ + "owner transfer params from file " + transferOwnershipMetadataFile, e);
+ }
+ return null;
+ }
+
+ private Metadata parseMetadataFile(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int type;
+ final int outerDepth = parser.getDepth();
+ int userId = 0;
+ String adminComponent = null;
+ String targetComponent = null;
+ String adminType = null;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ switch (parser.getName()) {
+ case TAG_USER_ID:
+ parser.next();
+ userId = Integer.parseInt(parser.getText());
+ break;
+ case TAG_TARGET_COMPONENT:
+ parser.next();
+ targetComponent = parser.getText();
+ break;
+ case TAG_SOURCE_COMPONENT:
+ parser.next();
+ adminComponent = parser.getText();
+ break;
+ case TAG_ADMIN_TYPE:
+ parser.next();
+ adminType = parser.getText();
+ break;
+ }
+ }
+ return new Metadata(adminComponent, targetComponent, userId, adminType);
+ }
+
+ void deleteMetadataFile() {
+ new File(mInjector.getOwnerTransferMetadataDir(), OWNER_TRANSFER_METADATA_XML).delete();
+ }
+
+ boolean metadataFileExists() {
+ return new File(mInjector.getOwnerTransferMetadataDir(),
+ OWNER_TRANSFER_METADATA_XML).exists();
+ }
+
+ static class Metadata {
+ final int userId;
+ final ComponentName sourceComponent;
+ final ComponentName targetComponent;
+ final String adminType;
+
+ Metadata(@NonNull String sourceComponent, @NonNull String targetComponent,
+ @NonNull int userId, @NonNull String adminType) {
+ this.sourceComponent = ComponentName.unflattenFromString(sourceComponent);
+ this.targetComponent = ComponentName.unflattenFromString(targetComponent);
+ Preconditions.checkNotNull(sourceComponent);
+ Preconditions.checkNotNull(targetComponent);
+ Preconditions.checkStringNotEmpty(adminType);
+ this.userId = userId;
+ this.adminType = adminType;
+ }
+
+ Metadata(@NonNull ComponentName sourceComponent, @NonNull ComponentName targetComponent,
+ @NonNull int userId, @NonNull String adminType) {
+ this(sourceComponent.flattenToString(), targetComponent.flattenToString(),
+ userId, adminType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Metadata)) {
+ return false;
+ }
+ Metadata params = (Metadata) obj;
+
+ return userId == params.userId
+ && sourceComponent.equals(params.sourceComponent)
+ && targetComponent.equals(params.targetComponent)
+ && TextUtils.equals(adminType, params.adminType);
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = 1;
+ hashCode = 31 * hashCode + userId;
+ hashCode = 31 * hashCode + sourceComponent.hashCode();
+ hashCode = 31 * hashCode + targetComponent.hashCode();
+ hashCode = 31 * hashCode + adminType.hashCode();
+ return hashCode;
+ }
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ public File getOwnerTransferMetadataDir() {
+ return Environment.getDataSystemDirectory();
+ }
+ }
+}
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index 5a3a8be..984c9f8 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -121,6 +121,14 @@
public static final int ICMP_ECHO_DATA_OFFSET = 8;
/**
+ * ICMPv4 constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc792
+ */
+ public static final int ICMPV4_ECHO_REQUEST_TYPE = 8;
+
+ /**
* ICMPv6 constants.
*
* See also:
@@ -139,6 +147,8 @@
public static final int ICMPV6_ND_OPTION_TLLA = 2;
public static final int ICMPV6_ND_OPTION_MTU = 5;
+ public static final int ICMPV6_ECHO_REQUEST_TYPE = 128;
+
/**
* UDP constants.
*
@@ -157,6 +167,14 @@
public static final int DHCP4_CLIENT_PORT = 68;
/**
+ * DNS constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc1035
+ */
+ public static final int DNS_SERVER_PORT = 53;
+
+ /**
* Utility functions.
*/
public static byte asByte(int i) { return (byte) i; }
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index 89a5fe1..d6cc805 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManagerInternal;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -64,6 +65,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import java.io.FileDescriptor;
@@ -113,12 +115,12 @@
private final SparseArray<UserState> mUserStates = new SparseArray<>();
- private final DevicePolicyManager mDpc;
+ private final DevicePolicyManager mDpm;
PrintManagerImpl(Context context) {
mContext = context;
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- mDpc = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
registerContentObservers();
registerBroadcastReceivers();
}
@@ -128,7 +130,16 @@
PrintAttributes attributes, String packageName, int appId, int userId) {
adapter = Preconditions.checkNotNull(adapter);
if (!isPrintingEnabled()) {
- final CharSequence disabledMessage = mDpc.getPrintingDisabledReason();
+ CharSequence disabledMessage = null;
+ DevicePolicyManagerInternal dpmi =
+ LocalServices.getService(DevicePolicyManagerInternal.class);
+ final int callingUserId = UserHandle.getCallingUserId();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ disabledMessage = dpmi.getPrintingDisabledReasonForUser(callingUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
if (disabledMessage != null) {
Toast.makeText(mContext, Looper.getMainLooper(), disabledMessage,
Toast.LENGTH_LONG).show();
@@ -711,7 +722,7 @@
}
private boolean isPrintingEnabled() {
- return mDpc == null || mDpc.isPrintingEnabled();
+ return mDpm == null || mDpm.isPrintingEnabled();
}
private void dump(@NonNull DualDumpOutputStream dumpStream,
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 0499bf0..372b5be 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -59,6 +59,8 @@
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Uses API introduced in O (26) -->
<uses-sdk android:minSdkVersion="1"
diff --git a/services/tests/servicestests/res/raw/active_admin_migrated.xml b/services/tests/servicestests/res/raw/active_admin_migrated.xml
new file mode 100644
index 0000000..47af30f
--- /dev/null
+++ b/services/tests/servicestests/res/raw/active_admin_migrated.xml
@@ -0,0 +1,11 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+ <admin name="com.another.package.name/whatever.random.class">
+ <policies flags="991"/>
+ <strong-auth-unlock-timeout value="0"/>
+ <user-restrictions no_add_managed_profile="true"/>
+ <default-enabled-user-restrictions>
+ <restriction value="no_add_managed_profile"/>
+ </default-enabled-user-restrictions>
+ </admin>
+</policies>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/raw/active_admin_not_migrated.xml b/services/tests/servicestests/res/raw/active_admin_not_migrated.xml
new file mode 100644
index 0000000..54eba4c
--- /dev/null
+++ b/services/tests/servicestests/res/raw/active_admin_not_migrated.xml
@@ -0,0 +1,11 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+ <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+ <policies flags="991"/>
+ <strong-auth-unlock-timeout value="0"/>
+ <user-restrictions no_add_managed_profile="true"/>
+ <default-enabled-user-restrictions>
+ <restriction value="no_add_managed_profile"/>
+ </default-enabled-user-restrictions>
+ </admin>
+</policies>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/raw/device_owner_migrated.xml b/services/tests/servicestests/res/raw/device_owner_migrated.xml
new file mode 100644
index 0000000..4ee05bf
--- /dev/null
+++ b/services/tests/servicestests/res/raw/device_owner_migrated.xml
@@ -0,0 +1,9 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+<device-owner
+ package="com.another.package.name"
+ name=""
+ component="com.another.package.name/whatever.random.class"
+ userRestrictionsMigrated="true" />
+<device-owner-context userId="0" />
+</root>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/raw/device_owner_not_migrated.xml b/services/tests/servicestests/res/raw/device_owner_not_migrated.xml
new file mode 100644
index 0000000..3a532af
--- /dev/null
+++ b/services/tests/servicestests/res/raw/device_owner_not_migrated.xml
@@ -0,0 +1,9 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+<device-owner
+ package="com.android.frameworks.servicestests"
+ name=""
+ component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"
+ userRestrictionsMigrated="true" />
+<device-owner-context userId="0" />
+</root>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/raw/profile_owner_migrated.xml b/services/tests/servicestests/res/raw/profile_owner_migrated.xml
new file mode 100644
index 0000000..f73d2cd
--- /dev/null
+++ b/services/tests/servicestests/res/raw/profile_owner_migrated.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+ <profile-owner package="com.another.package.name"
+ name="com.another.package.name"
+ component="com.another.package.name/whatever.random.class"
+ userRestrictionsMigrated="true"/>
+</root>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/raw/profile_owner_not_migrated.xml b/services/tests/servicestests/res/raw/profile_owner_not_migrated.xml
new file mode 100644
index 0000000..1ce3a47
--- /dev/null
+++ b/services/tests/servicestests/res/raw/profile_owner_not_migrated.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+ <profile-owner package="com.android.frameworks.servicestests"
+ name="com.android.frameworks.servicestests"
+ component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"
+ userRestrictionsMigrated="true"/>
+</root>
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
index de54e52..a29e169 100644
--- a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
@@ -15,6 +15,8 @@
*/
package com.android.server;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+
import static com.android.server.ForceAppStandbyTracker.TARGET_OP;
import static org.junit.Assert.assertEquals;
@@ -33,6 +35,7 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.PackageOps;
@@ -259,13 +262,19 @@
private static final int JOBS_AND_ALARMS = ALARMS_ONLY | JOBS_ONLY;
private void areRestricted(ForceAppStandbyTrackerTestable instance, int uid, String packageName,
- int restrictionTypes) {
+ int restrictionTypes, boolean exemptFromBatterySaver) {
assertEquals(((restrictionTypes & JOBS_ONLY) != 0),
- instance.areJobsRestricted(uid, packageName));
+ instance.areJobsRestricted(uid, packageName, exemptFromBatterySaver));
assertEquals(((restrictionTypes & ALARMS_ONLY) != 0),
instance.areAlarmsRestricted(uid, packageName));
}
+ private void areRestricted(ForceAppStandbyTrackerTestable instance, int uid, String packageName,
+ int restrictionTypes) {
+ areRestricted(instance, uid, packageName, restrictionTypes,
+ /*exemptFromBatterySaver=*/ false);
+ }
+
@Test
public void testAll() throws Exception {
final ForceAppStandbyTrackerTestable instance = newInstance();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
index 8d5556e..07262e1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
@@ -23,6 +23,8 @@
import static com.android.server.testutils.TestUtils.strictMock;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
@@ -32,6 +34,7 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.os.Handler;
import android.os.Message;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
@@ -46,7 +49,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.CompletableFuture;
import java.util.function.IntConsumer;
+import java.util.function.Supplier;
/**
@@ -130,7 +135,7 @@
}
@NonNull
- public MagnificationGestureHandler newInstance(boolean detectTripleTap,
+ private MagnificationGestureHandler newInstance(boolean detectTripleTap,
boolean detectShortcutTrigger) {
MagnificationGestureHandler h = new MagnificationGestureHandler(
mContext, mMagnificationController,
@@ -192,6 +197,16 @@
});
}
+ @Test
+ public void testTransitionToDelegatingStateAndClear_preservesShortcutTriggeredState() {
+ mMgh.mDetectingState.transitionToDelegatingStateAndClear();
+ assertFalse(mMgh.mDetectingState.mShortcutTriggered);
+
+ goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
+ mMgh.mDetectingState.transitionToDelegatingStateAndClear();
+ assertTrue(mMgh.mDetectingState.mShortcutTriggered);
+ }
+
/**
* Covers edges of the graph not covered by "canonical" transitions specified in
* {@link #goFromStateIdleTo} and {@link #returnToNormalFrom}
@@ -510,14 +525,20 @@
fastForward(1);
}
+ private static MotionEvent fromTouchscreen(MotionEvent ev) {
+ ev.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ return ev;
+ }
+
private MotionEvent moveEvent(float x, float y) {
- return MotionEvent.obtain(mLastDownTime, mClock.now(), ACTION_MOVE, x, y, 0);
+ return fromTouchscreen(
+ MotionEvent.obtain(mLastDownTime, mClock.now(), ACTION_MOVE, x, y, 0));
}
private MotionEvent downEvent() {
mLastDownTime = mClock.now();
- return MotionEvent.obtain(mLastDownTime, mLastDownTime,
- ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0);
+ return fromTouchscreen(MotionEvent.obtain(mLastDownTime, mLastDownTime,
+ ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0));
}
private MotionEvent upEvent() {
@@ -525,8 +546,8 @@
}
private MotionEvent upEvent(long downTime) {
- return MotionEvent.obtain(downTime, mClock.now(),
- MotionEvent.ACTION_UP, DEFAULT_X, DEFAULT_Y, 0);
+ return fromTouchscreen(MotionEvent.obtain(downTime, mClock.now(),
+ MotionEvent.ACTION_UP, DEFAULT_X, DEFAULT_Y, 0));
}
private MotionEvent pointerEvent(int action, float x, float y) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 06f138b..00e27c9 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -60,40 +60,33 @@
*/
public static class OwnersTestable extends Owners {
public static final String LEGACY_FILE = "legacy.xml";
- public static final String DEVICE_OWNER_FILE = "device_owner2.xml";
- public static final String PROFILE_OWNER_FILE = "profile_owner.xml";
-
- private final File mLegacyFile;
- private final File mDeviceOwnerFile;
- private final File mUsersDataDir;
public OwnersTestable(MockSystemServices services) {
super(services.userManager, services.userManagerInternal,
- services.packageManagerInternal);
- mLegacyFile = new File(services.dataDir, LEGACY_FILE);
- mDeviceOwnerFile = new File(services.dataDir, DEVICE_OWNER_FILE);
- mUsersDataDir = new File(services.dataDir, "users");
+ services.packageManagerInternal, new MockInjector(services));
}
- @Override
- File getLegacyConfigFileWithTestOverride() {
- return mLegacyFile;
- }
+ static class MockInjector extends Injector {
+ private final MockSystemServices mServices;
- @Override
- File getDeviceOwnerFileWithTestOverride() {
- return mDeviceOwnerFile;
- }
+ private MockInjector(MockSystemServices services) {
+ mServices = services;
+ }
- @Override
- File getProfileOwnerFileWithTestOverride(int userId) {
- final File userDir = new File(mUsersDataDir, String.valueOf(userId));
- return new File(userDir, PROFILE_OWNER_FILE);
+ @Override
+ File environmentGetDataSystemDirectory() {
+ return mServices.dataDir;
+ }
+
+ @Override
+ File environmentGetUserSystemDirectory(int userId) {
+ return mServices.environment.getUserSystemDirectory(userId);
+ }
}
}
public final DpmMockContext context;
- private final MockInjector mMockInjector;
+ protected final MockInjector mMockInjector;
public DevicePolicyManagerServiceTestable(MockSystemServices services, DpmMockContext context) {
this(new MockInjector(services, context));
@@ -124,8 +117,7 @@
}
}
-
- private static class MockInjector extends Injector {
+ static class MockInjector extends Injector {
public final DpmMockContext context;
private final MockSystemServices services;
@@ -133,7 +125,7 @@
// Key is a pair of uri and userId
private final Map<Pair<Uri, Integer>, ContentObserver> mContentObservers = new ArrayMap<>();
- private MockInjector(MockSystemServices services, DpmMockContext context) {
+ public MockInjector(MockSystemServices services, DpmMockContext context) {
super(context);
this.services = services;
this.context = context;
@@ -449,5 +441,11 @@
void postOnSystemServerInitThreadPool(Runnable runnable) {
runnable.run();
}
+
+ @Override
+ public TransferOwnershipMetadataManager newTransferOwnershipMetadataManager() {
+ return new TransferOwnershipMetadataManager(
+ new TransferOwnershipMetadataManagerTest.MockInjector());
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 725ede8..6b87ea9 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -50,6 +50,7 @@
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
import android.Manifest.permission;
+import android.annotation.RawRes;
import android.app.Activity;
import android.app.Notification;
import android.app.admin.DeviceAdminReceiver;
@@ -78,6 +79,7 @@
import android.security.KeyChain;
import android.security.keystore.AttestationUtils;
import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArraySet;
@@ -92,10 +94,13 @@
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import java.io.File;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -126,6 +131,7 @@
permission.MANAGE_DEVICE_ADMINS, permission.MANAGE_PROFILE_AND_DEVICE_OWNERS,
permission.MANAGE_USERS, permission.INTERACT_ACROSS_USERS_FULL);
public static final String NOT_DEVICE_OWNER_MSG = "does not own the device";
+ public static final String NOT_PROFILE_OWNER_MSG = "does not own the profile";
public static final String ONGOING_CALL_MSG = "ongoing call on the device";
// TODO replace all instances of this with explicit {@link #mServiceContext}.
@@ -200,9 +206,14 @@
setUpUserManager();
}
+ private TransferOwnershipMetadataManager getMockTransferMetadataManager() {
+ return dpms.mTransferOwnershipMetadataManager;
+ }
+
@Override
protected void tearDown() throws Exception {
flushTasks();
+ getMockTransferMetadataManager().deleteMetadataFile();
super.tearDown();
}
@@ -301,10 +312,10 @@
// Verify
verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
- MockUtils.checkAdminApps(admin1.getPackageName()),
+ MockUtils.checkApps(admin1.getPackageName()),
eq(UserHandle.USER_SYSTEM));
verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
- MockUtils.checkAdminApps(admin2.getPackageName(),
+ MockUtils.checkApps(admin2.getPackageName(),
adminAnotherPackage.getPackageName()),
eq(DpmMockContext.CALLER_USER_HANDLE));
verify(getServices().usageStatsManagerInternal).onAdminDataAvailable();
@@ -705,7 +716,7 @@
assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
- MockUtils.checkAdminApps(admin2.getPackageName()),
+ MockUtils.checkApps(admin2.getPackageName()),
eq(DpmMockContext.CALLER_USER_HANDLE));
// Again broadcast from saveSettingsLocked().
@@ -1360,6 +1371,7 @@
eq(packageName),
anyInt(),
eq(userId));
+ doReturn(true).when(getServices().ipackageManager).isPackageAvailable(packageName, userId);
// Setup application UID with the PackageManager
doReturn(uid).when(getServices().packageManager).getPackageUidAsUser(
eq(packageName),
@@ -2101,6 +2113,53 @@
}
}
+ public void testSetGetMeteredDataDisabled() throws Exception {
+ setAsProfileOwner(admin1);
+
+ final ArrayList<String> emptyList = new ArrayList<>();
+ assertEquals(emptyList, dpm.getMeteredDataDisabled(admin1));
+
+ // Setup
+ final ArrayList<String> pkgsToRestrict = new ArrayList<>();
+ final String package1 = "com.example.one";
+ final String package2 = "com.example.two";
+ pkgsToRestrict.add(package1);
+ pkgsToRestrict.add(package2);
+ setupPackageInPackageManager(package1, DpmMockContext.CALLER_USER_HANDLE, 123, 0);
+ setupPackageInPackageManager(package2, DpmMockContext.CALLER_USER_HANDLE, 456, 0);
+ List<String> excludedPkgs = dpm.setMeteredDataDisabled(admin1, pkgsToRestrict);
+
+ // Verify
+ assertEquals(emptyList, excludedPkgs);
+ assertEquals(pkgsToRestrict, dpm.getMeteredDataDisabled(admin1));
+ verify(getServices().networkPolicyManagerInternal).setMeteredRestrictedPackages(
+ MockUtils.checkApps(pkgsToRestrict.toArray(new String[0])),
+ eq(DpmMockContext.CALLER_USER_HANDLE));
+
+ // Setup
+ pkgsToRestrict.remove(package1);
+ excludedPkgs = dpm.setMeteredDataDisabled(admin1, pkgsToRestrict);
+
+ // Verify
+ assertEquals(emptyList, excludedPkgs);
+ assertEquals(pkgsToRestrict, dpm.getMeteredDataDisabled(admin1));
+ verify(getServices().networkPolicyManagerInternal).setMeteredRestrictedPackages(
+ MockUtils.checkApps(pkgsToRestrict.toArray(new String[0])),
+ eq(DpmMockContext.CALLER_USER_HANDLE));
+ }
+
+ public void testSetGetMeteredDataDisabled_deviceAdmin() {
+ mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
+ dpm.setActiveAdmin(admin1, true);
+ assertTrue(dpm.isAdminActive(admin1));
+ mContext.callerPermissions.remove(permission.MANAGE_DEVICE_ADMINS);
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ NOT_PROFILE_OWNER_MSG,
+ () -> dpm.setMeteredDataDisabled(admin1, new ArrayList<>()));
+ assertExpectException(SecurityException.class, /* messageRegex= */ NOT_PROFILE_OWNER_MSG,
+ () -> dpm.getMeteredDataDisabled(admin1));
+ }
+
public void testCreateAdminSupportIntent() throws Exception {
// Setup device owner.
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
@@ -3603,15 +3662,47 @@
MoreAsserts.assertEmpty(targetUsers);
}
+ private void verifyLockTaskState(int userId) throws Exception {
+ verifyLockTaskState(userId, new String[0], DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+ }
+
+ private void verifyLockTaskState(int userId, String[] packages, int flags) throws Exception {
+ verify(getServices().iactivityManager).updateLockTaskPackages(userId, packages);
+ verify(getServices().iactivityManager).updateLockTaskFeatures(userId, flags);
+ }
+
+ private void verifyCanSetLockTask(int uid, int userId, ComponentName who, String[] packages,
+ int flags) throws Exception {
+ mContext.binder.callingUid = uid;
+ dpm.setLockTaskPackages(who, packages);
+ MoreAsserts.assertEquals(packages, dpm.getLockTaskPackages(who));
+ for (String p : packages) {
+ assertTrue(dpm.isLockTaskPermitted(p));
+ }
+ assertFalse(dpm.isLockTaskPermitted("anotherPackage"));
+ // Test to see if set lock task features can be set
+ dpm.setLockTaskFeatures(who, flags);
+ verifyLockTaskState(userId, packages, flags);
+ }
+
+ private void verifyCanNotSetLockTask(int uid, ComponentName who, String[] packages,
+ int flags) throws Exception {
+ mContext.binder.callingUid = uid;
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.setLockTaskPackages(who, packages));
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.getLockTaskPackages(who));
+ assertFalse(dpm.isLockTaskPermitted("doPackage1"));
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.setLockTaskFeatures(who, flags));
+ }
+
public void testLockTaskPolicyAllowedForAffiliatedUsers() throws Exception {
// Setup a device owner.
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
// Lock task policy is updated when loading user data.
- verify(getServices().iactivityManager).updateLockTaskPackages(
- UserHandle.USER_SYSTEM, new String[0]);
- verify(getServices().iactivityManager).updateLockTaskFeatures(
- UserHandle.USER_SYSTEM, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+ verifyLockTaskState(UserHandle.USER_SYSTEM);
// Set up a managed profile managed by different package (package name shouldn't matter)
final int MANAGED_PROFILE_USER_ID = 15;
@@ -3619,40 +3710,30 @@
final ComponentName adminDifferentPackage =
new ComponentName("another.package", "whatever.class");
addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2);
- verify(getServices().iactivityManager).updateLockTaskPackages(
- MANAGED_PROFILE_USER_ID, new String[0]);
- verify(getServices().iactivityManager).updateLockTaskFeatures(
- MANAGED_PROFILE_USER_ID, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+ verifyLockTaskState(MANAGED_PROFILE_USER_ID);
+
+ // Setup a PO on the secondary user
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ setAsProfileOwner(admin3);
+ verifyLockTaskState(DpmMockContext.CALLER_USER_HANDLE);
// The DO can still set lock task packages
- mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
final String[] doPackages = {"doPackage1", "doPackage2"};
- dpm.setLockTaskPackages(admin1, doPackages);
- MoreAsserts.assertEquals(doPackages, dpm.getLockTaskPackages(admin1));
- assertTrue(dpm.isLockTaskPermitted("doPackage1"));
- assertFalse(dpm.isLockTaskPermitted("anotherPackage"));
- verify(getServices().iactivityManager).updateLockTaskPackages(
- UserHandle.USER_SYSTEM, doPackages);
- // And the DO can still set lock task features
- final int doFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+ final int flags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
| DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
- dpm.setLockTaskFeatures(admin1, doFlags);
- verify(getServices().iactivityManager).updateLockTaskFeatures(
- UserHandle.USER_SYSTEM, doFlags);
+ verifyCanSetLockTask(DpmMockContext.CALLER_SYSTEM_USER_UID, UserHandle.USER_SYSTEM, admin1, doPackages, flags);
+
+ final String[] secondaryPoPackages = {"secondaryPoPackage1", "secondaryPoPackage2"};
+ final int secondaryPoFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+ | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+ verifyCanNotSetLockTask(DpmMockContext.CALLER_UID, admin3, secondaryPoPackages, secondaryPoFlags);
// Managed profile is unaffiliated - shouldn't be able to setLockTaskPackages.
mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
final String[] poPackages = {"poPackage1", "poPackage2"};
- assertExpectException(SecurityException.class, /* messageRegex =*/ null,
- () -> dpm.setLockTaskPackages(adminDifferentPackage, poPackages));
- assertExpectException(SecurityException.class, /* messageRegex =*/ null,
- () -> dpm.getLockTaskPackages(adminDifferentPackage));
- assertFalse(dpm.isLockTaskPermitted("doPackage1"));
- // And it shouldn't be able to setLockTaskFeatures.
final int poFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
| DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
- assertExpectException(SecurityException.class, /* messageRegex =*/ null,
- () -> dpm.setLockTaskFeatures(adminDifferentPackage, poFlags));
+ verifyCanNotSetLockTask(MANAGED_PROFILE_ADMIN_UID, adminDifferentPackage, poPackages, poFlags);
// Setting same affiliation ids
final Set<String> userAffiliationIds = Collections.singleton("some-affiliation-id");
@@ -3667,12 +3748,9 @@
MoreAsserts.assertEquals(poPackages, dpm.getLockTaskPackages(adminDifferentPackage));
assertTrue(dpm.isLockTaskPermitted("poPackage1"));
assertFalse(dpm.isLockTaskPermitted("doPackage2"));
- verify(getServices().iactivityManager).updateLockTaskPackages(
- MANAGED_PROFILE_USER_ID, poPackages);
// And it can set lock task features.
dpm.setLockTaskFeatures(adminDifferentPackage, poFlags);
- verify(getServices().iactivityManager).updateLockTaskFeatures(
- MANAGED_PROFILE_USER_ID, poFlags);
+ verifyLockTaskState(MANAGED_PROFILE_USER_ID, poPackages, poFlags);
// Unaffiliate the profile, lock task mode no longer available on the profile.
dpm.setAffiliationIds(adminDifferentPackage, Collections.emptySet());
@@ -3683,8 +3761,38 @@
verify(getServices().iactivityManager, times(2)).updateLockTaskFeatures(
MANAGED_PROFILE_USER_ID, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+ // Verify that lock task packages were not cleared for the DO
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
assertTrue(dpm.isLockTaskPermitted("doPackage1"));
+
+ }
+
+ public void testLockTaskPolicyForProfileOwner() throws Exception {
+ // Setup a PO
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ setAsProfileOwner(admin1);
+ verifyLockTaskState(DpmMockContext.CALLER_USER_HANDLE);
+
+ final String[] poPackages = {"poPackage1", "poPackage2"};
+ final int poFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+ | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+ verifyCanSetLockTask(DpmMockContext.CALLER_UID, DpmMockContext.CALLER_USER_HANDLE, admin1,
+ poPackages, poFlags);
+
+ // Set up a managed profile managed by different package (package name shouldn't matter)
+ final int MANAGED_PROFILE_USER_ID = 15;
+ final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456);
+ final ComponentName adminDifferentPackage =
+ new ComponentName("another.package", "whatever.class");
+ addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2);
+ verifyLockTaskState(MANAGED_PROFILE_USER_ID);
+
+ // Managed profile is unaffiliated - shouldn't be able to setLockTaskPackages.
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+ final String[] mpoPackages = {"poPackage1", "poPackage2"};
+ final int mpoFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+ | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+ verifyCanNotSetLockTask(MANAGED_PROFILE_ADMIN_UID, adminDifferentPackage, mpoPackages, mpoFlags);
}
public void testIsDeviceManaged() throws Exception {
@@ -4561,6 +4669,23 @@
MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
}
+ public void testOverrideApnAPIsFailWithPO() throws Exception {
+ setupProfileOwner();
+ ApnSetting apn = (new ApnSetting.Builder()).build();
+ assertExpectException(SecurityException.class, null, () ->
+ dpm.addOverrideApn(admin1, apn));
+ assertExpectException(SecurityException.class, null, () ->
+ dpm.updateOverrideApn(admin1, 0, apn));
+ assertExpectException(SecurityException.class, null, () ->
+ dpm.removeOverrideApn(admin1, 0));
+ assertExpectException(SecurityException.class, null, () ->
+ dpm.getOverrideApns(admin1));
+ assertExpectException(SecurityException.class, null, () ->
+ dpm.setOverrideApnsEnabled(admin1, false));
+ assertExpectException(SecurityException.class, null, () ->
+ dpm.isOverrideApnEnabled(admin1));
+ }
+
private void verifyCanGetOwnerInstalledCaCerts(
final ComponentName caller, final DpmMockContext callerContext) throws Exception {
final String alias = "cert";
@@ -4719,6 +4844,176 @@
AttestationUtils.ID_TYPE_MEID});
}
+ public void testRevertDeviceOwnership_noMetadataFile() throws Exception {
+ setDeviceOwner();
+ initializeDpms();
+ assertFalse(getMockTransferMetadataManager().metadataFileExists());
+ assertTrue(dpms.isDeviceOwner(admin1, UserHandle.USER_SYSTEM));
+ assertTrue(dpms.isAdminActive(admin1, UserHandle.USER_SYSTEM));
+ }
+
+ public void testRevertDeviceOwnership_adminAndDeviceMigrated() throws Exception {
+ DpmTestUtils.writeInputStreamToFile(
+ getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
+ getDeviceOwnerPoliciesFile());
+ DpmTestUtils.writeInputStreamToFile(
+ getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_migrated),
+ getDeviceOwnerFile());
+ assertDeviceOwnershipRevertedWithFakeTransferMetadata();
+ }
+
+ public void testRevertDeviceOwnership_deviceNotMigrated()
+ throws Exception {
+ DpmTestUtils.writeInputStreamToFile(
+ getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
+ getDeviceOwnerPoliciesFile());
+ DpmTestUtils.writeInputStreamToFile(
+ getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_not_migrated),
+ getDeviceOwnerFile());
+ assertDeviceOwnershipRevertedWithFakeTransferMetadata();
+ }
+
+ public void testRevertDeviceOwnership_adminAndDeviceNotMigrated()
+ throws Exception {
+ DpmTestUtils.writeInputStreamToFile(
+ getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_not_migrated),
+ getDeviceOwnerPoliciesFile());
+ DpmTestUtils.writeInputStreamToFile(
+ getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_not_migrated),
+ getDeviceOwnerFile());
+ assertDeviceOwnershipRevertedWithFakeTransferMetadata();
+ }
+
+ public void testRevertProfileOwnership_noMetadataFile() throws Exception {
+ setupProfileOwner();
+ initializeDpms();
+ assertFalse(getMockTransferMetadataManager().metadataFileExists());
+ assertTrue(dpms.isProfileOwner(admin1, DpmMockContext.CALLER_USER_HANDLE));
+ assertTrue(dpms.isAdminActive(admin1, DpmMockContext.CALLER_USER_HANDLE));
+ UserHandle userHandle = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE);
+ }
+
+ public void testRevertProfileOwnership_adminAndProfileMigrated() throws Exception {
+ getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, UserInfo.FLAG_MANAGED_PROFILE,
+ UserHandle.USER_SYSTEM);
+ DpmTestUtils.writeInputStreamToFile(
+ getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
+ getProfileOwnerPoliciesFile());
+ DpmTestUtils.writeInputStreamToFile(
+ getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_migrated),
+ getProfileOwnerFile());
+ assertProfileOwnershipRevertedWithFakeTransferMetadata();
+ }
+
+ public void testRevertProfileOwnership_profileNotMigrated() throws Exception {
+ getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, UserInfo.FLAG_MANAGED_PROFILE,
+ UserHandle.USER_SYSTEM);
+ DpmTestUtils.writeInputStreamToFile(
+ getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
+ getProfileOwnerPoliciesFile());
+ DpmTestUtils.writeInputStreamToFile(
+ getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_not_migrated),
+ getProfileOwnerFile());
+ assertProfileOwnershipRevertedWithFakeTransferMetadata();
+ }
+
+ public void testRevertProfileOwnership_adminAndProfileNotMigrated() throws Exception {
+ getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, UserInfo.FLAG_MANAGED_PROFILE,
+ UserHandle.USER_SYSTEM);
+ DpmTestUtils.writeInputStreamToFile(
+ getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_not_migrated),
+ getProfileOwnerPoliciesFile());
+ DpmTestUtils.writeInputStreamToFile(
+ getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_not_migrated),
+ getProfileOwnerFile());
+ assertProfileOwnershipRevertedWithFakeTransferMetadata();
+ }
+
+ // admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one.
+ private void assertDeviceOwnershipRevertedWithFakeTransferMetadata() throws Exception {
+ writeFakeTransferMetadataFile(UserHandle.USER_SYSTEM,
+ TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER);
+
+ final long ident = mServiceContext.binder.clearCallingIdentity();
+ setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+ setUpPackageManagerForFakeAdmin(adminAnotherPackage,
+ DpmMockContext.CALLER_SYSTEM_USER_UID, admin1);
+ // To simulate a reboot, we just reinitialize dpms and call systemReady
+ initializeDpms();
+
+ assertTrue(dpm.isDeviceOwnerApp(admin1.getPackageName()));
+ assertFalse(dpm.isDeviceOwnerApp(adminAnotherPackage.getPackageName()));
+ assertFalse(dpm.isAdminActive(adminAnotherPackage));
+ assertTrue(dpm.isAdminActive(admin1));
+ assertTrue(dpm.isDeviceOwnerAppOnCallingUser(admin1.getPackageName()));
+ assertEquals(admin1, dpm.getDeviceOwnerComponentOnCallingUser());
+
+ assertTrue(dpm.isDeviceOwnerAppOnAnyUser(admin1.getPackageName()));
+ assertEquals(admin1, dpm.getDeviceOwnerComponentOnAnyUser());
+ assertEquals(UserHandle.USER_SYSTEM, dpm.getDeviceOwnerUserId());
+ assertFalse(getMockTransferMetadataManager().metadataFileExists());
+
+ mServiceContext.binder.restoreCallingIdentity(ident);
+ }
+
+ // admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one.
+ private void assertProfileOwnershipRevertedWithFakeTransferMetadata() throws Exception {
+ writeFakeTransferMetadataFile(DpmMockContext.CALLER_USER_HANDLE,
+ TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER);
+
+ int uid = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE,
+ DpmMockContext.CALLER_SYSTEM_USER_UID);
+ setUpPackageManagerForAdmin(admin1, uid);
+ setUpPackageManagerForFakeAdmin(adminAnotherPackage, uid, admin1);
+ // To simulate a reboot, we just reinitialize dpms and call systemReady
+ initializeDpms();
+
+ assertTrue(dpm.isProfileOwnerApp(admin1.getPackageName()));
+ assertTrue(dpm.isAdminActive(admin1));
+ assertFalse(dpm.isProfileOwnerApp(adminAnotherPackage.getPackageName()));
+ assertFalse(dpm.isAdminActive(adminAnotherPackage));
+ assertEquals(dpm.getProfileOwnerAsUser(DpmMockContext.CALLER_USER_HANDLE), admin1);
+ assertFalse(getMockTransferMetadataManager().metadataFileExists());
+ }
+
+ private void writeFakeTransferMetadataFile(int callerUserHandle, String adminType) {
+ TransferOwnershipMetadataManager metadataManager = getMockTransferMetadataManager();
+ metadataManager.deleteMetadataFile();
+
+ final TransferOwnershipMetadataManager.Metadata metadata =
+ new TransferOwnershipMetadataManager.Metadata(
+ admin1.flattenToString(), adminAnotherPackage.flattenToString(),
+ callerUserHandle,
+ adminType);
+ metadataManager.saveMetadataFile(metadata);
+ }
+
+ private File getDeviceOwnerFile() {
+ return dpms.mOwners.getDeviceOwnerFile();
+ }
+
+ private File getProfileOwnerFile() {
+ return dpms.mOwners.getProfileOwnerFile(DpmMockContext.CALLER_USER_HANDLE);
+ }
+
+ private File getProfileOwnerPoliciesFile() {
+ File parentDir = dpms.mMockInjector.environmentGetUserSystemDirectory(
+ DpmMockContext.CALLER_USER_HANDLE);
+ return getPoliciesFile(parentDir);
+ }
+
+ private File getDeviceOwnerPoliciesFile() {
+ return getPoliciesFile(getServices().systemUserDataDir);
+ }
+
+ private File getPoliciesFile(File parentDir) {
+ return new File(parentDir, "device_policies.xml");
+ }
+
+ private InputStream getRawStream(@RawRes int id) {
+ return mRealTestContext.getResources().openRawResource(id);
+ }
+
private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
when(getServices().settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
index cceb2d2..2882b88 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
@@ -16,9 +16,6 @@
package com.android.server.devicepolicy;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Sets;
-
import android.content.Context;
import android.os.Bundle;
import android.os.FileUtils;
@@ -28,21 +25,25 @@
import android.util.Log;
import android.util.Printer;
+import libcore.io.Streams;
+
+import com.google.android.collect.Lists;
+
+import junit.framework.AssertionFailedError;
+
import org.junit.Assert;
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
-import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-
-import junit.framework.AssertionFailedError;
public class DpmTestUtils extends AndroidTestCase {
public static void clearDir(File dir) {
@@ -136,6 +137,11 @@
}
}
+ public static void writeInputStreamToFile(InputStream stream, File file)
+ throws IOException {
+ Streams.copy(stream, new FileOutputStream(file));
+ }
+
private static boolean checkAssertRestrictions(Bundle a, Bundle b) {
try {
assertRestrictions(a, b);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 0343a52..34c69f5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -32,6 +32,7 @@
import android.app.backup.IBackupManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -39,8 +40,10 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
+import android.database.Cursor;
import android.media.IAudioService;
import android.net.IIpConnectivityMetrics;
+import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.PowerManager;
@@ -51,6 +54,7 @@
import android.provider.Settings;
import android.security.KeyChain;
import android.telephony.TelephonyManager;
+import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;
import android.util.ArrayMap;
import android.util.Pair;
@@ -144,6 +148,23 @@
packageManager = spy(realContext.getPackageManager());
contentResolver = new MockContentResolver();
+ contentResolver.addProvider("telephony", new MockContentProvider(realContext) {
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+ });
contentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
// Add the system user with a fake profile group already set up (this can happen in the real
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
index dec962e..92ea766 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -119,25 +119,25 @@
return MockitoHamcrest.argThat(m);
}
- public static Set<String> checkAdminApps(String... adminApps) {
+ public static Set<String> checkApps(String... adminApps) {
final Matcher<Set<String>> m = new BaseMatcher<Set<String>>() {
@Override
public boolean matches(Object item) {
if (item == null) return false;
- final Set<String> actualAdminApps = (Set<String>) item;
- if (adminApps.length != actualAdminApps.size()) {
+ final Set<String> actualApps = (Set<String>) item;
+ if (adminApps.length != actualApps.size()) {
return false;
}
- final Set<String> copyOfAdmins = new ArraySet<>(actualAdminApps);
+ final Set<String> copyOfApps = new ArraySet<>(actualApps);
for (String adminApp : adminApps) {
- copyOfAdmins.remove(adminApp);
+ copyOfApps.remove(adminApp);
}
- return copyOfAdmins.isEmpty();
+ return copyOfApps.isEmpty();
}
@Override
public void describeTo(Description description) {
- description.appendText("Admin apps=" + Arrays.toString(adminApps));
+ description.appendText("Apps=" + Arrays.toString(adminApps));
}
};
return MockitoHamcrest.argThat(m);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 85835f7..cb6a747 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -42,21 +42,21 @@
{
final OwnersTestable owners = new OwnersTestable(getServices());
- DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+ DpmTestUtils.writeToFile(owners.getLegacyConfigFile(),
DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test01/input.xml"));
owners.load();
// The legacy file should be removed.
- assertFalse(owners.getLegacyConfigFileWithTestOverride().exists());
+ assertFalse(owners.getLegacyConfigFile().exists());
// File was empty, so no new files should be created.
- assertFalse(owners.getDeviceOwnerFileWithTestOverride().exists());
+ assertFalse(owners.getDeviceOwnerFile().exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists());
+ assertFalse(owners.getProfileOwnerFile(10).exists());
+ assertFalse(owners.getProfileOwnerFile(11).exists());
+ assertFalse(owners.getProfileOwnerFile(20).exists());
+ assertFalse(owners.getProfileOwnerFile(21).exists());
assertFalse(owners.hasDeviceOwner());
assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
@@ -95,20 +95,20 @@
{
final OwnersTestable owners = new OwnersTestable(getServices());
- DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+ DpmTestUtils.writeToFile(owners.getLegacyConfigFile(),
DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test02/input.xml"));
owners.load();
// The legacy file should be removed.
- assertFalse(owners.getLegacyConfigFileWithTestOverride().exists());
+ assertFalse(owners.getLegacyConfigFile().exists());
- assertTrue(owners.getDeviceOwnerFileWithTestOverride().exists()); // TODO Check content
+ assertTrue(owners.getDeviceOwnerFile().exists()); // TODO Check content
- assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists());
+ assertFalse(owners.getProfileOwnerFile(10).exists());
+ assertFalse(owners.getProfileOwnerFile(11).exists());
+ assertFalse(owners.getProfileOwnerFile(20).exists());
+ assertFalse(owners.getProfileOwnerFile(21).exists());
assertTrue(owners.hasDeviceOwner());
assertEquals(null, owners.getDeviceOwnerName());
@@ -153,20 +153,20 @@
{
final OwnersTestable owners = new OwnersTestable(getServices());
- DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+ DpmTestUtils.writeToFile(owners.getLegacyConfigFile(),
DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test03/input.xml"));
owners.load();
// The legacy file should be removed.
- assertFalse(owners.getLegacyConfigFileWithTestOverride().exists());
+ assertFalse(owners.getLegacyConfigFile().exists());
- assertFalse(owners.getDeviceOwnerFileWithTestOverride().exists());
+ assertFalse(owners.getDeviceOwnerFile().exists());
- assertTrue(owners.getProfileOwnerFileWithTestOverride(10).exists());
- assertTrue(owners.getProfileOwnerFileWithTestOverride(11).exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists());
+ assertTrue(owners.getProfileOwnerFile(10).exists());
+ assertTrue(owners.getProfileOwnerFile(11).exists());
+ assertFalse(owners.getProfileOwnerFile(20).exists());
+ assertFalse(owners.getProfileOwnerFile(21).exists());
assertFalse(owners.hasDeviceOwner());
assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
@@ -231,20 +231,20 @@
{
final OwnersTestable owners = new OwnersTestable(getServices());
- DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+ DpmTestUtils.writeToFile(owners.getLegacyConfigFile(),
DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test04/input.xml"));
owners.load();
// The legacy file should be removed.
- assertFalse(owners.getLegacyConfigFileWithTestOverride().exists());
+ assertFalse(owners.getLegacyConfigFile().exists());
- assertTrue(owners.getDeviceOwnerFileWithTestOverride().exists());
+ assertTrue(owners.getDeviceOwnerFile().exists());
- assertTrue(owners.getProfileOwnerFileWithTestOverride(10).exists());
- assertTrue(owners.getProfileOwnerFileWithTestOverride(11).exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists());
+ assertTrue(owners.getProfileOwnerFile(10).exists());
+ assertTrue(owners.getProfileOwnerFile(11).exists());
+ assertFalse(owners.getProfileOwnerFile(20).exists());
+ assertFalse(owners.getProfileOwnerFile(21).exists());
assertTrue(owners.hasDeviceOwner());
assertEquals(null, owners.getDeviceOwnerName());
@@ -341,20 +341,20 @@
{
final OwnersTestable owners = new OwnersTestable(getServices());
- DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+ DpmTestUtils.writeToFile(owners.getLegacyConfigFile(),
DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test05/input.xml"));
owners.load();
// The legacy file should be removed.
- assertFalse(owners.getLegacyConfigFileWithTestOverride().exists());
+ assertFalse(owners.getLegacyConfigFile().exists());
// Note device initializer is no longer supported. No need to write the DO file.
- assertFalse(owners.getDeviceOwnerFileWithTestOverride().exists());
+ assertFalse(owners.getDeviceOwnerFile().exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists());
+ assertFalse(owners.getProfileOwnerFile(10).exists());
+ assertFalse(owners.getProfileOwnerFile(11).exists());
+ assertFalse(owners.getProfileOwnerFile(20).exists());
assertFalse(owners.hasDeviceOwner());
assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
@@ -397,19 +397,19 @@
{
final OwnersTestable owners = new OwnersTestable(getServices());
- DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+ DpmTestUtils.writeToFile(owners.getLegacyConfigFile(),
DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test06/input.xml"));
owners.load();
// The legacy file should be removed.
- assertFalse(owners.getLegacyConfigFileWithTestOverride().exists());
+ assertFalse(owners.getLegacyConfigFile().exists());
- assertTrue(owners.getDeviceOwnerFileWithTestOverride().exists());
+ assertTrue(owners.getDeviceOwnerFile().exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists());
+ assertFalse(owners.getProfileOwnerFile(10).exists());
+ assertFalse(owners.getProfileOwnerFile(11).exists());
+ assertFalse(owners.getProfileOwnerFile(20).exists());
assertFalse(owners.hasDeviceOwner());
assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId());
@@ -451,16 +451,16 @@
final OwnersTestable owners = new OwnersTestable(getServices());
// First, migrate to create new-style config files.
- DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
+ DpmTestUtils.writeToFile(owners.getLegacyConfigFile(),
DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test04/input.xml"));
owners.load();
- assertFalse(owners.getLegacyConfigFileWithTestOverride().exists());
+ assertFalse(owners.getLegacyConfigFile().exists());
- assertTrue(owners.getDeviceOwnerFileWithTestOverride().exists());
- assertTrue(owners.getProfileOwnerFileWithTestOverride(10).exists());
- assertTrue(owners.getProfileOwnerFileWithTestOverride(11).exists());
+ assertTrue(owners.getDeviceOwnerFile().exists());
+ assertTrue(owners.getProfileOwnerFile(10).exists());
+ assertTrue(owners.getProfileOwnerFile(11).exists());
// Then clear all information and save.
owners.clearDeviceOwner();
@@ -475,8 +475,8 @@
owners.writeProfileOwner(21);
// Now all files should be removed.
- assertFalse(owners.getDeviceOwnerFileWithTestOverride().exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists());
- assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists());
+ assertFalse(owners.getDeviceOwnerFile().exists());
+ assertFalse(owners.getProfileOwnerFile(10).exists());
+ assertFalse(owners.getProfileOwnerFile(11).exists());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/TransferOwnershipMetadataManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/TransferOwnershipMetadataManagerTest.java
new file mode 100644
index 0000000..03cabb2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/TransferOwnershipMetadataManagerTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER;
+
+
+import static com.android.server.devicepolicy.TransferOwnershipMetadataManager
+ .OWNER_TRANSFER_METADATA_XML;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Environment;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.devicepolicy.TransferOwnershipMetadataManager.Injector;
+import com.android.server.devicepolicy.TransferOwnershipMetadataManager.Metadata;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+* Unit tests for {@link TransferOwnershipMetadataManager}.
+ *
+ * bit FrameworksServicesTests:com.android.server.devicepolicy.TransferOwnershipMetadataManagerTest
+ * runtest -x frameworks/base/services/tests/servicestests/src/com/android/server/devicepolicy/TransferOwnershipMetadataManagerTest.java
+* */
+
+@RunWith(AndroidJUnit4.class)
+public class TransferOwnershipMetadataManagerTest {
+ private final static String ADMIN_PACKAGE = "com.dummy.admin.package";
+ private final static String TARGET_PACKAGE = "com.dummy.target.package";
+ private final static int USER_ID = 123;
+ private final static Metadata TEST_PARAMS = new Metadata(ADMIN_PACKAGE,
+ TARGET_PACKAGE, USER_ID, ADMIN_TYPE_DEVICE_OWNER);
+
+ private MockInjector mMockInjector;
+
+ @Before
+ public void setUp() {
+ mMockInjector = new MockInjector();
+ getOwnerTransferParams().deleteMetadataFile();
+ }
+
+ @Test
+ public void testSave() {
+ TransferOwnershipMetadataManager paramsManager = getOwnerTransferParams();
+ assertTrue(paramsManager.saveMetadataFile(TEST_PARAMS));
+ assertTrue(paramsManager.metadataFileExists());
+ }
+
+ @Test
+ public void testFileContentValid() {
+ TransferOwnershipMetadataManager paramsManager = getOwnerTransferParams();
+ assertTrue(paramsManager.saveMetadataFile(TEST_PARAMS));
+ Path path = Paths.get(new File(mMockInjector.getOwnerTransferMetadataDir(),
+ OWNER_TRANSFER_METADATA_XML).getAbsolutePath());
+ try {
+ String contents = new String(Files.readAllBytes(path), Charset.forName("UTF-8"));
+ assertEquals(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<user-id>" + USER_ID + "</user-id>\n"
+ + "<admin-component>" + ADMIN_PACKAGE + "</admin-component>\n"
+ + "<target-component>" + TARGET_PACKAGE + "</target-component>\n"
+ + "<admin-type>" + ADMIN_TYPE_DEVICE_OWNER + "</admin-type>\n",
+ contents);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ public void testLoad() {
+ TransferOwnershipMetadataManager paramsManager = getOwnerTransferParams();
+ paramsManager.saveMetadataFile(TEST_PARAMS);
+ assertEquals(TEST_PARAMS, paramsManager.loadMetadataFile());
+ }
+
+ @Test
+ public void testDelete() {
+ TransferOwnershipMetadataManager paramsManager = getOwnerTransferParams();
+ paramsManager.saveMetadataFile(TEST_PARAMS);
+ paramsManager.deleteMetadataFile();
+ assertFalse(paramsManager.metadataFileExists());
+ }
+
+ @After
+ public void tearDown() {
+ getOwnerTransferParams().deleteMetadataFile();
+ }
+
+ private TransferOwnershipMetadataManager getOwnerTransferParams() {
+ return new TransferOwnershipMetadataManager(mMockInjector);
+ }
+
+ static class MockInjector extends Injector {
+ @Override
+ public File getOwnerTransferMetadataDir() {
+ return Environment.getExternalStorageDirectory();
+ }
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 43d026d..e2064aa 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -47,6 +47,8 @@
/**
* Test reading and writing correctly from file.
+ *
+ * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
*/
@RunWith(AndroidJUnit4.class)
public class JobStoreTest {
@@ -116,6 +118,7 @@
.setPersisted(true)
.build();
final JobStatus ts = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null);
+ ts.addInternalFlags(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION);
mTaskStoreUnderTest.add(ts);
waitForPendingIo();
@@ -128,6 +131,8 @@
assertTasksEqual(task, loadedTaskStatus.getJob());
assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(ts));
assertEquals("Different uids.", SOME_UID, loadedTaskStatus.getUid());
+ assertEquals(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION,
+ loadedTaskStatus.getInternalFlags());
compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
ts.getEarliestRunTime(), loadedTaskStatus.getEarliestRunTime());
compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
@@ -272,7 +277,7 @@
0 /* sourceUserId */, 0, 0, "someTag",
invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
- persistedExecutionTimesUTC);
+ persistedExecutionTimesUTC, 0 /* innerFlagg */);
mTaskStoreUnderTest.add(js);
waitForPendingIo();
diff --git a/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index f6a749d..35cba18 100644
--- a/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -164,6 +164,6 @@
private static JobStatus createJobStatus(JobInfo.Builder job, long earliestRunTimeElapsedMillis,
long latestRunTimeElapsedMillis) {
return new JobStatus(job.build(), 0, null, -1, 0, 0, null, earliestRunTimeElapsedMillis,
- latestRunTimeElapsedMillis, 0, 0, null);
+ latestRunTimeElapsedMillis, 0, 0, null, 0);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/servicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 15c24ac..d78af22 100644
--- a/services/tests/servicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -71,6 +71,6 @@
final JobInfo job = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build();
return new JobStatus(job, 0, null, -1, 0, 0, null, earliestRunTimeElapsedMillis,
- latestRunTimeElapsedMillis, 0, 0, null);
+ latestRunTimeElapsedMillis, 0, 0, null, 0);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index 4bdd1c5..bc61c58 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -18,11 +18,14 @@
import android.content.Context;
+import com.android.server.PersistentDataBlockManagerInternal;
+
import java.io.File;
public class LockSettingsStorageTestable extends LockSettingsStorage {
public File mStorageDir;
+ public PersistentDataBlockManagerInternal mPersistentDataBlock;
public LockSettingsStorageTestable(Context context, File storageDir) {
super(context);
@@ -53,6 +56,11 @@
userId).getAbsolutePath());
}
+ @Override
+ public PersistentDataBlockManagerInternal getPersistentDataBlock() {
+ return mPersistentDataBlock;
+ }
+
private File makeDirs(File baseDir, String filePath) {
File path = new File(filePath);
if (path.getParent() == null) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index b0325cb..237091d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -29,8 +29,12 @@
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.test.AndroidTestCase;
+import android.util.Log;
+import android.util.Log.TerribleFailure;
+import android.util.Log.TerribleFailureHandler;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
@@ -52,7 +56,7 @@
public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 33};
- LockSettingsStorage mStorage;
+ LockSettingsStorageTestable mStorage;
File mStorageDir;
private File mDb;
@@ -346,6 +350,39 @@
assertEquals(null, mStorage.readSyntheticPasswordState(10, 1234L, "state"));
}
+ public void testPersistentDataBlock_unavailable() {
+ mStorage.mPersistentDataBlock = null;
+
+ assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
+ }
+
+ public void testPersistentDataBlock_empty() {
+ mStorage.mPersistentDataBlock = mock(PersistentDataBlockManagerInternal.class);
+
+ assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
+ }
+
+ public void testPersistentDataBlock_withData() {
+ mStorage.mPersistentDataBlock = mock(PersistentDataBlockManagerInternal.class);
+ when(mStorage.mPersistentDataBlock.getFrpCredentialHandle())
+ .thenReturn(PersistentData.toBytes(PersistentData.TYPE_SP_WEAVER, SOME_USER_ID,
+ DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, PAYLOAD));
+
+ PersistentData data = mStorage.readPersistentDataBlock();
+
+ assertEquals(PersistentData.TYPE_SP_WEAVER, data.type);
+ assertEquals(SOME_USER_ID, data.userId);
+ assertEquals(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, data.qualityForUi);
+ assertArrayEquals(PAYLOAD, data.payload);
+ }
+
+ public void testPersistentDataBlock_exception() {
+ mStorage.mPersistentDataBlock = mock(PersistentDataBlockManagerInternal.class);
+ when(mStorage.mPersistentDataBlock.getFrpCredentialHandle())
+ .thenThrow(new IllegalStateException("oops"));
+ assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
+ }
+
public void testPersistentData_serializeUnserialize() {
byte[] serialized = PersistentData.toBytes(PersistentData.TYPE_SP, SOME_USER_ID,
DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, PAYLOAD);
@@ -366,6 +403,13 @@
assertSame(PersistentData.NONE, deserialized);
}
+ public void testPersistentData_unserializeInvalid() {
+ assertNotNull(suppressAndReturnWtf(() -> {
+ PersistentData deserialized = PersistentData.fromBytes(new byte[]{5});
+ assertSame(PersistentData.NONE, deserialized);
+ }));
+ }
+
public void testPersistentData_unserialize_version1() {
// This test ensures that we can read serialized VERSION_1 PersistentData even if we change
// the wire format in the future.
@@ -450,4 +494,19 @@
assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN, cred.type);
assertArrayEquals(pattern, cred.hash);
}
+
+ /**
+ * Suppresses reporting of the WTF to system_server, so we don't pollute the dropbox with
+ * intentionally caused WTFs.
+ */
+ private TerribleFailure suppressAndReturnWtf(Runnable r) {
+ TerribleFailure[] captured = new TerribleFailure[1];
+ TerribleFailureHandler prevWtfHandler = Log.setWtfHandler((t, w, s) -> captured[0] = w);
+ try {
+ r.run();
+ } finally {
+ Log.setWtfHandler(prevWtfHandler);
+ }
+ return captured[0];
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index 6edaf87..6a3a260 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -16,11 +16,11 @@
package com.android.server.locksettings.recoverablekeystore;
-import static android.security.keystore.recovery.KeychainProtectionParams.TYPE_LOCKSCREEN;
+import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_LOCKSCREEN;
-import static android.security.keystore.recovery.KeychainProtectionParams.TYPE_PASSWORD;
-import static android.security.keystore.recovery.KeychainProtectionParams.TYPE_PATTERN;
-import static android.security.keystore.recovery.KeychainProtectionParams.TYPE_PIN;
+import static android.security.keystore.recovery.KeyChainProtectionParams.UI_FORMAT_PASSWORD;
+import static android.security.keystore.recovery.KeyChainProtectionParams.UI_FORMAT_PATTERN;
+import static android.security.keystore.recovery.KeyChainProtectionParams.UI_FORMAT_PIN;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
@@ -41,7 +41,7 @@
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.recovery.KeyDerivationParams;
-import android.security.keystore.recovery.KeychainSnapshot;
+import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
@@ -190,19 +190,19 @@
@Test
public void getUiFormat_returnsPinIfPin() {
- assertEquals(TYPE_PIN,
+ assertEquals(UI_FORMAT_PIN,
KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234"));
}
@Test
public void getUiFormat_returnsPasswordIfPassword() {
- assertEquals(TYPE_PASSWORD,
+ assertEquals(UI_FORMAT_PASSWORD,
KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234a"));
}
@Test
public void getUiFormat_returnsPatternIfPattern() {
- assertEquals(TYPE_PATTERN,
+ assertEquals(UI_FORMAT_PATTERN,
KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PATTERN, "1234"));
}
@@ -287,33 +287,33 @@
mKeySyncTask.run();
- KeychainSnapshot keychainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
- KeyDerivationParams KeyDerivationParams =
- keychainSnapshot.getKeychainProtectionParams().get(0).getKeyDerivationParams();
- assertThat(KeyDerivationParams.getAlgorithm()).isEqualTo(
+ KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+ KeyDerivationParams keyDerivationParams =
+ keyChainSnapshot.getKeyChainProtectionParams().get(0).getKeyDerivationParams();
+ assertThat(keyDerivationParams.getAlgorithm()).isEqualTo(
KeyDerivationParams.ALGORITHM_SHA256);
verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
byte[] lockScreenHash = KeySyncTask.hashCredentials(
- KeyDerivationParams.getSalt(),
+ keyDerivationParams.getSalt(),
TEST_CREDENTIAL);
Long counterId = mRecoverableKeyStoreDb.getCounterId(TEST_USER_ID, TEST_RECOVERY_AGENT_UID);
counterId = 1L; // TODO: use value from the database.
assertThat(counterId).isNotNull();
byte[] recoveryKey = decryptThmEncryptedKey(
lockScreenHash,
- keychainSnapshot.getEncryptedRecoveryKeyBlob(),
+ keyChainSnapshot.getEncryptedRecoveryKeyBlob(),
/*vaultParams=*/ KeySyncUtils.packVaultParams(
mKeyPair.getPublic(),
counterId,
/*maxAttempts=*/ 10,
TEST_VAULT_HANDLE));
- List<WrappedApplicationKey> applicationKeys = keychainSnapshot.getWrappedApplicationKeys();
+ List<WrappedApplicationKey> applicationKeys = keyChainSnapshot.getWrappedApplicationKeys();
assertThat(applicationKeys).hasSize(1);
- assertThat(keychainSnapshot.getCounterId()).isEqualTo(counterId);
- assertThat(keychainSnapshot.getMaxAttempts()).isEqualTo(10);
- assertThat(keychainSnapshot.getTrustedHardwarePublicKey())
+ assertThat(keyChainSnapshot.getCounterId()).isEqualTo(counterId);
+ assertThat(keyChainSnapshot.getMaxAttempts()).isEqualTo(10);
+ assertThat(keyChainSnapshot.getTrustedHardwarePublicKey())
.isEqualTo(SecureBox.encodePublicKey(mKeyPair.getPublic()));
- assertThat(keychainSnapshot.getServerParams()).isEqualTo(TEST_VAULT_HANDLE);
+ assertThat(keyChainSnapshot.getServerParams()).isEqualTo(TEST_VAULT_HANDLE);
WrappedApplicationKey keyData = applicationKeys.get(0);
assertEquals(TEST_APP_KEY_ALIAS, keyData.getAlias());
assertThat(keyData.getAlias()).isEqualTo(keyData.getAlias());
@@ -332,14 +332,14 @@
mKeySyncTask.run();
- KeychainSnapshot keychainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
- assertThat(keychainSnapshot.getSnapshotVersion()).isEqualTo(1); // default value;
+ KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+ assertThat(keyChainSnapshot.getSnapshotVersion()).isEqualTo(1); // default value;
mRecoverableKeyStoreDb.setShouldCreateSnapshot(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, true);
mKeySyncTask.run();
- keychainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
- assertThat(keychainSnapshot.getSnapshotVersion()).isEqualTo(2); // Updated
+ keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+ assertThat(keyChainSnapshot.getSnapshotVersion()).isEqualTo(2); // Updated
}
@Test
@@ -362,10 +362,10 @@
mKeySyncTask.run();
- KeychainSnapshot keychainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
- assertThat(keychainSnapshot.getKeychainProtectionParams()).hasSize(1);
- assertThat(keychainSnapshot.getKeychainProtectionParams().get(0).getLockScreenUiFormat()).
- isEqualTo(TYPE_PASSWORD);
+ KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+ assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
+ assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
+ isEqualTo(UI_FORMAT_PASSWORD);
}
@Test
@@ -388,11 +388,11 @@
mKeySyncTask.run();
- KeychainSnapshot keychainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
- assertThat(keychainSnapshot.getKeychainProtectionParams()).hasSize(1);
+ KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+ assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
// Password with only digits is changed to pin.
- assertThat(keychainSnapshot.getKeychainProtectionParams().get(0).getLockScreenUiFormat()).
- isEqualTo(TYPE_PIN);
+ assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
+ isEqualTo(UI_FORMAT_PIN);
}
@Test
@@ -415,10 +415,10 @@
mKeySyncTask.run();
- KeychainSnapshot keychainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
- assertThat(keychainSnapshot.getKeychainProtectionParams()).hasSize(1);
- assertThat(keychainSnapshot.getKeychainProtectionParams().get(0).getLockScreenUiFormat()).
- isEqualTo(TYPE_PATTERN);
+ KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+ assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
+ assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
+ isEqualTo(UI_FORMAT_PATTERN);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index a3a2e47..c863aab 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -16,8 +16,8 @@
package com.android.server.locksettings.recoverablekeystore;
-import static android.security.keystore.recovery.KeychainProtectionParams.TYPE_LOCKSCREEN;
-import static android.security.keystore.recovery.KeychainProtectionParams.TYPE_PASSWORD;
+import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_LOCKSCREEN;
+import static android.security.keystore.recovery.KeyChainProtectionParams.UI_FORMAT_PASSWORD;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertArrayEquals;
@@ -43,7 +43,7 @@
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.recovery.KeyDerivationParams;
-import android.security.keystore.recovery.KeychainProtectionParams;
+import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.support.test.filters.SmallTest;
import android.support.test.InstrumentationRegistry;
@@ -250,9 +250,9 @@
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
ImmutableList.of(
- new KeychainProtectionParams(
+ new KeyChainProtectionParams(
TYPE_LOCKSCREEN,
- TYPE_PASSWORD,
+ UI_FORMAT_PASSWORD,
KeyDerivationParams.createSha256Params(TEST_SALT),
TEST_SECRET)));
@@ -269,9 +269,9 @@
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
ImmutableList.of(
- new KeychainProtectionParams(
+ new KeyChainProtectionParams(
TYPE_LOCKSCREEN,
- TYPE_PASSWORD,
+ UI_FORMAT_PASSWORD,
KeyDerivationParams.createSha256Params(TEST_SALT),
TEST_SECRET)));
@@ -290,9 +290,9 @@
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
ImmutableList.of(
- new KeychainProtectionParams(
+ new KeyChainProtectionParams(
TYPE_LOCKSCREEN,
- TYPE_PASSWORD,
+ UI_FORMAT_PASSWORD,
KeyDerivationParams.createSha256Params(TEST_SALT),
TEST_SECRET)));
@@ -309,9 +309,9 @@
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
ImmutableList.of(
- new KeychainProtectionParams(
+ new KeyChainProtectionParams(
TYPE_LOCKSCREEN,
- TYPE_PASSWORD,
+ UI_FORMAT_PASSWORD,
KeyDerivationParams.createSha256Params(TEST_SALT),
TEST_SECRET)));
@@ -332,7 +332,7 @@
fail("should have thrown");
} catch (UnsupportedOperationException e) {
assertThat(e.getMessage()).startsWith(
- "Only a single KeychainProtectionParams is supported");
+ "Only a single KeyChainProtectionParams is supported");
}
}
@@ -345,9 +345,9 @@
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
ImmutableList.of(
- new KeychainProtectionParams(
+ new KeyChainProtectionParams(
TYPE_LOCKSCREEN,
- TYPE_PASSWORD,
+ UI_FORMAT_PASSWORD,
KeyDerivationParams.createSha256Params(TEST_SALT),
TEST_SECRET)));
fail("should have thrown");
@@ -367,9 +367,9 @@
vaultParams,
TEST_VAULT_CHALLENGE,
ImmutableList.of(
- new KeychainProtectionParams(
+ new KeyChainProtectionParams(
TYPE_LOCKSCREEN,
- TYPE_PASSWORD,
+ UI_FORMAT_PASSWORD,
KeyDerivationParams.createSha256Params(TEST_SALT),
TEST_SECRET)));
fail("should have thrown");
@@ -400,9 +400,9 @@
TEST_PUBLIC_KEY,
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
- ImmutableList.of(new KeychainProtectionParams(
+ ImmutableList.of(new KeyChainProtectionParams(
TYPE_LOCKSCREEN,
- TYPE_PASSWORD,
+ UI_FORMAT_PASSWORD,
KeyDerivationParams.createSha256Params(TEST_SALT),
TEST_SECRET)));
@@ -424,9 +424,9 @@
TEST_PUBLIC_KEY,
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
- ImmutableList.of(new KeychainProtectionParams(
+ ImmutableList.of(new KeyChainProtectionParams(
TYPE_LOCKSCREEN,
- TYPE_PASSWORD,
+ UI_FORMAT_PASSWORD,
KeyDerivationParams.createSha256Params(TEST_SALT),
TEST_SECRET)));
byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
@@ -456,9 +456,9 @@
TEST_PUBLIC_KEY,
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
- ImmutableList.of(new KeychainProtectionParams(
+ ImmutableList.of(new KeyChainProtectionParams(
TYPE_LOCKSCREEN,
- TYPE_PASSWORD,
+ UI_FORMAT_PASSWORD,
KeyDerivationParams.createSha256Params(TEST_SALT),
TEST_SECRET)));
byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java
index 89c5c6c..d61a294 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java
@@ -3,7 +3,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
-import android.security.keystore.recovery.KeychainSnapshot;
+import android.security.keystore.recovery.KeyChainSnapshot;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -26,25 +26,25 @@
@Test
public void get_returnsSetSnapshot() {
int userId = 1000;
- KeychainSnapshot keychainSnapshot = new KeychainSnapshot(
+ KeyChainSnapshot keyChainSnapshot = new KeyChainSnapshot(
/*snapshotVersion=*/ 1,
new ArrayList<>(),
new ArrayList<>(),
new byte[0]);
- mRecoverySnapshotStorage.put(userId, keychainSnapshot);
+ mRecoverySnapshotStorage.put(userId, keyChainSnapshot);
- assertEquals(keychainSnapshot, mRecoverySnapshotStorage.get(userId));
+ assertEquals(keyChainSnapshot, mRecoverySnapshotStorage.get(userId));
}
@Test
public void remove_removesSnapshots() {
int userId = 1000;
- KeychainSnapshot keychainSnapshot = new KeychainSnapshot(
+ KeyChainSnapshot keyChainSnapshot = new KeyChainSnapshot(
/*snapshotVersion=*/ 1,
new ArrayList<>(),
new ArrayList<>(),
new byte[0]);
- mRecoverySnapshotStorage.put(userId, keychainSnapshot);
+ mRecoverySnapshotStorage.put(userId, keyChainSnapshot);
mRecoverySnapshotStorage.remove(userId);
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 49601c3..5c7348c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -497,7 +497,9 @@
new PackageParser.SigningDetails(
new Signature[] { new Signature(new byte[16]) },
2,
- new ArraySet<>());
+ new ArraySet<>(),
+ null,
+ null);
pkg.mExtras = new Bundle();
pkg.mRestrictedAccountType = "foo19";
pkg.mRequiredAccountType = "foo20";
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index e0bebee..9ae6f00 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1179,6 +1179,34 @@
}
@Test
+ public void testUpdateAppNotifyCreatorBlock() throws Exception {
+ mService.setRankingHelper(mRankingHelper);
+
+ mBinderService.setNotificationsEnabledForPackage(PKG, 0, false);
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+ assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
+ captor.getValue().getAction());
+ assertEquals(PKG, captor.getValue().getPackage());
+ assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false));
+ }
+
+ @Test
+ public void testUpdateAppNotifyCreatorUnblock() throws Exception {
+ mService.setRankingHelper(mRankingHelper);
+
+ mBinderService.setNotificationsEnabledForPackage(PKG, 0, true);
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+ assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
+ captor.getValue().getAction());
+ assertEquals(PKG, captor.getValue().getPackage());
+ assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
+ }
+
+ @Test
public void testUpdateChannelNotifyCreatorBlock() throws Exception {
mService.setRankingHelper(mRankingHelper);
when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index cb32d1f..4d458b0 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -92,6 +92,17 @@
return false;
}
+ /**
+ * Returns whether the event type is one caused by user visible
+ * interaction. Excludes those that are internally generated.
+ * @param eventType
+ * @return
+ */
+ private boolean isUserVisibleEvent(int eventType) {
+ return eventType != UsageEvents.Event.SYSTEM_INTERACTION
+ && eventType != UsageEvents.Event.STANDBY_BUCKET_CHANGED;
+ }
+
void update(String packageName, long timeStamp, int eventType) {
UsageStats usageStats = getOrCreateUsageStats(packageName);
@@ -109,7 +120,7 @@
usageStats.mLastEvent = eventType;
}
- if (eventType != UsageEvents.Event.SYSTEM_INTERACTION) {
+ if (isUserVisibleEvent(eventType)) {
usageStats.mLastTimeUsed = timeStamp;
}
usageStats.mEndTimeStamp = timeStamp;
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 979feaa..096fdcc 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -115,6 +115,26 @@
AppStandbyController mAppStandby;
+ private UsageStatsManagerInternal.AppIdleStateChangeListener mStandbyChangeListener =
+ new UsageStatsManagerInternal.AppIdleStateChangeListener() {
+ @Override
+ public void onAppIdleStateChanged(String packageName, int userId, boolean idle,
+ int bucket) {
+ Event event = new Event();
+ event.mEventType = Event.STANDBY_BUCKET_CHANGED;
+ event.mBucket = bucket;
+ event.mPackage = packageName;
+ // This will later be converted to system time.
+ event.mTimeStamp = SystemClock.elapsedRealtime();
+ mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+ }
+
+ @Override
+ public void onParoleStateChanged(boolean isParoleOn) {
+
+ }
+ };
+
public UsageStatsService(Context context) {
super(context);
}
@@ -129,6 +149,7 @@
mAppStandby = new AppStandbyController(getContext(), BackgroundThread.get().getLooper());
+ mAppStandby.addListener(mStandbyChangeListener);
File systemDataDir = new File(Environment.getDataDirectory(), "system");
mUsageStatsDir = new File(systemDataDir, "usagestats");
mUsageStatsDir.mkdirs();
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
index cc53a9c..d1ed599 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
@@ -64,6 +64,7 @@
private static final String LAST_EVENT_ATTR = "lastEvent";
private static final String TYPE_ATTR = "type";
private static final String SHORTCUT_ID_ATTR = "shortcutId";
+ private static final String STANDBY_BUCKET_ATTR = "standbyBucket";
// Time attributes stored as an offset of the beginTime.
private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
@@ -173,6 +174,9 @@
final String id = XmlUtils.readStringAttribute(parser, SHORTCUT_ID_ATTR);
event.mShortcutId = (id != null) ? id.intern() : null;
break;
+ case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
+ event.mBucket = XmlUtils.readIntAttribute(parser, STANDBY_BUCKET_ATTR, 0);
+ break;
}
if (statsOut.events == null) {
@@ -276,6 +280,10 @@
XmlUtils.writeStringAttribute(xml, SHORTCUT_ID_ATTR, event.mShortcutId);
}
break;
+ case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
+ if (event.mBucket != 0) {
+ XmlUtils.writeIntAttribute(xml, STANDBY_BUCKET_ATTR, event.mBucket);
+ }
}
xml.endTag(null, EVENT_TAG);
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index f02221c..ec12da2 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -599,6 +599,9 @@
if (event.mShortcutId != null) {
pw.printPair("shortcutId", event.mShortcutId);
}
+ if (event.mEventType == UsageEvents.Event.STANDBY_BUCKET_CHANGED) {
+ pw.printPair("standbyBucket", event.mBucket);
+ }
pw.printHexPair("flags", event.mFlags);
pw.println();
}
@@ -645,6 +648,8 @@
return "CHOOSER_ACTION";
case UsageEvents.Event.NOTIFICATION_SEEN:
return "NOTIFICATION_SEEN";
+ case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
+ return "STANDBY_BUCKET_CHANGED";
default:
return "UNKNOWN";
}
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index e633053..8c45724 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -1102,6 +1102,16 @@
"android.provider.Telephony.MMS_DOWNLOADED";
/**
+ * Broadcast Action: A debug code has been entered in the dialer. These "secret codes"
+ * are used to activate developer menus by dialing certain codes. And they are of the
+ * form {@code *#*#<code>#*#*}. The intent will have the data URI:
+ * {@code android_secret_code://<code>}.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SECRET_CODE_ACTION =
+ "android.provider.Telephony.SECRET_CODE";
+
+ /**
* Broadcast action: When the default SMS package changes,
* the previous default SMS package and the new default SMS
* package are sent this broadcast to notify them of the change.
diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index fc814be..703f96d 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -16,6 +16,8 @@
package android.telephony;
+import android.annotation.SystemApi;
+
/**
* Contains access network related constants.
*/
@@ -30,6 +32,18 @@
}
/**
+ * Wireless transportation type
+ * @hide
+ */
+ @SystemApi
+ public static final class TransportType {
+ /** Wireless Wide Area Networks (i.e. Cellular) */
+ public static final int WWAN = 1;
+ /** Wireless Local Area Networks (i.e. Wifi) */
+ public static final int WLAN = 2;
+ }
+
+ /**
* Frenquency bands for GERAN.
* http://www.etsi.org/deliver/etsi_ts/145000_145099/145005/14.00.00_60/ts_145005v140000p.pdf
*/
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 649d478..cbc9428 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -275,7 +275,6 @@
*
* @see SubscriptionManager#getSubscriptionPlans(int)
* @see SubscriptionManager#setSubscriptionPlans(int, java.util.List)
- * @hide
*/
@SystemApi
public static final String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING =
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 98ea451..0ee870a 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -251,7 +251,15 @@
*/
public static final int LISTEN_USER_MOBILE_DATA_STATE = 0x00080000;
- /*
+ /**
+ * Listen for changes to the physical channel configuration.
+ *
+ * @see #onPhysicalChannelConfigurationChanged
+ * @hide
+ */
+ public static final int LISTEN_PHYSICAL_CHANNEL_CONFIGURATION = 0x00100000;
+
+ /*
* Subscription used to listen to the phone state changes
* @hide
*/
@@ -362,7 +370,10 @@
case LISTEN_CARRIER_NETWORK_CHANGE:
PhoneStateListener.this.onCarrierNetworkChange((boolean)msg.obj);
break;
-
+ case LISTEN_PHYSICAL_CHANNEL_CONFIGURATION:
+ PhoneStateListener.this.onPhysicalChannelConfigurationChanged(
+ (List<PhysicalChannelConfig>)msg.obj);
+ break;
}
}
};
@@ -561,6 +572,16 @@
}
/**
+ * Callback invoked when the current physical channel configuration has changed
+ *
+ * @param configs List of the current {@link PhysicalChannelConfig}s
+ * @hide
+ */
+ public void onPhysicalChannelConfigurationChanged(List<PhysicalChannelConfig> configs) {
+ // default implementation empty
+ }
+
+ /**
* Callback invoked when telephony has received notice from a carrier
* app that a network action that could result in connectivity loss
* has been requested by an app using
diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.aidl b/telephony/java/android/telephony/PhysicalChannelConfig.aidl
new file mode 100644
index 0000000..651c103
--- /dev/null
+++ b/telephony/java/android/telephony/PhysicalChannelConfig.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.telephony;
+
+parcelable PhysicalChannelConfig;
\ No newline at end of file
diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.java b/telephony/java/android/telephony/PhysicalChannelConfig.java
new file mode 100644
index 0000000..651d68d
--- /dev/null
+++ b/telephony/java/android/telephony/PhysicalChannelConfig.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.annotation.IntDef;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+public final class PhysicalChannelConfig implements Parcelable {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({CONNECTION_PRIMARY_SERVING, CONNECTION_SECONDARY_SERVING})
+ public @interface ConnectionStatus {}
+
+ /**
+ * UE has connection to cell for signalling and possibly data (3GPP 36.331, 25.331).
+ */
+ public static final int CONNECTION_PRIMARY_SERVING = 1;
+
+ /**
+ * UE has connection to cell for data (3GPP 36.331, 25.331).
+ */
+ public static final int CONNECTION_SECONDARY_SERVING = 2;
+
+ /**
+ * Connection status of the cell.
+ *
+ * <p>One of {@link #CONNECTION_PRIMARY_SERVING}, {@link #CONNECTION_SECONDARY_SERVING}.
+ */
+ private int mCellConnectionStatus;
+
+ /**
+ * Cell bandwidth, in kHz.
+ */
+ private int mCellBandwidthDownlinkKhz;
+
+ public PhysicalChannelConfig(int status, int bandwidth) {
+ mCellConnectionStatus = status;
+ mCellBandwidthDownlinkKhz = bandwidth;
+ }
+
+ public PhysicalChannelConfig(Parcel in) {
+ mCellConnectionStatus = in.readInt();
+ mCellBandwidthDownlinkKhz = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mCellConnectionStatus);
+ dest.writeInt(mCellBandwidthDownlinkKhz);
+ }
+
+ /**
+ * @return Cell bandwidth, in kHz
+ */
+ public int getCellBandwidthDownlink() {
+ return mCellBandwidthDownlinkKhz;
+ }
+
+ /**
+ * Gets the connection status of the cell.
+ *
+ * @see #CONNECTION_PRIMARY_SERVING
+ * @see #CONNECTION_SECONDARY_SERVING
+ *
+ * @return Connection status of the cell
+ */
+ @ConnectionStatus
+ public int getConnectionStatus() {
+ return mCellConnectionStatus;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof PhysicalChannelConfig)) {
+ return false;
+ }
+
+ PhysicalChannelConfig config = (PhysicalChannelConfig) o;
+ return mCellConnectionStatus == config.mCellConnectionStatus
+ && mCellBandwidthDownlinkKhz == config.mCellBandwidthDownlinkKhz;
+ }
+
+ @Override
+ public int hashCode() {
+ return (mCellBandwidthDownlinkKhz * 29) + (mCellConnectionStatus * 31);
+ }
+
+ public static final Parcelable.Creator<PhysicalChannelConfig> CREATOR =
+ new Parcelable.Creator<PhysicalChannelConfig>() {
+ public PhysicalChannelConfig createFromParcel(Parcel in) {
+ return new PhysicalChannelConfig(in);
+ }
+
+ public PhysicalChannelConfig[] newArray(int size) {
+ return new PhysicalChannelConfig[size];
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index d2134f9..fc2ef27 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -51,12 +51,15 @@
"none", "poor", "moderate", "good", "great"
};
- /** @hide */
- //Use int max, as -1 is a valid value in signal strength
- public static final int INVALID = 0x7FFFFFFF;
+ /**
+ * Use Integer.MAX_VALUE because -1 is a valid value in signal strength.
+ * @hide
+ */
+ public static final int INVALID = Integer.MAX_VALUE;
private static final int LTE_RSRP_THRESHOLDS_NUM = 6;
+ /** Parameters reported by the Radio */
private int mGsmSignalStrength; // Valid values are (0-31, 99) as defined in TS 27.007 8.5
private int mGsmBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5
private int mCdmaDbm; // This value is the RSSI value
@@ -69,11 +72,13 @@
private int mLteRsrq;
private int mLteRssnr;
private int mLteCqi;
- private int mLteRsrpBoost; // offset to be reduced from the rsrp threshold while calculating
- // signal strength level
private int mTdScdmaRscp;
- private boolean isGsm; // This value is set by the ServiceStateTracker onSignalStrengthResult
+ /** Parameters from the framework */
+ private int mLteRsrpBoost; // offset to be reduced from the rsrp threshold while calculating
+ // signal strength level
+ private boolean mIsGsm; // This value is set by the ServiceStateTracker
+ // onSignalStrengthResult.
private boolean mUseOnlyRsrpForLteLevel; // Use only RSRP for the number of LTE signal bar.
// The threshold of LTE RSRP for determining the display level of LTE signal bar.
@@ -103,28 +108,12 @@
* @hide
*/
public SignalStrength() {
- mGsmSignalStrength = 99;
- mGsmBitErrorRate = -1;
- mCdmaDbm = -1;
- mCdmaEcio = -1;
- mEvdoDbm = -1;
- mEvdoEcio = -1;
- mEvdoSnr = -1;
- mLteSignalStrength = 99;
- mLteRsrp = INVALID;
- mLteRsrq = INVALID;
- mLteRssnr = INVALID;
- mLteCqi = INVALID;
- mLteRsrpBoost = 0;
- mTdScdmaRscp = INVALID;
- isGsm = true;
- mUseOnlyRsrpForLteLevel = false;
- setLteRsrpThresholds(getDefaultLteRsrpThresholds());
+ this(true);
}
/**
* This constructor is used to create SignalStrength with default
- * values and set the isGsmFlag with the value passed in the input
+ * values and set the gsmFlag with the value passed in the input
*
* @param gsmFlag true if Gsm Phone,false if Cdma phone
* @return newly created SignalStrength
@@ -143,134 +132,26 @@
mLteRsrq = INVALID;
mLteRssnr = INVALID;
mLteCqi = INVALID;
- mLteRsrpBoost = 0;
mTdScdmaRscp = INVALID;
- isGsm = gsmFlag;
+ mLteRsrpBoost = 0;
+ mIsGsm = gsmFlag;
mUseOnlyRsrpForLteLevel = false;
setLteRsrpThresholds(getDefaultLteRsrpThresholds());
}
/**
- * Constructor
+ * Constructor with all fields present
*
* @hide
*/
- public SignalStrength(int gsmSignalStrength, int gsmBitErrorRate,
+ public SignalStrength(
+ int gsmSignalStrength, int gsmBitErrorRate,
int cdmaDbm, int cdmaEcio,
int evdoDbm, int evdoEcio, int evdoSnr,
int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
- int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag, boolean lteLevelBaseOnRsrp) {
- initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
- evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
- lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag, lteLevelBaseOnRsrp);
- mTdScdmaRscp = tdScdmaRscp;
- }
-
- /**
- * Constructor
- *
- * @hide
- */
- public SignalStrength(int gsmSignalStrength, int gsmBitErrorRate,
- int cdmaDbm, int cdmaEcio,
- int evdoDbm, int evdoEcio, int evdoSnr,
- int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
- int tdScdmaRscp, boolean gsmFlag) {
- initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
- evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
- lteRsrq, lteRssnr, lteCqi, 0, gsmFlag, false);
- mTdScdmaRscp = tdScdmaRscp;
- }
-
- /**
- * Constructor
- *
- * @hide
- */
- public SignalStrength(int gsmSignalStrength, int gsmBitErrorRate,
- int cdmaDbm, int cdmaEcio,
- int evdoDbm, int evdoEcio, int evdoSnr,
- int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
- boolean gsmFlag) {
- initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
- evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
- lteRsrq, lteRssnr, lteCqi, 0, gsmFlag, false);
- }
-
- /**
- * Constructor
- *
- * @hide
- */
- public SignalStrength(int gsmSignalStrength, int gsmBitErrorRate,
- int cdmaDbm, int cdmaEcio,
- int evdoDbm, int evdoEcio, int evdoSnr,
- boolean gsmFlag) {
- initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
- evdoDbm, evdoEcio, evdoSnr, 99, INVALID,
- INVALID, INVALID, INVALID, 0, gsmFlag, false);
- }
-
- /**
- * Copy constructors
- *
- * @param s Source SignalStrength
- *
- * @hide
- */
- public SignalStrength(SignalStrength s) {
- copyFrom(s);
- }
-
- /**
- * Initialize gsm/cdma values, sets lte values to defaults.
- *
- * @param gsmSignalStrength
- * @param gsmBitErrorRate
- * @param cdmaDbm
- * @param cdmaEcio
- * @param evdoDbm
- * @param evdoEcio
- * @param evdoSnr
- * @param gsm
- *
- * @hide
- */
- public void initialize(int gsmSignalStrength, int gsmBitErrorRate,
- int cdmaDbm, int cdmaEcio,
- int evdoDbm, int evdoEcio, int evdoSnr,
- boolean gsm) {
- initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
- evdoDbm, evdoEcio, evdoSnr, 99, INVALID,
- INVALID, INVALID, INVALID, 0, gsm, false);
- }
-
- /**
- * Initialize all the values
- *
- * @param gsmSignalStrength
- * @param gsmBitErrorRate
- * @param cdmaDbm
- * @param cdmaEcio
- * @param evdoDbm
- * @param evdoEcio
- * @param evdoSnr
- * @param lteSignalStrength
- * @param lteRsrp
- * @param lteRsrq
- * @param lteRssnr
- * @param lteCqi
- * @param lteRsrpBoost
- * @param gsm
- * @param useOnlyRsrpForLteLevel
- *
- * @hide
- */
- public void initialize(int gsmSignalStrength, int gsmBitErrorRate,
- int cdmaDbm, int cdmaEcio,
- int evdoDbm, int evdoEcio, int evdoSnr,
- int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
- int lteRsrpBoost, boolean gsm, boolean useOnlyRsrpForLteLevel) {
+ int tdScdmaRscp,
+ // values Added by config
+ int lteRsrpBoost, boolean gsmFlag, boolean lteLevelBaseOnRsrp) {
mGsmSignalStrength = gsmSignalStrength;
mGsmBitErrorRate = gsmBitErrorRate;
mCdmaDbm = cdmaDbm;
@@ -283,16 +164,41 @@
mLteRsrq = lteRsrq;
mLteRssnr = lteRssnr;
mLteCqi = lteCqi;
- mLteRsrpBoost = lteRsrpBoost;
mTdScdmaRscp = INVALID;
- isGsm = gsm;
- mUseOnlyRsrpForLteLevel = useOnlyRsrpForLteLevel;
-
+ mLteRsrpBoost = lteRsrpBoost;
+ mIsGsm = gsmFlag;
+ mUseOnlyRsrpForLteLevel = lteLevelBaseOnRsrp;
setLteRsrpThresholds(getDefaultLteRsrpThresholds());
if (DBG) log("initialize: " + toString());
}
/**
+ * Constructor for only values provided by Radio HAL
+ *
+ * @hide
+ */
+ public SignalStrength(int gsmSignalStrength, int gsmBitErrorRate,
+ int cdmaDbm, int cdmaEcio,
+ int evdoDbm, int evdoEcio, int evdoSnr,
+ int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
+ int tdScdmaRscp) {
+ this(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
+ evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
+ lteRsrq, lteRssnr, lteCqi, tdScdmaRscp, 0, true, false);
+ }
+
+ /**
+ * Copy constructors
+ *
+ * @param s Source SignalStrength
+ *
+ * @hide
+ */
+ public SignalStrength(SignalStrength s) {
+ copyFrom(s);
+ }
+
+ /**
* @hide
*/
protected void copyFrom(SignalStrength s) {
@@ -308,9 +214,9 @@
mLteRsrq = s.mLteRsrq;
mLteRssnr = s.mLteRssnr;
mLteCqi = s.mLteCqi;
- mLteRsrpBoost = s.mLteRsrpBoost;
mTdScdmaRscp = s.mTdScdmaRscp;
- isGsm = s.isGsm;
+ mLteRsrpBoost = s.mLteRsrpBoost;
+ mIsGsm = s.mIsGsm;
mUseOnlyRsrpForLteLevel = s.mUseOnlyRsrpForLteLevel;
setLteRsrpThresholds(s.mLteRsrpThresholds);
}
@@ -335,40 +241,11 @@
mLteRsrq = in.readInt();
mLteRssnr = in.readInt();
mLteCqi = in.readInt();
- mLteRsrpBoost = in.readInt();
mTdScdmaRscp = in.readInt();
- isGsm = (in.readInt() != 0);
- mUseOnlyRsrpForLteLevel = (in.readInt() != 0);
- for (int i = 0; i < LTE_RSRP_THRESHOLDS_NUM; i++) {
- mLteRsrpThresholds[i] = in.readInt();
- }
- }
-
- /**
- * Make a SignalStrength object from the given parcel as passed up by
- * the ril which does not have isGsm. isGsm will be changed by ServiceStateTracker
- * so the default is a don't care.
- *
- * @hide
- */
- public static SignalStrength makeSignalStrengthFromRilParcel(Parcel in) {
- if (DBG) log("Size of signalstrength parcel:" + in.dataSize());
-
- SignalStrength ss = new SignalStrength();
- ss.mGsmSignalStrength = in.readInt();
- ss.mGsmBitErrorRate = in.readInt();
- ss.mCdmaDbm = in.readInt();
- ss.mCdmaEcio = in.readInt();
- ss.mEvdoDbm = in.readInt();
- ss.mEvdoEcio = in.readInt();
- ss.mEvdoSnr = in.readInt();
- ss.mLteSignalStrength = in.readInt();
- ss.mLteRsrp = in.readInt();
- ss.mLteRsrq = in.readInt();
- ss.mLteRssnr = in.readInt();
- ss.mLteCqi = in.readInt();
- ss.mTdScdmaRscp = in.readInt();
- return ss;
+ mLteRsrpBoost = in.readInt();
+ mIsGsm = in.readBoolean();
+ mUseOnlyRsrpForLteLevel = in.readBoolean();
+ in.readIntArray(mLteRsrpThresholds);
}
/**
@@ -387,13 +264,11 @@
out.writeInt(mLteRsrq);
out.writeInt(mLteRssnr);
out.writeInt(mLteCqi);
- out.writeInt(mLteRsrpBoost);
out.writeInt(mTdScdmaRscp);
- out.writeInt(isGsm ? 1 : 0);
- out.writeInt(mUseOnlyRsrpForLteLevel ? 1 : 0);
- for (int i = 0; i < LTE_RSRP_THRESHOLDS_NUM; i++) {
- out.writeInt(mLteRsrpThresholds[i]);
- }
+ out.writeInt(mLteRsrpBoost);
+ out.writeBoolean(mIsGsm);
+ out.writeBoolean(mUseOnlyRsrpForLteLevel);
+ out.writeIntArray(mLteRsrpThresholds);
}
/**
@@ -456,24 +331,24 @@
}
/**
- * Fix {@link #isGsm} based on the signal strength data.
+ * Fix {@link #mIsGsm} based on the signal strength data.
*
* @hide
*/
public void fixType() {
- isGsm = getCdmaRelatedSignalStrength() == SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ mIsGsm = getCdmaRelatedSignalStrength() == SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
}
/**
* @param true - Gsm, Lte phones
* false - Cdma phones
*
- * Used by voice phone to set the isGsm
+ * Used by voice phone to set the mIsGsm
* flag
* @hide
*/
public void setGsm(boolean gsmFlag) {
- isGsm = gsmFlag;
+ mIsGsm = gsmFlag;
}
/**
@@ -604,7 +479,7 @@
* while 4 represents a very strong signal strength.
*/
public int getLevel() {
- int level = isGsm ? getGsmRelatedSignalStrength() : getCdmaRelatedSignalStrength();
+ int level = mIsGsm ? getGsmRelatedSignalStrength() : getCdmaRelatedSignalStrength();
if (DBG) log("getLevel=" + level);
return level;
}
@@ -616,15 +491,13 @@
*/
public int getAsuLevel() {
int asuLevel = 0;
- if (isGsm) {
- if (getLteLevel() == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
- if (getTdScdmaLevel() == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
- asuLevel = getGsmAsuLevel();
- } else {
- asuLevel = getTdScdmaAsuLevel();
- }
- } else {
+ if (mIsGsm) {
+ if (mLteRsrp != SignalStrength.INVALID) {
asuLevel = getLteAsuLevel();
+ } else if (mTdScdmaRscp != SignalStrength.INVALID) {
+ asuLevel = getTdScdmaAsuLevel();
+ } else {
+ asuLevel = getGsmAsuLevel();
}
} else {
int cdmaAsuLevel = getCdmaAsuLevel();
@@ -966,7 +839,7 @@
* @return true if this is for GSM
*/
public boolean isGsm() {
- return this.isGsm;
+ return this.mIsGsm;
}
/**
@@ -1038,7 +911,7 @@
+ (mEvdoDbm * primeNum) + (mEvdoEcio * primeNum) + (mEvdoSnr * primeNum)
+ (mLteSignalStrength * primeNum) + (mLteRsrp * primeNum)
+ (mLteRsrq * primeNum) + (mLteRssnr * primeNum) + (mLteCqi * primeNum)
- + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0)
+ + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (mIsGsm ? 1 : 0)
+ (mUseOnlyRsrpForLteLevel ? 1 : 0) + (Arrays.hashCode(mLteRsrpThresholds)));
}
@@ -1073,7 +946,7 @@
&& mLteCqi == s.mLteCqi
&& mLteRsrpBoost == s.mLteRsrpBoost
&& mTdScdmaRscp == s.mTdScdmaRscp
- && isGsm == s.isGsm
+ && mIsGsm == s.mIsGsm
&& mUseOnlyRsrpForLteLevel == s.mUseOnlyRsrpForLteLevel
&& Arrays.equals(mLteRsrpThresholds, s.mLteRsrpThresholds));
}
@@ -1098,7 +971,7 @@
+ " " + mLteCqi
+ " " + mLteRsrpBoost
+ " " + mTdScdmaRscp
- + " " + (isGsm ? "gsm|lte" : "cdma")
+ + " " + (mIsGsm ? "gsm|lte" : "cdma")
+ " " + (mUseOnlyRsrpForLteLevel ? "use_only_rsrp_for_lte_level" :
"use_rsrp_and_rssnr_for_lte_level")
+ " " + (Arrays.toString(mLteRsrpThresholds)));
@@ -1153,10 +1026,10 @@
mLteRsrq = m.getInt("LteRsrq");
mLteRssnr = m.getInt("LteRssnr");
mLteCqi = m.getInt("LteCqi");
- mLteRsrpBoost = m.getInt("lteRsrpBoost");
+ mLteRsrpBoost = m.getInt("LteRsrpBoost");
mTdScdmaRscp = m.getInt("TdScdma");
- isGsm = m.getBoolean("isGsm");
- mUseOnlyRsrpForLteLevel = m.getBoolean("useOnlyRsrpForLteLevel");
+ mIsGsm = m.getBoolean("IsGsm");
+ mUseOnlyRsrpForLteLevel = m.getBoolean("UseOnlyRsrpForLteLevel");
ArrayList<Integer> lteRsrpThresholds = m.getIntegerArrayList("lteRsrpThresholds");
for (int i = 0; i < lteRsrpThresholds.size(); i++) {
mLteRsrpThresholds[i] = lteRsrpThresholds.get(i);
@@ -1182,10 +1055,10 @@
m.putInt("LteRsrq", mLteRsrq);
m.putInt("LteRssnr", mLteRssnr);
m.putInt("LteCqi", mLteCqi);
- m.putInt("lteRsrpBoost", mLteRsrpBoost);
+ m.putInt("LteRsrpBoost", mLteRsrpBoost);
m.putInt("TdScdma", mTdScdmaRscp);
- m.putBoolean("isGsm", isGsm);
- m.putBoolean("useOnlyRsrpForLteLevel", mUseOnlyRsrpForLteLevel);
+ m.putBoolean("IsGsm", mIsGsm);
+ m.putBoolean("UseOnlyRsrpForLteLevel", mUseOnlyRsrpForLteLevel);
ArrayList<Integer> lteRsrpThresholds = new ArrayList<Integer>();
for (int value : mLteRsrpThresholds) {
lteRsrpThresholds.add(value);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 57f4cf2..debf43d 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -462,8 +462,6 @@
* <p>
* Contains {@link #EXTRA_SUBSCRIPTION_INDEX} to indicate which subscription
* the user is interested in.
- *
- * @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@SystemApi
@@ -481,8 +479,6 @@
* <p>
* Contains {@link #EXTRA_SUBSCRIPTION_INDEX} to indicate which subscription
* the user is interested in.
- *
- * @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@SystemApi
@@ -1698,7 +1694,6 @@
* </ul>
*
* @param subId the subscriber this relationship applies to
- * @hide
*/
@SystemApi
public @NonNull List<SubscriptionPlan> getSubscriptionPlans(int subId) {
@@ -1728,7 +1723,6 @@
* @param plans the list of plans. The first plan is always the primary and
* most important plan. Any additional plans are secondary and
* may not be displayed or used by decision making logic.
- * @hide
*/
@SystemApi
public void setSubscriptionPlans(int subId, @NonNull List<SubscriptionPlan> plans) {
@@ -1769,7 +1763,6 @@
* be automatically cleared, or {@code 0} to leave in the
* requested state until explicitly cleared, or the next reboot,
* whichever happens first.
- * @hide
*/
@SystemApi
public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered,
@@ -1804,7 +1797,6 @@
* be automatically cleared, or {@code 0} to leave in the
* requested state until explicitly cleared, or the next reboot,
* whichever happens first.
- * @hide
*/
@SystemApi
public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested,
diff --git a/telephony/java/android/telephony/SubscriptionPlan.java b/telephony/java/android/telephony/SubscriptionPlan.java
index 265e3e7..9411652 100644
--- a/telephony/java/android/telephony/SubscriptionPlan.java
+++ b/telephony/java/android/telephony/SubscriptionPlan.java
@@ -43,7 +43,6 @@
*
* @see SubscriptionManager#setSubscriptionPlans(int, java.util.List)
* @see SubscriptionManager#getSubscriptionPlans(int)
- * @hide
*/
@SystemApi
public final class SubscriptionPlan implements Parcelable {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e33b25e..2bdbfdd 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -29,6 +29,7 @@
import android.annotation.WorkerThread;
import android.app.ActivityThread;
import android.app.PendingIntent;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
@@ -42,6 +43,7 @@
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.SystemProperties;
+import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.service.carrier.CarrierIdentifier;
import android.telecom.PhoneAccount;
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index b9ed005..88bae33 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -142,11 +142,12 @@
/**
* Gets all the profiles on eUicc.
*
+ * @param cardId The Id of the eUICC.
* @param callback The callback to get the result code and all the profiles.
*/
- public void getAllProfiles(ResultCallback<EuiccProfileInfo[]> callback) {
+ public void getAllProfiles(String cardId, ResultCallback<EuiccProfileInfo[]> callback) {
try {
- getIEuiccCardController().getAllProfiles(mContext.getOpPackageName(),
+ getIEuiccCardController().getAllProfiles(mContext.getOpPackageName(), cardId,
new IGetAllProfilesCallback.Stub() {
@Override
public void onComplete(int resultCode, EuiccProfileInfo[] profiles) {
@@ -162,12 +163,13 @@
/**
* Gets the profile of the given iccid.
*
+ * @param cardId The Id of the eUICC.
* @param iccid The iccid of the profile.
* @param callback The callback to get the result code and profile.
*/
- public void getProfile(String iccid, ResultCallback<EuiccProfileInfo> callback) {
+ public void getProfile(String cardId, String iccid, ResultCallback<EuiccProfileInfo> callback) {
try {
- getIEuiccCardController().getProfile(mContext.getOpPackageName(), iccid,
+ getIEuiccCardController().getProfile(mContext.getOpPackageName(), cardId, iccid,
new IGetProfileCallback.Stub() {
@Override
public void onComplete(int resultCode, EuiccProfileInfo profile) {
@@ -183,14 +185,16 @@
/**
* Disables the profile of the given iccid.
*
+ * @param cardId The Id of the eUICC.
* @param iccid The iccid of the profile.
* @param refresh Whether sending the REFRESH command to modem.
* @param callback The callback to get the result code.
*/
- public void disableProfile(String iccid, boolean refresh, ResultCallback<Void> callback) {
+ public void disableProfile(String cardId, String iccid, boolean refresh,
+ ResultCallback<Void> callback) {
try {
- getIEuiccCardController().disableProfile(mContext.getOpPackageName(), iccid, refresh,
- new IDisableProfileCallback.Stub() {
+ getIEuiccCardController().disableProfile(mContext.getOpPackageName(), cardId, iccid,
+ refresh, new IDisableProfileCallback.Stub() {
@Override
public void onComplete(int resultCode) {
callback.onComplete(resultCode, null);
@@ -206,15 +210,16 @@
* Switches from the current profile to another profile. The current profile will be disabled
* and the specified profile will be enabled.
*
+ * @param cardId The Id of the eUICC.
* @param iccid The iccid of the profile to switch to.
* @param refresh Whether sending the REFRESH command to modem.
* @param callback The callback to get the result code and the EuiccProfileInfo enabled.
*/
- public void switchToProfile(String iccid, boolean refresh,
+ public void switchToProfile(String cardId, String iccid, boolean refresh,
ResultCallback<EuiccProfileInfo> callback) {
try {
- getIEuiccCardController().switchToProfile(mContext.getOpPackageName(), iccid, refresh,
- new ISwitchToProfileCallback.Stub() {
+ getIEuiccCardController().switchToProfile(mContext.getOpPackageName(), cardId, iccid,
+ refresh, new ISwitchToProfileCallback.Stub() {
@Override
public void onComplete(int resultCode, EuiccProfileInfo profile) {
callback.onComplete(resultCode, profile);
@@ -227,30 +232,18 @@
}
/**
- * Gets the EID of the eUICC.
- *
- * @return The EID.
- */
- public String getEid() {
- try {
- return getIEuiccCardController().getEid();
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling getEid", e);
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Sets the nickname of the profile of the given iccid.
*
+ * @param cardId The Id of the eUICC.
* @param iccid The iccid of the profile.
* @param nickname The nickname of the profile.
* @param callback The callback to get the result code.
*/
- public void setNickname(String iccid, String nickname, ResultCallback<Void> callback) {
+ public void setNickname(String cardId, String iccid, String nickname,
+ ResultCallback<Void> callback) {
try {
- getIEuiccCardController().setNickname(mContext.getOpPackageName(), iccid, nickname,
- new ISetNicknameCallback.Stub() {
+ getIEuiccCardController().setNickname(mContext.getOpPackageName(), cardId, iccid,
+ nickname, new ISetNicknameCallback.Stub() {
@Override
public void onComplete(int resultCode) {
callback.onComplete(resultCode, null);
@@ -265,12 +258,13 @@
/**
* Deletes the profile of the given iccid from eUICC.
*
+ * @param cardId The Id of the eUICC.
* @param iccid The iccid of the profile.
* @param callback The callback to get the result code.
*/
- public void deleteProfile(String iccid, ResultCallback<Void> callback) {
+ public void deleteProfile(String cardId, String iccid, ResultCallback<Void> callback) {
try {
- getIEuiccCardController().deleteProfile(mContext.getOpPackageName(), iccid,
+ getIEuiccCardController().deleteProfile(mContext.getOpPackageName(), cardId, iccid,
new IDeleteProfileCallback.Stub() {
@Override
public void onComplete(int resultCode) {
@@ -286,13 +280,14 @@
/**
* Resets the eUICC memory.
*
+ * @param cardId The Id of the eUICC.
* @param options Bits of the options of resetting which parts of the eUICC memory. See
* EuiccCard for details.
* @param callback The callback to get the result code.
*/
- public void resetMemory(@ResetOption int options, ResultCallback<Void> callback) {
+ public void resetMemory(String cardId, @ResetOption int options, ResultCallback<Void> callback) {
try {
- getIEuiccCardController().resetMemory(mContext.getOpPackageName(), options,
+ getIEuiccCardController().resetMemory(mContext.getOpPackageName(), cardId, options,
new IResetMemoryCallback.Stub() {
@Override
public void onComplete(int resultCode) {
@@ -308,11 +303,12 @@
/**
* Gets the default SM-DP+ address from eUICC.
*
+ * @param cardId The Id of the eUICC.
* @param callback The callback to get the result code and the default SM-DP+ address.
*/
- public void getDefaultSmdpAddress(ResultCallback<String> callback) {
+ public void getDefaultSmdpAddress(String cardId, ResultCallback<String> callback) {
try {
- getIEuiccCardController().getDefaultSmdpAddress(mContext.getOpPackageName(),
+ getIEuiccCardController().getDefaultSmdpAddress(mContext.getOpPackageName(), cardId,
new IGetDefaultSmdpAddressCallback.Stub() {
@Override
public void onComplete(int resultCode, String address) {
@@ -328,11 +324,12 @@
/**
* Gets the SM-DS address from eUICC.
*
+ * @param cardId The Id of the eUICC.
* @param callback The callback to get the result code and the SM-DS address.
*/
- public void getSmdsAddress(ResultCallback<String> callback) {
+ public void getSmdsAddress(String cardId, ResultCallback<String> callback) {
try {
- getIEuiccCardController().getSmdsAddress(mContext.getOpPackageName(),
+ getIEuiccCardController().getSmdsAddress(mContext.getOpPackageName(), cardId,
new IGetSmdsAddressCallback.Stub() {
@Override
public void onComplete(int resultCode, String address) {
@@ -348,12 +345,13 @@
/**
* Sets the default SM-DP+ address of eUICC.
*
+ * @param cardId The Id of the eUICC.
* @param defaultSmdpAddress The default SM-DP+ address to set.
* @param callback The callback to get the result code.
*/
- public void setDefaultSmdpAddress(String defaultSmdpAddress, ResultCallback<Void> callback) {
+ public void setDefaultSmdpAddress(String cardId, String defaultSmdpAddress, ResultCallback<Void> callback) {
try {
- getIEuiccCardController().setDefaultSmdpAddress(mContext.getOpPackageName(),
+ getIEuiccCardController().setDefaultSmdpAddress(mContext.getOpPackageName(), cardId,
defaultSmdpAddress,
new ISetDefaultSmdpAddressCallback.Stub() {
@Override
@@ -370,11 +368,12 @@
/**
* Gets Rules Authorisation Table.
*
+ * @param cardId The Id of the eUICC.
* @param callback the callback to get the result code and the rule authorisation table.
*/
- public void getRulesAuthTable(ResultCallback<EuiccRulesAuthTable> callback) {
+ public void getRulesAuthTable(String cardId, ResultCallback<EuiccRulesAuthTable> callback) {
try {
- getIEuiccCardController().getRulesAuthTable(mContext.getOpPackageName(),
+ getIEuiccCardController().getRulesAuthTable(mContext.getOpPackageName(), cardId,
new IGetRulesAuthTableCallback.Stub() {
@Override
public void onComplete(int resultCode, EuiccRulesAuthTable rat) {
@@ -390,11 +389,12 @@
/**
* Gets the eUICC challenge for new profile downloading.
*
+ * @param cardId The Id of the eUICC.
* @param callback the callback to get the result code and the challenge.
*/
- public void getEuiccChallenge(ResultCallback<byte[]> callback) {
+ public void getEuiccChallenge(String cardId, ResultCallback<byte[]> callback) {
try {
- getIEuiccCardController().getEuiccChallenge(mContext.getOpPackageName(),
+ getIEuiccCardController().getEuiccChallenge(mContext.getOpPackageName(), cardId,
new IGetEuiccChallengeCallback.Stub() {
@Override
public void onComplete(int resultCode, byte[] challenge) {
@@ -410,11 +410,12 @@
/**
* Gets the eUICC info1 defined in GSMA RSP v2.0+ for new profile downloading.
*
+ * @param cardId The Id of the eUICC.
* @param callback the callback to get the result code and the info1.
*/
- public void getEuiccInfo1(ResultCallback<byte[]> callback) {
+ public void getEuiccInfo1(String cardId, ResultCallback<byte[]> callback) {
try {
- getIEuiccCardController().getEuiccInfo1(mContext.getOpPackageName(),
+ getIEuiccCardController().getEuiccInfo1(mContext.getOpPackageName(), cardId,
new IGetEuiccInfo1Callback.Stub() {
@Override
public void onComplete(int resultCode, byte[] info) {
@@ -430,11 +431,12 @@
/**
* Gets the eUICC info2 defined in GSMA RSP v2.0+ for new profile downloading.
*
+ * @param cardId The Id of the eUICC.
* @param callback the callback to get the result code and the info2.
*/
- public void getEuiccInfo2(ResultCallback<byte[]> callback) {
+ public void getEuiccInfo2(String cardId, ResultCallback<byte[]> callback) {
try {
- getIEuiccCardController().getEuiccInfo2(mContext.getOpPackageName(),
+ getIEuiccCardController().getEuiccInfo2(mContext.getOpPackageName(), cardId,
new IGetEuiccInfo2Callback.Stub() {
@Override
public void onComplete(int resultCode, byte[] info) {
@@ -450,6 +452,7 @@
/**
* Authenticates the SM-DP+ server by the eUICC.
*
+ * @param cardId The Id of the eUICC.
* @param matchingId the activation code token defined in GSMA RSP v2.0+ or empty when it is not
* required.
* @param serverSigned1 ASN.1 data in byte array signed and returned by the SM-DP+ server.
@@ -463,12 +466,13 @@
* @param callback the callback to get the result code and a byte array which represents a
* {@code AuthenticateServerResponse} defined in GSMA RSP v2.0+.
*/
- public void authenticateServer(String matchingId, byte[] serverSigned1,
+ public void authenticateServer(String cardId, String matchingId, byte[] serverSigned1,
byte[] serverSignature1, byte[] euiccCiPkIdToBeUsed, byte[] serverCertificate,
ResultCallback<byte[]> callback) {
try {
getIEuiccCardController().authenticateServer(
mContext.getOpPackageName(),
+ cardId,
matchingId,
serverSigned1,
serverSignature1,
@@ -489,6 +493,7 @@
/**
* Prepares the profile download request sent to SM-DP+.
*
+ * @param cardId The Id of the eUICC.
* @param hashCc the hash of confirmation code. It can be null if there is no confirmation code
* required.
* @param smdpSigned2 ASN.1 data in byte array indicating the data to be signed by the SM-DP+
@@ -500,11 +505,12 @@
* @param callback the callback to get the result code and a byte array which represents a
* {@code PrepareDownloadResponse} defined in GSMA RSP v2.0+
*/
- public void prepareDownload(@Nullable byte[] hashCc, byte[] smdpSigned2,
+ public void prepareDownload(String cardId, @Nullable byte[] hashCc, byte[] smdpSigned2,
byte[] smdpSignature2, byte[] smdpCertificate, ResultCallback<byte[]> callback) {
try {
getIEuiccCardController().prepareDownload(
mContext.getOpPackageName(),
+ cardId,
hashCc,
smdpSigned2,
smdpSignature2,
@@ -524,15 +530,17 @@
/**
* Loads a downloaded bound profile package onto the eUICC.
*
+ * @param cardId The Id of the eUICC.
* @param boundProfilePackage the Bound Profile Package data returned by SM-DP+ server.
* @param callback the callback to get the result code and a byte array which represents a
* {@code LoadBoundProfilePackageResponse} defined in GSMA RSP v2.0+.
*/
- public void loadBoundProfilePackage(byte[] boundProfilePackage,
+ public void loadBoundProfilePackage(String cardId, byte[] boundProfilePackage,
ResultCallback<byte[]> callback) {
try {
getIEuiccCardController().loadBoundProfilePackage(
mContext.getOpPackageName(),
+ cardId,
boundProfilePackage,
new ILoadBoundProfilePackageCallback.Stub() {
@Override
@@ -549,16 +557,18 @@
/**
* Cancels the current profile download session.
*
+ * @param cardId The Id of the eUICC.
* @param transactionId the transaction ID returned by SM-DP+ server.
* @param reason the cancel reason.
* @param callback the callback to get the result code and an byte[] which represents a
* {@code CancelSessionResponse} defined in GSMA RSP v2.0+.
*/
- public void cancelSession(byte[] transactionId, @CancelReason int reason,
+ public void cancelSession(String cardId, byte[] transactionId, @CancelReason int reason,
ResultCallback<byte[]> callback) {
try {
getIEuiccCardController().cancelSession(
mContext.getOpPackageName(),
+ cardId,
transactionId,
reason,
new ICancelSessionCallback.Stub() {
@@ -576,13 +586,14 @@
/**
* Lists all notifications of the given {@code notificationEvents}.
*
+ * @param cardId The Id of the eUICC.
* @param events bits of the event types ({@link EuiccNotification.Event}) to list.
* @param callback the callback to get the result code and the list of notifications.
*/
- public void listNotifications(@EuiccNotification.Event int events,
+ public void listNotifications(String cardId, @EuiccNotification.Event int events,
ResultCallback<EuiccNotification[]> callback) {
try {
- getIEuiccCardController().listNotifications(mContext.getOpPackageName(), events,
+ getIEuiccCardController().listNotifications(mContext.getOpPackageName(), cardId, events,
new IListNotificationsCallback.Stub() {
@Override
public void onComplete(int resultCode, EuiccNotification[] notifications) {
@@ -598,14 +609,15 @@
/**
* Retrieves contents of all notification of the given {@code events}.
*
+ * @param cardId The Id of the eUICC.
* @param events bits of the event types ({@link EuiccNotification.Event}) to list.
* @param callback the callback to get the result code and the list of notifications.
*/
- public void retrieveNotificationList(@EuiccNotification.Event int events,
+ public void retrieveNotificationList(String cardId, @EuiccNotification.Event int events,
ResultCallback<EuiccNotification[]> callback) {
try {
- getIEuiccCardController().retrieveNotificationList(mContext.getOpPackageName(), events,
- new IRetrieveNotificationListCallback.Stub() {
+ getIEuiccCardController().retrieveNotificationList(mContext.getOpPackageName(), cardId,
+ events, new IRetrieveNotificationListCallback.Stub() {
@Override
public void onComplete(int resultCode, EuiccNotification[] notifications) {
callback.onComplete(resultCode, notifications);
@@ -620,13 +632,15 @@
/**
* Retrieves the content of a notification of the given {@code seqNumber}.
*
+ * @param cardId The Id of the eUICC.
* @param seqNumber the sequence number of the notification.
* @param callback the callback to get the result code and the notification.
*/
- public void retrieveNotification(int seqNumber, ResultCallback<EuiccNotification> callback) {
+ public void retrieveNotification(String cardId, int seqNumber,
+ ResultCallback<EuiccNotification> callback) {
try {
- getIEuiccCardController().retrieveNotification(mContext.getOpPackageName(), seqNumber,
- new IRetrieveNotificationCallback.Stub() {
+ getIEuiccCardController().retrieveNotification(mContext.getOpPackageName(), cardId,
+ seqNumber, new IRetrieveNotificationCallback.Stub() {
@Override
public void onComplete(int resultCode, EuiccNotification notification) {
callback.onComplete(resultCode, notification);
@@ -641,13 +655,16 @@
/**
* Removes a notification from eUICC.
*
+ * @param cardId The Id of the eUICC.
* @param seqNumber the sequence number of the notification.
* @param callback the callback to get the result code.
*/
- public void removeNotificationFromList(int seqNumber, ResultCallback<Void> callback) {
+ public void removeNotificationFromList(String cardId, int seqNumber,
+ ResultCallback<Void> callback) {
try {
getIEuiccCardController().removeNotificationFromList(
mContext.getOpPackageName(),
+ cardId,
seqNumber,
new IRemoveNotificationFromListCallback.Stub() {
@Override
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index d146707..7f913ce 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
@@ -598,7 +599,11 @@
}
}
- private static IEuiccController getIEuiccController() {
+ /**
+ * @hide
+ */
+ @TestApi
+ protected IEuiccController getIEuiccController() {
return IEuiccController.Stub.asInterface(ServiceManager.getService("econtroller"));
}
}
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl
index abc55c7..e33f44c 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl
@@ -41,42 +41,49 @@
/** @hide */
interface IEuiccCardController {
- oneway void getAllProfiles(String callingPackage, in IGetAllProfilesCallback callback);
- oneway void getProfile(String callingPackage, String iccid, in IGetProfileCallback callback);
- oneway void disableProfile(String callingPackage, String iccid, boolean refresh,
+ oneway void getAllProfiles(String callingPackage, String cardId,
+ in IGetAllProfilesCallback callback);
+ oneway void getProfile(String callingPackage, String cardId, String iccid,
+ in IGetProfileCallback callback);
+ oneway void disableProfile(String callingPackage, String cardId, String iccid, boolean refresh,
in IDisableProfileCallback callback);
- oneway void switchToProfile(String callingPackage, String iccid, boolean refresh,
+ oneway void switchToProfile(String callingPackage, String cardId, String iccid, boolean refresh,
in ISwitchToProfileCallback callback);
- String getEid();
- oneway void setNickname(String callingPackage, String iccid, String nickname,
+ oneway void setNickname(String callingPackage, String cardId, String iccid, String nickname,
in ISetNicknameCallback callback);
- oneway void deleteProfile(String callingPackage, String iccid,
+ oneway void deleteProfile(String callingPackage, String cardId, String iccid,
in IDeleteProfileCallback callback);
- oneway void resetMemory(String callingPackage, int options, in IResetMemoryCallback callback);
- oneway void getDefaultSmdpAddress(String callingPackage,
+ oneway void resetMemory(String callingPackage, String cardId, int options, in IResetMemoryCallback callback);
+ oneway void getDefaultSmdpAddress(String callingPackage, String cardId,
in IGetDefaultSmdpAddressCallback callback);
- oneway void getSmdsAddress(String callingPackage, in IGetSmdsAddressCallback callback);
- oneway void setDefaultSmdpAddress(String callingPackage, String address,
+ oneway void getSmdsAddress(String callingPackage, String cardId,
+ in IGetSmdsAddressCallback callback);
+ oneway void setDefaultSmdpAddress(String callingPackage, String cardId, String address,
in ISetDefaultSmdpAddressCallback callback);
- oneway void getRulesAuthTable(String callingPackage, in IGetRulesAuthTableCallback callback);
- oneway void getEuiccChallenge(String callingPackage, in IGetEuiccChallengeCallback callback);
- oneway void getEuiccInfo1(String callingPackage, in IGetEuiccInfo1Callback callback);
- oneway void getEuiccInfo2(String callingPackage, in IGetEuiccInfo2Callback callback);
- oneway void authenticateServer(String callingPackage, String matchingId,
+ oneway void getRulesAuthTable(String callingPackage, String cardId,
+ in IGetRulesAuthTableCallback callback);
+ oneway void getEuiccChallenge(String callingPackage, String cardId,
+ in IGetEuiccChallengeCallback callback);
+ oneway void getEuiccInfo1(String callingPackage, String cardId,
+ in IGetEuiccInfo1Callback callback);
+ oneway void getEuiccInfo2(String callingPackage, String cardId,
+ in IGetEuiccInfo2Callback callback);
+ oneway void authenticateServer(String callingPackage, String cardId, String matchingId,
in byte[] serverSigned1, in byte[] serverSignature1, in byte[] euiccCiPkIdToBeUsed,
in byte[] serverCertificatein, in IAuthenticateServerCallback callback);
- oneway void prepareDownload(String callingPackage, in byte[] hashCc, in byte[] smdpSigned2,
- in byte[] smdpSignature2, in byte[] smdpCertificate, in IPrepareDownloadCallback callback);
- oneway void loadBoundProfilePackage(String callingPackage, in byte[] boundProfilePackage,
- in ILoadBoundProfilePackageCallback callback);
- oneway void cancelSession(String callingPackage, in byte[] transactionId, int reason,
- in ICancelSessionCallback callback);
- oneway void listNotifications(String callingPackage, int events,
+ oneway void prepareDownload(String callingPackage, String cardId, in byte[] hashCc,
+ in byte[] smdpSigned2, in byte[] smdpSignature2, in byte[] smdpCertificate,
+ in IPrepareDownloadCallback callback);
+ oneway void loadBoundProfilePackage(String callingPackage, String cardId,
+ in byte[] boundProfilePackage, in ILoadBoundProfilePackageCallback callback);
+ oneway void cancelSession(String callingPackage, String cardId, in byte[] transactionId,
+ int reason, in ICancelSessionCallback callback);
+ oneway void listNotifications(String callingPackage, String cardId, int events,
in IListNotificationsCallback callback);
- oneway void retrieveNotificationList(String callingPackage, int events,
+ oneway void retrieveNotificationList(String callingPackage, String cardId, int events,
in IRetrieveNotificationListCallback callback);
- oneway void retrieveNotification(String callingPackage, int seqNumber,
+ oneway void retrieveNotification(String callingPackage, String cardId, int seqNumber,
in IRetrieveNotificationCallback callback);
- oneway void removeNotificationFromList(String callingPackage, int seqNumber,
+ oneway void removeNotificationFromList(String callingPackage, String cardId, int seqNumber,
in IRemoveNotificationFromListCallback callback);
}
diff --git a/test-mock/src/android/test/mock/MockPackageManager.java b/test-mock/src/android/test/mock/MockPackageManager.java
index 41cde17..1ddc52c 100644
--- a/test-mock/src/android/test/mock/MockPackageManager.java
+++ b/test-mock/src/android/test/mock/MockPackageManager.java
@@ -1196,4 +1196,17 @@
public CharSequence getHarmfulAppWarning(String packageName) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public boolean hasSigningCertificate(
+ String packageName, byte[] certificate, @PackageManager.CertificateInputType int type) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean hasSigningCertificate(
+ int uid, byte[] certificate, @PackageManager.CertificateInputType int type) {
+ throw new UnsupportedOperationException();
+ }
+
}
diff --git a/tests/FrameworkPerf/AndroidManifest.xml b/tests/FrameworkPerf/AndroidManifest.xml
index 2591aaf..d62ef9e 100644
--- a/tests/FrameworkPerf/AndroidManifest.xml
+++ b/tests/FrameworkPerf/AndroidManifest.xml
@@ -1,5 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworkperf">
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-sdk android:minSdkVersion="5" />
diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml
index c6824ec..8697f1b 100644
--- a/tests/OneMedia/AndroidManifest.xml
+++ b/tests/OneMedia/AndroidManifest.xml
@@ -5,6 +5,7 @@
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="19"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/java/android/net/NetworkCapabilitiesTest.java
index cd2d098..2e1519b 100644
--- a/tests/net/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/java/android/net/NetworkCapabilitiesTest.java
@@ -33,12 +33,15 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import android.os.Parcel;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArraySet;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Set;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -180,4 +183,84 @@
assertEquals(20, NetworkCapabilities
.maxBandwidth(10, 20));
}
+
+ @Test
+ public void testSetUids() {
+ final NetworkCapabilities netCap = new NetworkCapabilities();
+ final Set<UidRange> uids = new ArraySet<>();
+ uids.add(new UidRange(50, 100));
+ uids.add(new UidRange(3000, 4000));
+ netCap.setUids(uids);
+ assertTrue(netCap.appliesToUid(50));
+ assertTrue(netCap.appliesToUid(80));
+ assertTrue(netCap.appliesToUid(100));
+ assertTrue(netCap.appliesToUid(3000));
+ assertTrue(netCap.appliesToUid(3001));
+ assertFalse(netCap.appliesToUid(10));
+ assertFalse(netCap.appliesToUid(25));
+ assertFalse(netCap.appliesToUid(49));
+ assertFalse(netCap.appliesToUid(101));
+ assertFalse(netCap.appliesToUid(2000));
+ assertFalse(netCap.appliesToUid(100000));
+
+ assertTrue(netCap.appliesToUidRange(new UidRange(50, 100)));
+ assertTrue(netCap.appliesToUidRange(new UidRange(70, 72)));
+ assertTrue(netCap.appliesToUidRange(new UidRange(3500, 3912)));
+ assertFalse(netCap.appliesToUidRange(new UidRange(1, 100)));
+ assertFalse(netCap.appliesToUidRange(new UidRange(49, 100)));
+ assertFalse(netCap.appliesToUidRange(new UidRange(1, 10)));
+ assertFalse(netCap.appliesToUidRange(new UidRange(60, 101)));
+ assertFalse(netCap.appliesToUidRange(new UidRange(60, 3400)));
+
+ NetworkCapabilities netCap2 = new NetworkCapabilities();
+ assertFalse(netCap2.satisfiedByUids(netCap));
+ assertFalse(netCap2.equalsUids(netCap));
+ netCap2.setUids(uids);
+ assertTrue(netCap2.satisfiedByUids(netCap));
+ assertTrue(netCap.equalsUids(netCap2));
+ assertTrue(netCap2.equalsUids(netCap));
+
+ uids.add(new UidRange(600, 700));
+ netCap2.setUids(uids);
+ assertFalse(netCap2.satisfiedByUids(netCap));
+ assertFalse(netCap.appliesToUid(650));
+ assertTrue(netCap2.appliesToUid(650));
+ netCap.combineCapabilities(netCap2);
+ assertTrue(netCap2.satisfiedByUids(netCap));
+ assertTrue(netCap.appliesToUid(650));
+ assertFalse(netCap.appliesToUid(500));
+
+ assertFalse(new NetworkCapabilities().satisfiedByUids(netCap));
+ netCap.combineCapabilities(new NetworkCapabilities());
+ assertTrue(netCap.appliesToUid(500));
+ assertTrue(netCap.appliesToUidRange(new UidRange(1, 100000)));
+ assertFalse(netCap2.appliesToUid(500));
+ assertFalse(netCap2.appliesToUidRange(new UidRange(1, 100000)));
+ assertTrue(new NetworkCapabilities().satisfiedByUids(netCap));
+ }
+
+ @Test
+ public void testParcelNetworkCapabilities() {
+ final Set<UidRange> uids = new ArraySet<>();
+ uids.add(new UidRange(50, 100));
+ uids.add(new UidRange(3000, 4000));
+ final NetworkCapabilities netCap = new NetworkCapabilities()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .setUids(uids)
+ .addCapability(NET_CAPABILITY_EIMS)
+ .addCapability(NET_CAPABILITY_NOT_METERED);
+ assertEqualsThroughMarshalling(netCap);
+ }
+
+ private void assertEqualsThroughMarshalling(NetworkCapabilities netCap) {
+ Parcel p = Parcel.obtain();
+ netCap.writeToParcel(p, /* flags */ 0);
+ p.setDataPosition(0);
+ byte[] marshalledData = p.marshall();
+
+ p = Parcel.obtain();
+ p.unmarshall(marshalledData, 0, marshalledData.length);
+ p.setDataPosition(0);
+ assertEquals(NetworkCapabilities.CREATOR.createFromParcel(p), netCap);
+ }
}
diff --git a/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java b/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
index 56b8e60..b14f550 100644
--- a/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
+++ b/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
@@ -67,7 +67,7 @@
IoUtils.deleteContents(mTestProc);
}
- mFactory = new NetworkStatsFactory(mTestProc);
+ mFactory = new NetworkStatsFactory(mTestProc, false);
}
@After
@@ -116,6 +116,20 @@
}
@Test
+ public void testNetworkStatsSummary() throws Exception {
+ stageFile(R.raw.net_dev_typical, file("net/dev"));
+
+ final NetworkStats stats = mFactory.readNetworkStatsIfaceDev();
+ assertEquals(6, stats.size());
+ assertStatsEntry(stats, "lo", UID_ALL, SET_ALL, TAG_NONE, 8308L, 8308L);
+ assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 1507570L, 489339L);
+ assertStatsEntry(stats, "ifb0", UID_ALL, SET_ALL, TAG_NONE, 52454L, 0L);
+ assertStatsEntry(stats, "ifb1", UID_ALL, SET_ALL, TAG_NONE, 52454L, 0L);
+ assertStatsEntry(stats, "sit0", UID_ALL, SET_ALL, TAG_NONE, 0L, 0L);
+ assertStatsEntry(stats, "ip6tnl0", UID_ALL, SET_ALL, TAG_NONE, 0L, 0L);
+ }
+
+ @Test
public void testNetworkStatsSingle() throws Exception {
stageFile(R.raw.xt_qtaguid_iface_typical, file("net/xt_qtaguid/iface_stat_all"));
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 70cacb3..6e643a3 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -44,6 +44,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
@@ -101,6 +102,7 @@
import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.StringNetworkSpecifier;
+import android.net.UidRange;
import android.net.metrics.IpConnectivityLog;
import android.net.util.MultinetworkPolicyTracker;
import android.os.ConditionVariable;
@@ -126,11 +128,13 @@
import com.android.internal.util.WakeupMessage;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.connectivity.ConnectivityConstants;
import com.android.server.connectivity.DefaultNetworkMetrics;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkMonitor;
+import com.android.server.connectivity.Vpn;
import com.android.server.net.NetworkPinner;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -360,7 +364,7 @@
MockNetworkAgent(int transport, LinkProperties linkProperties) {
final int type = transportToLegacyType(transport);
- final String typeName = ConnectivityManager.getNetworkTypeName(type);
+ final String typeName = ConnectivityManager.getNetworkTypeName(transport);
mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock");
mNetworkCapabilities = new NetworkCapabilities();
mNetworkCapabilities.addTransportType(transport);
@@ -377,6 +381,9 @@
case TRANSPORT_WIFI_AWARE:
mScore = 20;
break;
+ case TRANSPORT_VPN:
+ mScore = ConnectivityConstants.VPN_DEFAULT_SCORE;
+ break;
default:
throw new UnsupportedOperationException("unimplemented network type");
}
@@ -438,6 +445,11 @@
mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
}
+ public void setUids(Set<UidRange> uids) {
+ mNetworkCapabilities.setUids(uids);
+ mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+ }
+
public void setSignalStrength(int signalStrength) {
mNetworkCapabilities.setSignalStrength(signalStrength);
mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
@@ -1463,6 +1475,11 @@
return nc;
}
+ void expectCapabilitiesLike(Predicate<NetworkCapabilities> fn, MockNetworkAgent agent) {
+ CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
+ assertTrue(fn.test((NetworkCapabilities) cbi.arg));
+ }
+
void assertNoCallback() {
waitForIdle();
CallbackInfo c = mCallbacks.peek();
@@ -3625,4 +3642,76 @@
return;
}
}
+
+ @Test
+ public void testVpnNetworkActive() {
+ final int uid = Process.myUid();
+
+ final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback();
+ final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
+ final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback();
+ final NetworkRequest genericRequest = new NetworkRequest.Builder().build();
+ final NetworkRequest wifiRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI).build();
+ final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_VPN).build();
+ mCm.registerNetworkCallback(genericRequest, genericNetworkCallback);
+ mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
+ mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback);
+
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(false);
+
+ genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ vpnNetworkCallback.assertNoCallback();
+
+ // TODO : check callbacks agree with the return value of mCm.getActiveNetwork().
+ // Right now this is not possible because establish() is not adequately instrumented
+ // in this test.
+
+ final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ ranges.add(new UidRange(uid, uid));
+ vpnNetworkAgent.setUids(ranges);
+ vpnNetworkAgent.connect(false);
+
+ genericNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
+ wifiNetworkCallback.assertNoCallback();
+ vpnNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
+
+ genericNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+ vpnNetworkCallback.expectCapabilitiesLike(
+ nc -> nc.appliesToUid(uid) && !nc.appliesToUid(uid + 1), vpnNetworkAgent);
+
+ ranges.clear();
+ vpnNetworkAgent.setUids(ranges);
+
+ genericNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+ wifiNetworkCallback.assertNoCallback();
+ vpnNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+
+ ranges.add(new UidRange(uid, uid));
+ vpnNetworkAgent.setUids(ranges);
+
+ genericNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent);
+ wifiNetworkCallback.assertNoCallback();
+ vpnNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent);
+
+ mWiFiNetworkAgent.disconnect();
+
+ genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ vpnNetworkCallback.assertNoCallback();
+
+ vpnNetworkAgent.disconnect();
+
+ genericNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+ wifiNetworkCallback.assertNoCallback();
+ vpnNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+
+ mCm.unregisterNetworkCallback(genericNetworkCallback);
+ mCm.unregisterNetworkCallback(wifiNetworkCallback);
+ mCm.unregisterNetworkCallback(vpnNetworkCallback);
+ }
}
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 1618e07..66e0955 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -19,7 +19,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
@@ -136,7 +135,12 @@
verify(mMockNetd)
.ipSecDeleteSecurityAssociation(
- eq(spiResp.resourceId), anyString(), anyString(), eq(TEST_SPI));
+ eq(spiResp.resourceId),
+ anyString(),
+ anyString(),
+ eq(TEST_SPI),
+ anyInt(),
+ anyInt());
// Verify quota and RefcountedResource objects cleaned up
IpSecService.UserRecord userRecord =
@@ -168,7 +172,12 @@
verify(mMockNetd)
.ipSecDeleteSecurityAssociation(
- eq(spiResp.resourceId), anyString(), anyString(), eq(TEST_SPI));
+ eq(spiResp.resourceId),
+ anyString(),
+ anyString(),
+ eq(TEST_SPI),
+ anyInt(),
+ anyInt());
// Verify quota and RefcountedResource objects cleaned up
assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent);
@@ -219,8 +228,10 @@
anyInt(),
anyString(),
anyString(),
- anyLong(),
+ anyInt(),
eq(TEST_SPI),
+ anyInt(),
+ anyInt(),
eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
eq(AUTH_KEY),
anyInt(),
@@ -252,8 +263,10 @@
anyInt(),
anyString(),
anyString(),
- anyLong(),
+ anyInt(),
eq(TEST_SPI),
+ anyInt(),
+ anyInt(),
eq(""),
eq(new byte[] {}),
eq(0),
@@ -305,7 +318,12 @@
verify(mMockNetd)
.ipSecDeleteSecurityAssociation(
- eq(createTransformResp.resourceId), anyString(), anyString(), eq(TEST_SPI));
+ eq(createTransformResp.resourceId),
+ anyString(),
+ anyString(),
+ eq(TEST_SPI),
+ anyInt(),
+ anyInt());
// Verify quota and RefcountedResource objects cleaned up
IpSecService.UserRecord userRecord =
@@ -339,7 +357,12 @@
verify(mMockNetd)
.ipSecDeleteSecurityAssociation(
- eq(createTransformResp.resourceId), anyString(), anyString(), eq(TEST_SPI));
+ eq(createTransformResp.resourceId),
+ anyString(),
+ anyString(),
+ eq(TEST_SPI),
+ anyInt(),
+ anyInt());
// Verify quota and RefcountedResource objects cleaned up
assertEquals(0, userRecord.mTransformQuotaTracker.mCurrent);
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
index a375b60..2c94a60 100644
--- a/tests/net/java/com/android/server/IpSecServiceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -635,4 +635,25 @@
verify(mMockNetd).ipSecSetEncapSocketOwner(argThat(fdMatcher), eq(Os.getuid()));
mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
}
+
+ @Test
+ public void testReserveNetId() {
+ int start = mIpSecService.TUN_INTF_NETID_START;
+ for (int i = 0; i < mIpSecService.TUN_INTF_NETID_RANGE; i++) {
+ assertEquals(start + i, mIpSecService.reserveNetId());
+ }
+
+ // Check that resource exhaustion triggers an exception
+ try {
+ mIpSecService.reserveNetId();
+ fail("Did not throw error for all netIds reserved");
+ } catch (IllegalStateException expected) {
+ }
+
+ // Now release one and try again
+ int releasedNetId =
+ mIpSecService.TUN_INTF_NETID_START + mIpSecService.TUN_INTF_NETID_RANGE / 2;
+ mIpSecService.releaseNetId(releasedNetId);
+ assertEquals(releasedNetId, mIpSecService.reserveNetId());
+ }
}
diff --git a/tests/net/res/raw/net_dev_typical b/tests/net/res/raw/net_dev_typical
new file mode 100644
index 0000000..290bf03
--- /dev/null
+++ b/tests/net/res/raw/net_dev_typical
@@ -0,0 +1,8 @@
+Inter-| Receive | Transmit
+ face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
+ lo: 8308 116 0 0 0 0 0 0 8308 116 0 0 0 0 0 0
+rmnet0: 1507570 2205 0 0 0 0 0 0 489339 2237 0 0 0 0 0 0
+ ifb0: 52454 151 0 151 0 0 0 0 0 0 0 0 0 0 0 0
+ ifb1: 52454 151 0 151 0 0 0 0 0 0 0 0 0 0 0 0
+ sit0: 0 0 0 0 0 0 0 0 0 0 148 0 0 0 0 0
+ip6tnl0: 0 0 0 0 0 0 0 0 0 0 151 151 0 0 0 0
diff --git a/tests/notification/Android.mk b/tests/notification/Android.mk
index 0669553..255e6e7 100644
--- a/tests/notification/Android.mk
+++ b/tests/notification/Android.mk
@@ -7,7 +7,7 @@
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
LOCAL_PACKAGE_NAME := NotificationTests
LOCAL_SDK_VERSION := 21
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
index ee0e36c..81a0773 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
@@ -18,7 +18,6 @@
import java.util.LinkedList;
import java.util.List;
import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.TryCatchBlockSorter;
@@ -101,7 +100,7 @@
try {
a.analyze(owner, mn);
} catch (AnalyzerException e) {
- e.printStackTrace();
+ throw new RuntimeException("Locked region code injection: " + e.getMessage(), e);
}
InsnList instructions = mn.instructions;
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index e0e6b58..3dbb503 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -317,6 +317,7 @@
fprintf(out, "\n");
fprintf(out, "#include <stdint.h>\n");
fprintf(out, "#include <vector>\n");
+ fprintf(out, "#include <set>\n");
fprintf(out, "\n");
fprintf(out, "namespace android {\n");
@@ -361,6 +362,36 @@
fprintf(out, "};\n");
fprintf(out, "\n");
+ fprintf(out, "const static std::set<int> kAtomsWithUidField = {\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ for (vector<AtomField>::const_iterator field = atom->fields.begin();
+ field != atom->fields.end(); field++) {
+ if (field->name == "uid") {
+ string constant = make_constant_name(atom->name);
+ fprintf(out, " %s,\n", constant.c_str());
+ break;
+ }
+ }
+ }
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
+ fprintf(out, "const static std::set<int> kAtomsWithAttributionChain = {\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ for (vector<AtomField>::const_iterator field = atom->fields.begin();
+ field != atom->fields.end(); field++) {
+ if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ string constant = make_constant_name(atom->name);
+ fprintf(out, " %s,\n", constant.c_str());
+ break;
+ }
+ }
+ }
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n", maxPushedAtomId);
// Print write methods
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index e9e61a5..101b3e2 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -66,11 +66,11 @@
List<OsuProvider> getMatchingOsuProviders(in ScanResult scanResult);
- int addOrUpdateNetwork(in WifiConfiguration config);
+ int addOrUpdateNetwork(in WifiConfiguration config, String packageName);
- boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config);
+ boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config, String packageName);
- boolean removePasspointConfiguration(in String fqdn);
+ boolean removePasspointConfiguration(in String fqdn, String packageName);
List<PasspointConfiguration> getPasspointConfigurations();
@@ -80,21 +80,21 @@
void deauthenticateNetwork(long holdoff, boolean ess);
- boolean removeNetwork(int netId);
+ boolean removeNetwork(int netId, String packageName);
- boolean enableNetwork(int netId, boolean disableOthers);
+ boolean enableNetwork(int netId, boolean disableOthers, String packageName);
- boolean disableNetwork(int netId);
+ boolean disableNetwork(int netId, String packageName);
- void startScan(in ScanSettings requested, in WorkSource ws, in String packageName);
+ void startScan(in ScanSettings requested, in WorkSource ws, String packageName);
List<ScanResult> getScanResults(String callingPackage);
- void disconnect();
+ void disconnect(String packageName);
- void reconnect();
+ void reconnect(String packageName);
- void reassociate();
+ void reassociate(String packageName);
WifiInfo getConnectionInfo(String callingPackage);
@@ -108,7 +108,7 @@
boolean isDualBandSupported();
- boolean saveConfiguration();
+ boolean saveConfiguration(String packageName);
DhcpInfo getDhcpInfo();
@@ -134,7 +134,7 @@
boolean stopSoftAp();
- int startLocalOnlyHotspot(in Messenger messenger, in IBinder binder, in String packageName);
+ int startLocalOnlyHotspot(in Messenger messenger, in IBinder binder, String packageName);
void stopLocalOnlyHotspot();
@@ -146,9 +146,9 @@
WifiConfiguration getWifiApConfiguration();
- void setWifiApConfiguration(in WifiConfiguration wifiConfig);
+ void setWifiApConfiguration(in WifiConfiguration wifiConfig, String packageName);
- Messenger getWifiServiceMessenger();
+ Messenger getWifiServiceMessenger(String packageName);
void enableTdls(String remoteIPAddress, boolean enable);
@@ -165,9 +165,9 @@
void enableWifiConnectivityManager(boolean enabled);
- void disableEphemeralNetwork(String SSID);
+ void disableEphemeralNetwork(String SSID, String packageName);
- void factoryReset();
+ void factoryReset(String packageName);
Network getCurrentNetwork();
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index eaad137..c46789c 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -294,18 +294,6 @@
}
/**
- * num IP configuration failures
- * @hide
- */
- public int numIpConfigFailures;
-
- /**
- * @hide
- * Last time we blacklisted the ScanResult
- */
- public long blackListTimestamp;
-
- /**
* Status indicating the scan result does not correspond to a user's saved configuration
* @hide
* @removed
@@ -314,12 +302,6 @@
public boolean untrusted;
/**
- * Number of time we connected to it
- * @hide
- */
- public int numConnection;
-
- /**
* Number of time autojoin used it
* @hide
*/
@@ -432,12 +414,6 @@
*/
public List<String> anqpLines;
- /**
- * @hide
- * storing the raw bytes of full result IEs
- **/
- public byte[] bytes;
-
/** information elements from beacon
* @hide
*/
@@ -612,9 +588,7 @@
distanceSdCm = source.distanceSdCm;
seen = source.seen;
untrusted = source.untrusted;
- numConnection = source.numConnection;
numUsage = source.numUsage;
- numIpConfigFailures = source.numIpConfigFailures;
venueName = source.venueName;
operatorFriendlyName = source.operatorFriendlyName;
flags = source.flags;
@@ -697,9 +671,7 @@
dest.writeInt(centerFreq1);
dest.writeLong(seen);
dest.writeInt(untrusted ? 1 : 0);
- dest.writeInt(numConnection);
dest.writeInt(numUsage);
- dest.writeInt(numIpConfigFailures);
dest.writeString((venueName != null) ? venueName.toString() : "");
dest.writeString((operatorFriendlyName != null) ? operatorFriendlyName.toString() : "");
dest.writeLong(this.flags);
@@ -779,9 +751,7 @@
sr.seen = in.readLong();
sr.untrusted = in.readInt() != 0;
- sr.numConnection = in.readInt();
sr.numUsage = in.readInt();
- sr.numIpConfigFailures = in.readInt();
sr.venueName = in.readString();
sr.operatorFriendlyName = in.readString();
sr.flags = in.readLong();
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index e4b510d..50ae905 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1132,7 +1132,7 @@
*/
private int addOrUpdateNetwork(WifiConfiguration config) {
try {
- return mService.addOrUpdateNetwork(config);
+ return mService.addOrUpdateNetwork(config, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1153,7 +1153,7 @@
*/
public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
try {
- if (!mService.addOrUpdatePasspointConfiguration(config)) {
+ if (!mService.addOrUpdatePasspointConfiguration(config, mContext.getOpPackageName())) {
throw new IllegalArgumentException();
}
} catch (RemoteException e) {
@@ -1170,7 +1170,7 @@
*/
public void removePasspointConfiguration(String fqdn) {
try {
- if (!mService.removePasspointConfiguration(fqdn)) {
+ if (!mService.removePasspointConfiguration(fqdn, mContext.getOpPackageName())) {
throw new IllegalArgumentException();
}
} catch (RemoteException e) {
@@ -1256,7 +1256,7 @@
*/
public boolean removeNetwork(int netId) {
try {
- return mService.removeNetwork(netId);
+ return mService.removeNetwork(netId, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1302,7 +1302,7 @@
boolean success;
try {
- success = mService.enableNetwork(netId, attemptConnect);
+ success = mService.enableNetwork(netId, attemptConnect, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1328,7 +1328,7 @@
*/
public boolean disableNetwork(int netId) {
try {
- return mService.disableNetwork(netId);
+ return mService.disableNetwork(netId, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1341,7 +1341,7 @@
*/
public boolean disconnect() {
try {
- mService.disconnect();
+ mService.disconnect(mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1356,7 +1356,7 @@
*/
public boolean reconnect() {
try {
- mService.reconnect();
+ mService.reconnect(mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1371,7 +1371,7 @@
*/
public boolean reassociate() {
try {
- mService.reassociate();
+ mService.reassociate(mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1701,7 +1701,7 @@
@Deprecated
public boolean saveConfiguration() {
try {
- return mService.saveConfiguration();
+ return mService.saveConfiguration(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2136,7 +2136,7 @@
@RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
public boolean setWifiApConfiguration(WifiConfiguration wifiConfig) {
try {
- mService.setWifiApConfiguration(wifiConfig);
+ mService.setWifiApConfiguration(wifiConfig, mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -3052,7 +3052,7 @@
public void disableEphemeralNetwork(String SSID) {
if (SSID == null) throw new IllegalArgumentException("SSID cannot be null");
try {
- mService.disableEphemeralNetwork(SSID);
+ mService.disableEphemeralNetwork(SSID, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3093,7 +3093,7 @@
*/
public Messenger getWifiServiceMessenger() {
try {
- return mService.getWifiServiceMessenger();
+ return mService.getWifiServiceMessenger(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3619,7 +3619,7 @@
*/
public void factoryReset() {
try {
- mService.factoryReset();
+ mService.factoryReset(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}