Merge "Update account visibility API."
diff --git a/api/current.txt b/api/current.txt
index 395d7aa..f27cf03 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -37387,6 +37387,7 @@
field public static final java.lang.String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool";
field public static final java.lang.String KEY_APN_EXPAND_BOOL = "apn_expand_bool";
field public static final java.lang.String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool";
+ field public static final java.lang.String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array";
field public static final java.lang.String KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL = "carrier_allow_turnoff_ims_bool";
field public static final java.lang.String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings";
field public static final java.lang.String KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL = "carrier_force_disable_etws_cmas_test_bool";
diff --git a/api/system-current.txt b/api/system-current.txt
index 8bedf98..8f91d85 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -26056,7 +26056,7 @@
field public static final java.lang.String EXTRA_SEQUENCE = "android.net.extra.SEQUENCE";
}
- public static final class NetworkRecommendationProvider.ResultCallback {
+ public static class NetworkRecommendationProvider.ResultCallback {
method public void onResult(android.net.RecommendationResult);
}
@@ -26131,9 +26131,11 @@
}
public final class RecommendationResult implements android.os.Parcelable {
- ctor public RecommendationResult(android.net.wifi.WifiConfiguration);
+ method public static android.net.RecommendationResult createDoNotConnectRecommendation();
+ method public static android.net.RecommendationResult createConnectRecommendation(android.net.wifi.WifiConfiguration);
method public int describeContents();
method public android.net.wifi.WifiConfiguration getWifiConfiguration();
+ method public boolean hasRecommendation();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.net.RecommendationResult> CREATOR;
}
@@ -26194,8 +26196,9 @@
ctor public ScoredNetwork(android.net.NetworkKey, android.net.RssiCurve, boolean, android.os.Bundle);
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final java.lang.String ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL = "android.net.attributes.key.HAS_CAPTIVE_PORTAL";
+ field public static final java.lang.String ATTRIBUTES_KEY_RANKING_SCORE_OFFSET = "android.net.attributes.key.RANKING_SCORE_OFFSET";
field public static final android.os.Parcelable.Creator<android.net.ScoredNetwork> CREATOR;
- field public static final java.lang.String EXTRA_HAS_CAPTIVE_PORTAL = "android.net.extra.HAS_CAPTIVE_PORTAL";
field public final android.os.Bundle attributes;
field public final boolean meteredHint;
field public final android.net.NetworkKey networkKey;
@@ -27182,6 +27185,7 @@
field public int level;
field public java.lang.CharSequence operatorFriendlyName;
field public long timestamp;
+ field public boolean untrusted;
field public java.lang.CharSequence venueName;
}
@@ -27225,6 +27229,7 @@
field public boolean hiddenSSID;
field public java.lang.String lastUpdateName;
field public int lastUpdateUid;
+ field public boolean meteredHint;
field public int networkId;
field public int numAssociation;
field public int numScorerOverride;
@@ -35753,6 +35758,8 @@
method public static boolean putInt(android.content.ContentResolver, java.lang.String, int);
method public static boolean putLong(android.content.ContentResolver, java.lang.String, long);
method public static boolean putString(android.content.ContentResolver, java.lang.String, java.lang.String);
+ method public static boolean putString(android.content.ContentResolver, java.lang.String, java.lang.String, java.lang.String, boolean);
+ method public static void resetToDefaults(android.content.ContentResolver, java.lang.String);
field public static final java.lang.String ADB_ENABLED = "adb_enabled";
field public static final java.lang.String AIRPLANE_MODE_ON = "airplane_mode_on";
field public static final java.lang.String AIRPLANE_MODE_RADIOS = "airplane_mode_radios";
@@ -35826,6 +35833,8 @@
method public static boolean putInt(android.content.ContentResolver, java.lang.String, int);
method public static boolean putLong(android.content.ContentResolver, java.lang.String, long);
method public static boolean putString(android.content.ContentResolver, java.lang.String, java.lang.String);
+ method public static boolean putString(android.content.ContentResolver, java.lang.String, java.lang.String, java.lang.String, boolean);
+ method public static void resetToDefaults(android.content.ContentResolver, java.lang.String);
method public static final deprecated void setLocationProviderEnabled(android.content.ContentResolver, java.lang.String, boolean);
field public static final java.lang.String ACCESSIBILITY_DISPLAY_INVERSION_ENABLED = "accessibility_display_inversion_enabled";
field public static final java.lang.String ACCESSIBILITY_ENABLED = "accessibility_enabled";
@@ -40468,6 +40477,7 @@
field public static final java.lang.String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool";
field public static final java.lang.String KEY_APN_EXPAND_BOOL = "apn_expand_bool";
field public static final java.lang.String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool";
+ field public static final java.lang.String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array";
field public static final java.lang.String KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL = "carrier_allow_turnoff_ims_bool";
field public static final java.lang.String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings";
field public static final java.lang.String KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL = "carrier_force_disable_etws_cmas_test_bool";
diff --git a/api/test-current.txt b/api/test-current.txt
index e43d1b5..4f1f147 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -37505,6 +37505,7 @@
field public static final java.lang.String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool";
field public static final java.lang.String KEY_APN_EXPAND_BOOL = "apn_expand_bool";
field public static final java.lang.String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool";
+ field public static final java.lang.String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array";
field public static final java.lang.String KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL = "carrier_allow_turnoff_ims_bool";
field public static final java.lang.String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings";
field public static final java.lang.String KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL = "carrier_force_disable_etws_cmas_test_bool";
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 9c17780..72ccf72 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -185,6 +185,11 @@
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
+ w.setOnWindowSwipeDismissedCallback(() -> {
+ if (mCancelable) {
+ cancel();
+ }
+ });
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 94d24e4..db1162a 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -51,6 +51,7 @@
import android.view.Display;
import android.view.DisplayAdjustments;
+import dalvik.system.BaseDexClassLoader;
import dalvik.system.VMRuntime;
import java.io.File;
@@ -600,6 +601,40 @@
VMRuntime.registerAppInfo(profileFile.getPath(), mApplicationInfo.dataDir,
codePaths.toArray(new String[codePaths.size()]), foreignDexProfilesFile.getPath());
+
+ // Setup the reporter to notify package manager of any relevant dex loads.
+ // At this point the primary apk is loaded and will not be reported.
+ // Anything loaded from now on will be tracked as a potential secondary
+ // or foreign dex file. The goal is to enable:
+ // 1) monitoring and compilation of secondary dex file
+ // 2) track foreign dex file usage (used to determined the
+ // compilation filter of apks).
+ if (BaseDexClassLoader.getReporter() != DexLoadReporter.INSTANCE) {
+ // Set the dex load reporter if not already set.
+ // Note that during the app's life cycle different LoadedApks may be
+ // created and loaded (e.g. if two different apps share the same runtime).
+ BaseDexClassLoader.setReporter(DexLoadReporter.INSTANCE);
+ }
+ }
+
+ private static class DexLoadReporter implements BaseDexClassLoader.Reporter {
+ private static final DexLoadReporter INSTANCE = new DexLoadReporter();
+
+ private DexLoadReporter() {}
+
+ @Override
+ public void report(List<String> dexPaths) {
+ if (dexPaths.isEmpty()) {
+ return;
+ }
+ String packageName = ActivityThread.currentPackageName();
+ try {
+ ActivityThread.getPackageManager().notifyDexLoad(
+ packageName, dexPaths, VMRuntime.getRuntime().vmInstructionSet());
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re);
+ }
+ }
}
/**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index b641e63..6172884 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -385,7 +385,7 @@
"com.android.server.action.BUGREPORT_SHARING_DECLINED";
/**
- * Action: Bugreport has been collected and is dispatched to {@link DevicePolicyManagerService}.
+ * Action: Bugreport has been collected and is dispatched to {@code DevicePolicyManagerService}.
*
* @hide
*/
@@ -1060,6 +1060,15 @@
= "android.app.action.SET_NEW_PARENT_PROFILE_PASSWORD";
/**
+ * Broadcast action: Tell the status bar to open the device monitoring dialog, e.g. when
+ * Network logging was enabled and the user tapped the notification.
+ * <p class="note">This is a protected intent that can only be sent by the system.</p>
+ * @hide
+ */
+ public static final String ACTION_SHOW_DEVICE_MONITORING_DIALOG
+ = "android.app.action.SHOW_DEVICE_MONITORING_DIALOG";
+
+ /**
* Flag used by {@link #addCrossProfileIntentFilter} to allow activities in
* the parent profile to access intents sent from the managed profile.
* That is, when an app in the managed profile calls
@@ -1165,7 +1174,7 @@
public @interface UserProvisioningState {}
/**
- * Result code for {@link checkProvisioningPreCondition}.
+ * Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
* {@link #ACTION_PROVISION_MANAGED_PROFILE}, {@link #ACTION_PROVISION_MANAGED_USER} and
@@ -1176,7 +1185,7 @@
public static final int CODE_OK = 0;
/**
- * Result code for {@link checkProvisioningPreCondition}.
+ * Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
* {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the device already has a device
@@ -1187,7 +1196,7 @@
public static final int CODE_HAS_DEVICE_OWNER = 1;
/**
- * Result code for {@link checkProvisioningPreCondition}.
+ * Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
* {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the user has a profile owner and for
@@ -1198,7 +1207,7 @@
public static final int CODE_USER_HAS_PROFILE_OWNER = 2;
/**
- * Result code for {@link checkProvisioningPreCondition}.
+ * Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
* {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the user isn't running.
@@ -1208,7 +1217,7 @@
public static final int CODE_USER_NOT_RUNNING = 3;
/**
- * Result code for {@link checkProvisioningPreCondition}.
+ * Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
* {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} if the device has already been setup and
@@ -1233,7 +1242,7 @@
public static final int CODE_ACCOUNTS_NOT_EMPTY = 6;
/**
- * Result code for {@link checkProvisioningPreCondition}.
+ * Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
* {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} if the user is not a system user.
@@ -1243,7 +1252,7 @@
public static final int CODE_NOT_SYSTEM_USER = 7;
/**
- * Result code for {@link checkProvisioningPreCondition}.
+ * Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
* {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} and {@link #ACTION_PROVISION_MANAGED_USER}
@@ -1254,7 +1263,7 @@
public static final int CODE_HAS_PAIRED = 8;
/**
- * Result code for {@link checkProvisioningPreCondition}.
+ * Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} and
* {@link #ACTION_PROVISION_MANAGED_USER} on devices which do not support managed users.
@@ -1265,7 +1274,7 @@
public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9;
/**
- * Result code for {@link checkProvisioningPreCondition}.
+ * Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_USER} if the user is a system user.
*
@@ -1274,7 +1283,7 @@
public static final int CODE_SYSTEM_USER = 10;
/**
- * Result code for {@link checkProvisioningPreCondition}.
+ * Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the user cannot have more
* managed profiles.
@@ -1284,7 +1293,7 @@
public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11;
/**
- * Result code for {@link checkProvisioningPreCondition}.
+ * Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_USER} and
* {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} on devices not running with split system
@@ -1295,7 +1304,7 @@
public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12;
/**
- * Result code for {@link checkProvisioningPreCondition}.
+ * Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
* {@link #ACTION_PROVISION_MANAGED_PROFILE}, {@link #ACTION_PROVISION_MANAGED_USER} and
@@ -1307,7 +1316,7 @@
public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13;
/**
- * Result code for {@link checkProvisioningPreCondition}.
+ * Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the device the user is a
* system user on a split system user device.
@@ -1317,7 +1326,17 @@
public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14;
/**
- * Result codes for {@link checkProvisioningPreCondition} indicating all the provisioning pre
+ * Result code for {@link #checkProvisioningPreCondition}.
+ *
+ * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when adding a managed profile is
+ * disallowed by {@link UserManager#DISALLOW_ADD_MANAGED_PROFILE}.
+ *
+ * @hide
+ */
+ public static final int CODE_ADD_MANAGED_PROFILE_DISALLOWED = 15;
+
+ /**
+ * Result codes for {@link #checkProvisioningPreCondition} indicating all the provisioning pre
* conditions.
*
* @hide
@@ -1327,7 +1346,7 @@
CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED,
CODE_MANAGED_USERS_NOT_SUPPORTED, CODE_SYSTEM_USER, CODE_CANNOT_ADD_MANAGED_PROFILE,
CODE_NOT_SYSTEM_USER_SPLIT, CODE_DEVICE_ADMIN_NOT_SUPPORTED,
- CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER})
+ CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER, CODE_ADD_MANAGED_PROFILE_DISALLOWED})
public @interface ProvisioningPreCondition {}
/**
@@ -2294,7 +2313,7 @@
* Determine whether the current password the user has set is sufficient to meet the policy
* requirements (e.g. quality, minimum length) that have been requested by the admins of this
* user and its participating profiles. Restrictions on profiles that have a separate challenge
- * are not taken into account.
+ * are not taken into account. The user must be unlocked in order to perform the check.
* <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
@@ -2307,6 +2326,7 @@
* @return Returns true if the password meets the current requirements, else false.
* @throws SecurityException if the calling application does not own an active administrator
* that uses {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD}
+ * @throws InvalidStateException if the user is not unlocked.
*/
public boolean isActivePasswordSufficient() {
if (mService != null) {
@@ -3817,6 +3837,19 @@
/**
* @hide
*/
+ public void reportPasswordChanged(@UserIdInt int userId) {
+ if (mService != null) {
+ try {
+ mService.reportPasswordChanged(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
public void reportFailedPasswordAttempt(int userHandle) {
if (mService != null) {
try {
@@ -6184,34 +6217,40 @@
}
/**
- * Returns if provisioning a managed profile or device is possible or not.
+ * Returns whether it is possible for the caller to initiate provisioning of a managed profile
+ * or device, setting itself as the device or profile owner.
+ *
* @param action One of {@link #ACTION_PROVISION_MANAGED_DEVICE},
* {@link #ACTION_PROVISION_MANAGED_PROFILE}.
- * @return if provisioning a managed profile or device is possible or not.
+ * @return whether provisioning a managed profile or device is possible.
* @throws IllegalArgumentException if the supplied action is not valid.
*/
- public boolean isProvisioningAllowed(String action) {
+ public boolean isProvisioningAllowed(@NonNull String action) {
throwIfParentInstance("isProvisioningAllowed");
try {
- return mService.isProvisioningAllowed(action);
+ return mService.isProvisioningAllowed(action, mContext.getPackageName());
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
- * Checks if provisioning a managed profile or device is possible and returns one of the
- * {@link ProvisioningPreCondition}.
+ * Checks whether it is possible to initiate provisioning a managed device,
+ * profile or user, setting the given package as owner.
*
* @param action One of {@link #ACTION_PROVISION_MANAGED_DEVICE},
* {@link #ACTION_PROVISION_MANAGED_PROFILE},
* {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE},
* {@link #ACTION_PROVISION_MANAGED_USER}
+ * @param packageName The package of the component that would be set as device, user, or profile
+ * owner.
+ * @return A {@link ProvisioningPreCondition} value indicating whether provisioning is allowed.
* @hide
*/
- public @ProvisioningPreCondition int checkProvisioningPreCondition(String action) {
+ public @ProvisioningPreCondition int checkProvisioningPreCondition(
+ String action, @NonNull String packageName) {
try {
- return mService.checkProvisioningPreCondition(action);
+ return mService.checkProvisioningPreCondition(action, packageName);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -6940,8 +6979,8 @@
* @hide
* Force update user setup completed status. This API has no effect on user build.
* @throws {@link SecurityException} if the caller has no
- * {@link android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS} or the caller is
- * not {@link UserHandle.SYSTEM_USER}
+ * {@code android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS} or the caller is
+ * not {@link UserHandle#SYSTEM_USER}
*/
public void forceUpdateUserSetupComplete() {
try {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3e22825..9be694e 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -123,6 +123,7 @@
boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle);
void setActivePasswordState(in PasswordMetrics metrics, int userHandle);
+ void reportPasswordChanged(int userId);
void reportFailedPasswordAttempt(int userHandle);
void reportSuccessfulPasswordAttempt(int userHandle);
void reportFailedFingerprintAttempt(int userHandle);
@@ -269,8 +270,8 @@
boolean setPermissionGrantState(in ComponentName admin, String packageName,
String permission, int grantState);
int getPermissionGrantState(in ComponentName admin, String packageName, String permission);
- boolean isProvisioningAllowed(String action);
- int checkProvisioningPreCondition(String action);
+ boolean isProvisioningAllowed(String action, String packageName);
+ int checkProvisioningPreCondition(String action, String packageName);
void setKeepUninstalledPackages(in ComponentName admin,in List<String> packageList);
List<String> getKeepUninstalledPackages(in ComponentName admin);
boolean isManagedProfile(in ComponentName admin);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 11da8dd..f0f1d99 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2499,9 +2499,8 @@
* for high frequency calls.
* </p>
*
- * @param service Identifies the service to be started. The Intent must be either
- * fully explicit (supplying a component name) or specify a specific package
- * name it is targetted to. Additional values
+ * @param service Identifies the service to be started. The Intent must be
+ * fully explicit (supplying a component name). Additional values
* may be included in the Intent extras to supply arguments along with
* this specific start call.
*
@@ -2579,10 +2578,8 @@
* {@link #registerReceiver}, since the lifetime of this BroadcastReceiver
* is tied to another object (the one that registered it).</p>
*
- * @param service Identifies the service to connect to. The Intent may
- * specify either an explicit component name, or a logical
- * description (action, category, etc) to match an
- * {@link IntentFilter} published by a service.
+ * @param service Identifies the service to connect to. The Intent must
+ * specify an explicit component name.
* @param conn Receives information as the service is started and stopped.
* This must be a valid ServiceConnection object; it must not be null.
* @param flags Operation options for the binding. May be 0,
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 16af5e1..c7984b3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1131,6 +1131,12 @@
* for compatibility with old applications. If you don't set a ClipData,
* it will be copied there for you when calling {@link Context#startActivity(Intent)}.
* <p>
+ * Starting from {@link android.os.Build.VERSION_CODES#O}, if
+ * {@link #CATEGORY_TYPED_OPENABLE} is passed, then the Uris passed in
+ * either {@link #EXTRA_STREAM} or via {@link #setClipData(ClipData)} may
+ * be openable only as asset typed files using
+ * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}.
+ * <p>
* Optional standard extras, which may be interpreted by some recipients as
* appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC},
* {@link #EXTRA_BCC}, {@link #EXTRA_SUBJECT}.
@@ -1169,6 +1175,12 @@
* for compatibility with old applications. If you don't set a ClipData,
* it will be copied there for you when calling {@link Context#startActivity(Intent)}.
* <p>
+ * Starting from {@link android.os.Build.VERSION_CODES#O}, if
+ * {@link #CATEGORY_TYPED_OPENABLE} is passed, then the Uris passed in
+ * either {@link #EXTRA_STREAM} or via {@link #setClipData(ClipData)} may
+ * be openable only as asset typed files using
+ * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}.
+ * <p>
* Optional standard extras, which may be interpreted by some recipients as
* appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC},
* {@link #EXTRA_BCC}, {@link #EXTRA_SUBJECT}.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index d753a6e..b9b61db 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -458,6 +458,15 @@
void notifyPackageUse(String packageName, int reason);
/**
+ * Notify the package manager that a list of dex files have been loaded.
+ *
+ * @param loadingPackageName the name of the package who performs the load
+ * @param dexPats the list of the dex files paths that have been loaded
+ * @param loaderIsa the ISA of the loader process
+ */
+ void notifyDexLoad(String loadingPackageName, in List<String> dexPaths, String loaderIsa);
+
+ /**
* Ask the package manager to perform dex-opt (if needed) on the given
* package if it already hasn't done so.
*
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 1ba68a6..2590a6b 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -221,4 +221,9 @@
public abstract void requestEphemeralResolutionPhaseTwo(EphemeralResponse responseObj,
Intent origIntent, String resolvedType, Intent launchIntent, String callingPackage,
int userId);
+
+ /**
+ * @return The SetupWizard package name.
+ */
+ public abstract String getSetupWizardPackageName();
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 32bf66a..2236291 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -24,7 +24,10 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Intent;
@@ -58,7 +61,6 @@
import android.view.Gravity;
import java.io.File;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -85,7 +87,6 @@
import libcore.io.IoUtils;
import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
-import static android.content.pm.ActivityInfo.FLAG_IMMERSIVE;
import static android.content.pm.ActivityInfo.FLAG_ON_TOP_LAUNCHER;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
@@ -2103,63 +2104,22 @@
sa.recycle();
- if (minCode != null) {
- boolean allowedCodename = false;
- for (String codename : SDK_CODENAMES) {
- if (minCode.equals(codename)) {
- allowedCodename = true;
- break;
- }
- }
- if (!allowedCodename) {
- if (SDK_CODENAMES.length > 0) {
- outError[0] = "Requires development platform " + minCode
- + " (current platform is any of "
- + Arrays.toString(SDK_CODENAMES) + ")";
- } else {
- outError[0] = "Requires development platform " + minCode
- + " but this is a release platform.";
- }
- mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
- return null;
- }
- pkg.applicationInfo.minSdkVersion =
- android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
- } else if (minVers > SDK_VERSION) {
- outError[0] = "Requires newer sdk version #" + minVers
- + " (current version is #" + SDK_VERSION + ")";
+ final int minSdkVersion = PackageParser.computeMinSdkVersion(minVers, minCode,
+ SDK_VERSION, SDK_CODENAMES, outError);
+ if (minSdkVersion < 0) {
mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
return null;
- } else {
- pkg.applicationInfo.minSdkVersion = minVers;
}
- if (targetCode != null) {
- boolean allowedCodename = false;
- for (String codename : SDK_CODENAMES) {
- if (targetCode.equals(codename)) {
- allowedCodename = true;
- break;
- }
- }
- if (!allowedCodename) {
- if (SDK_CODENAMES.length > 0) {
- outError[0] = "Requires development platform " + targetCode
- + " (current platform is any of "
- + Arrays.toString(SDK_CODENAMES) + ")";
- } else {
- outError[0] = "Requires development platform " + targetCode
- + " but this is a release platform.";
- }
- mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
- return null;
- }
- // If the code matches, it definitely targets this SDK.
- pkg.applicationInfo.targetSdkVersion
- = android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
- } else {
- pkg.applicationInfo.targetSdkVersion = targetVers;
+ final int targetSdkVersion = PackageParser.computeTargetSdkVersion(targetVers,
+ targetCode, SDK_VERSION, SDK_CODENAMES, outError);
+ if (targetSdkVersion < 0) {
+ mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
+ return null;
}
+
+ pkg.applicationInfo.minSdkVersion = minSdkVersion;
+ pkg.applicationInfo.targetSdkVersion = targetSdkVersion;
}
XmlUtils.skipCurrentTag(parser);
@@ -2407,6 +2367,137 @@
return pkg;
}
+ /**
+ * Computes the targetSdkVersion to use at runtime. If the package is not
+ * compatible with this platform, populates {@code outError[0]} with an
+ * error message.
+ * <p>
+ * If {@code targetCode} is not specified, e.g. the value is {@code null},
+ * then the {@code targetVers} will be returned unmodified.
+ * <p>
+ * Otherwise, the behavior varies based on whether the current platform
+ * is a pre-release version, e.g. the {@code platformSdkCodenames} array
+ * has length > 0:
+ * <ul>
+ * <li>If this is a pre-release platform and the value specified by
+ * {@code targetCode} is contained within the array of allowed pre-release
+ * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}.
+ * <li>If this is a released platform, this method will return -1 to
+ * indicate that the package is not compatible with this platform.
+ * </ul>
+ *
+ * @param targetVers targetSdkVersion number, if specified in the
+ * application manifest, or 0 otherwise
+ * @param targetCode targetSdkVersion code, if specified in the application
+ * manifest, or {@code null} otherwise
+ * @param platformSdkVersion platform SDK version number, typically
+ * Build.VERSION.SDK_INT
+ * @param platformSdkCodenames array of allowed pre-release SDK codenames
+ * for this platform
+ * @param outError output array to populate with error, if applicable
+ * @return the targetSdkVersion to use at runtime, or -1 if the package is
+ * not compatible with this platform
+ * @hide Exposed for unit testing only.
+ */
+ @TestApi
+ public static int computeTargetSdkVersion(@IntRange(from = 0) int targetVers,
+ @Nullable String targetCode, @IntRange(from = 1) int platformSdkVersion,
+ @NonNull String[] platformSdkCodenames, @NonNull String[] outError) {
+ // If it's a release SDK, return the version number unmodified.
+ if (targetCode == null) {
+ return targetVers;
+ }
+
+ // If it's a pre-release SDK and the codename matches this platform, it
+ // definitely targets this SDK.
+ if (ArrayUtils.contains(platformSdkCodenames, targetCode)) {
+ return Build.VERSION_CODES.CUR_DEVELOPMENT;
+ }
+
+ // Otherwise, we're looking at an incompatible pre-release SDK.
+ if (platformSdkCodenames.length > 0) {
+ outError[0] = "Requires development platform " + targetCode
+ + " (current platform is any of "
+ + Arrays.toString(platformSdkCodenames) + ")";
+ } else {
+ outError[0] = "Requires development platform " + targetCode
+ + " but this is a release platform.";
+ }
+ return -1;
+ }
+
+ /**
+ * Computes the minSdkVersion to use at runtime. If the package is not
+ * compatible with this platform, populates {@code outError[0]} with an
+ * error message.
+ * <p>
+ * If {@code minCode} is not specified, e.g. the value is {@code null},
+ * then behavior varies based on the {@code platformSdkVersion}:
+ * <ul>
+ * <li>If the platform SDK version is greater than or equal to the
+ * {@code minVers}, returns the {@code mniVers} unmodified.
+ * <li>Otherwise, returns -1 to indicate that the package is not
+ * compatible with this platform.
+ * </ul>
+ * <p>
+ * Otherwise, the behavior varies based on whether the current platform
+ * is a pre-release version, e.g. the {@code platformSdkCodenames} array
+ * has length > 0:
+ * <ul>
+ * <li>If this is a pre-release platform and the value specified by
+ * {@code targetCode} is contained within the array of allowed pre-release
+ * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}.
+ * <li>If this is a released platform, this method will return -1 to
+ * indicate that the package is not compatible with this platform.
+ * </ul>
+ *
+ * @param minVers minSdkVersion number, if specified in the application
+ * manifest, or 1 otherwise
+ * @param minCode minSdkVersion code, if specified in the application
+ * manifest, or {@code null} otherwise
+ * @param platformSdkVersion platform SDK version number, typically
+ * Build.VERSION.SDK_INT
+ * @param platformSdkCodenames array of allowed prerelease SDK codenames
+ * for this platform
+ * @param outError output array to populate with error, if applicable
+ * @return the minSdkVersion to use at runtime, or -1 if the package is not
+ * compatible with this platform
+ * @hide Exposed for unit testing only.
+ */
+ @TestApi
+ public static int computeMinSdkVersion(@IntRange(from = 1) int minVers,
+ @Nullable String minCode, @IntRange(from = 1) int platformSdkVersion,
+ @NonNull String[] platformSdkCodenames, @NonNull String[] outError) {
+ // If it's a release SDK, make sure we meet the minimum SDK requirement.
+ if (minCode == null) {
+ if (minVers <= platformSdkVersion) {
+ return minVers;
+ }
+
+ // We don't meet the minimum SDK requirement.
+ outError[0] = "Requires newer sdk version #" + minVers
+ + " (current version is #" + platformSdkVersion + ")";
+ return -1;
+ }
+
+ // If it's a pre-release SDK and the codename matches this platform, we
+ // definitely meet the minimum SDK requirement.
+ if (ArrayUtils.contains(platformSdkCodenames, minCode)) {
+ return Build.VERSION_CODES.CUR_DEVELOPMENT;
+ }
+
+ // Otherwise, we're looking at an incompatible pre-release SDK.
+ if (platformSdkCodenames.length > 0) {
+ outError[0] = "Requires development platform " + minCode
+ + " (current platform is any of "
+ + Arrays.toString(platformSdkCodenames) + ")";
+ } else {
+ outError[0] = "Requires development platform " + minCode
+ + " but this is a release platform.";
+ }
+ return -1;
+ }
+
private FeatureInfo parseUsesFeature(Resources res, AttributeSet attrs) {
FeatureInfo fi = new FeatureInfo();
TypedArray sa = res.obtainAttributes(attrs,
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 28e392e..d1d5f40 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -230,6 +230,13 @@
public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL";
/**
+ * Key for passing a user agent string to the captive portal login activity.
+ * {@hide}
+ */
+ public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT =
+ "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
+
+ /**
* Broadcast action to indicate the change of data activity status
* (idle or active) on a network in a recent period.
* The network becomes active when data transmission is started, or
diff --git a/core/java/android/net/NetworkRecommendationProvider.java b/core/java/android/net/NetworkRecommendationProvider.java
index af5a052c..16ae867 100644
--- a/core/java/android/net/NetworkRecommendationProvider.java
+++ b/core/java/android/net/NetworkRecommendationProvider.java
@@ -75,7 +75,7 @@
* A callback implementing applications should invoke when a {@link RecommendationResult}
* is available.
*/
- public static final class ResultCallback {
+ public static class ResultCallback {
private final IRemoteCallback mCallback;
private final int mSequence;
private final AtomicBoolean mCallbackRun;
diff --git a/core/java/android/net/RecommendationResult.java b/core/java/android/net/RecommendationResult.java
index a330d84..70cf09c 100644
--- a/core/java/android/net/RecommendationResult.java
+++ b/core/java/android/net/RecommendationResult.java
@@ -16,6 +16,7 @@
package android.net;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.net.wifi.WifiConfiguration;
@@ -23,6 +24,7 @@
import android.os.Parcelable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
/**
* The result of a network recommendation.
@@ -34,7 +36,32 @@
public final class RecommendationResult implements Parcelable {
private final WifiConfiguration mWifiConfiguration;
- public RecommendationResult(@Nullable WifiConfiguration wifiConfiguration) {
+ /**
+ * Create a {@link RecommendationResult} that indicates that no network connection should be
+ * attempted at this time.
+ *
+ * @return a {@link RecommendationResult}
+ */
+ public static RecommendationResult createDoNotConnectRecommendation() {
+ return new RecommendationResult((WifiConfiguration) null);
+ }
+
+ /**
+ * Create a {@link RecommendationResult} that indicates that a connection attempt should be
+ * made for the given Wi-Fi network.
+ *
+ * @param wifiConfiguration {@link WifiConfiguration} with at least SSID and BSSID set.
+ * @return a {@link RecommendationResult}
+ */
+ public static RecommendationResult createConnectRecommendation(
+ @NonNull WifiConfiguration wifiConfiguration) {
+ Preconditions.checkNotNull(wifiConfiguration, "wifiConfiguration must not be null");
+ Preconditions.checkNotNull(wifiConfiguration.SSID, "SSID must not be null");
+ Preconditions.checkNotNull(wifiConfiguration.BSSID, "BSSID must not be null");
+ return new RecommendationResult(wifiConfiguration);
+ }
+
+ private RecommendationResult(@Nullable WifiConfiguration wifiConfiguration) {
mWifiConfiguration = wifiConfiguration;
}
@@ -43,14 +70,29 @@
}
/**
- * @return The recommended {@link WifiConfiguration} to connect to. A {@code null} value
- * indicates that no WiFi connection should be attempted at this time.
+ * @return {@code true} if a network recommendation exists. {@code false} indicates that
+ * no connection should be attempted at this time.
*/
- public WifiConfiguration getWifiConfiguration() {
+ public boolean hasRecommendation() {
+ return mWifiConfiguration != null;
+ }
+
+ /**
+ * @return The recommended {@link WifiConfiguration} to connect to. A {@code null} value
+ * is returned if {@link #hasRecommendation} returns {@code false}.
+ */
+ @Nullable public WifiConfiguration getWifiConfiguration() {
return mWifiConfiguration;
}
@Override
+ public String toString() {
+ return "RecommendationResult{" +
+ "mWifiConfiguration=" + mWifiConfiguration +
+ "}";
+ }
+
+ @Override
public int describeContents() {
return 0;
}
diff --git a/core/java/android/net/ScoredNetwork.java b/core/java/android/net/ScoredNetwork.java
index cf81e91..94e5187 100644
--- a/core/java/android/net/ScoredNetwork.java
+++ b/core/java/android/net/ScoredNetwork.java
@@ -16,11 +16,14 @@
package android.net;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.Math;
+import java.lang.UnsupportedOperationException;
import java.util.Objects;
/**
@@ -43,7 +46,17 @@
* <p>
* If no value is associated with this key then it's unknown.
*/
- public static final String EXTRA_HAS_CAPTIVE_PORTAL = "android.net.extra.HAS_CAPTIVE_PORTAL";
+ public static final String ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL =
+ "android.net.attributes.key.HAS_CAPTIVE_PORTAL";
+
+ /**
+ * Key used with the {@link #attributes} bundle to define the rankingScoreOffset int value.
+ *
+ * <p>The rankingScoreOffset is used when calculating the ranking score used to rank networks
+ * against one another. See {@link #calculateRankingScore} for more information.
+ */
+ public static final String ATTRIBUTES_KEY_RANKING_SCORE_OFFSET =
+ "android.net.attributes.key.RANKING_SCORE_OFFSET";
/** A {@link NetworkKey} uniquely identifying this network. */
public final NetworkKey networkKey;
@@ -71,8 +84,10 @@
* An additional collection of optional attributes set by
* the Network Recommendation Provider.
*
- * @see #EXTRA_HAS_CAPTIVE_PORTAL
+ * @see #ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL
+ * @see #ATTRIBUTES_KEY_RANKING_SCORE_OFFSET_KEY
*/
+ @Nullable
public final Bundle attributes;
/**
@@ -122,7 +137,7 @@
* @param attributes optional provider specific attributes
*/
public ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve, boolean meteredHint,
- Bundle attributes) {
+ @Nullable Bundle attributes) {
this.networkKey = networkKey;
this.rssiCurve = rssiCurve;
this.meteredHint = meteredHint;
@@ -136,7 +151,7 @@
} else {
rssiCurve = null;
}
- meteredHint = in.readByte() != 0;
+ meteredHint = (in.readByte() == 1);
attributes = in.readBundle();
}
@@ -156,7 +171,6 @@
}
out.writeByte((byte) (meteredHint ? 1 : 0));
out.writeBundle(attributes);
-
}
@Override
@@ -187,6 +201,54 @@
'}';
}
+ /**
+ * Returns true if a ranking score can be calculated for this network.
+ *
+ * @hide
+ */
+ public boolean hasRankingScore() {
+ return (rssiCurve != null)
+ || (attributes != null
+ && attributes.containsKey(ATTRIBUTES_KEY_RANKING_SCORE_OFFSET));
+ }
+
+ /**
+ * Returns a ranking score for a given RSSI which can be used to comparatively
+ * rank networks.
+ *
+ * <p>The score obtained by the rssiCurve is bitshifted left by 8 bits to expand it to an
+ * integer and then the offset is added. If the addition operation overflows or underflows,
+ * Integer.MAX_VALUE and Integer.MIN_VALUE will be returned respectively.
+ *
+ * <p>{@link #hasRankingScore} should be called first to ensure this network is capable
+ * of returning a ranking score.
+ *
+ * @throws UnsupportedOperationException if there is no RssiCurve and no rankingScoreOffset
+ * for this network (hasRankingScore returns false).
+ *
+ * @hide
+ */
+ public int calculateRankingScore(int rssi) throws UnsupportedOperationException {
+ if (!hasRankingScore()) {
+ throw new UnsupportedOperationException(
+ "Either rssiCurve or rankingScoreOffset is required to calculate the "
+ + "ranking score");
+ }
+
+ int offset = 0;
+ if (attributes != null) {
+ offset += attributes.getInt(ATTRIBUTES_KEY_RANKING_SCORE_OFFSET, 0 /* default */);
+ }
+
+ int score = (rssiCurve == null) ? 0 : rssiCurve.lookupScore(rssi) << Byte.SIZE;
+
+ try {
+ return Math.addExact(score, offset);
+ } catch (ArithmeticException e) {
+ return (score < 0) ? Integer.MIN_VALUE : Integer.MAX_VALUE;
+ }
+ }
+
public static final Parcelable.Creator<ScoredNetwork> CREATOR =
new Parcelable.Creator<ScoredNetwork>() {
@Override
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 37222ad..f1b00d6 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -16,11 +16,17 @@
package android.provider;
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.annotation.UserIdInt;
import android.app.ActivityThread;
import android.app.AppOpsManager;
import android.app.Application;
@@ -66,6 +72,8 @@
import com.android.internal.widget.ILockSettings;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
@@ -347,7 +355,6 @@
* Input: Nothing.
* <p>
* Output: Nothing.
-
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_WIFI_SETTINGS =
@@ -1211,8 +1218,6 @@
public static final String ACTION_HOME_SETTINGS
= "android.settings.HOME_SETTINGS";
-
-
/**
* Activity Action: Show Default apps settings.
* <p>
@@ -1387,6 +1392,21 @@
*/
public static final String CALL_METHOD_USER_KEY = "_user";
+ /**
+ * @hide - Boolean argument extra to the fast-path call()-based requests
+ */
+ public static final String CALL_METHOD_MAKE_DEFAULT_KEY = "_make_default";
+
+ /**
+ * @hide - User handle argument extra to the fast-path call()-based requests
+ */
+ public static final String CALL_METHOD_RESET_MODE_KEY = "_reset_mode";
+
+ /**
+ * @hide - String argument extra to the fast-path call()-based requests
+ */
+ public static final String CALL_METHOD_TAG_KEY = "_tag";
+
/** @hide - Private call() method to write to 'system' table */
public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system";
@@ -1396,6 +1416,12 @@
/** @hide - Private call() method to write to 'global' table */
public static final String CALL_METHOD_PUT_GLOBAL= "PUT_global";
+ /** @hide - Private call() method to reset to defaults the 'global' table */
+ public static final String CALL_METHOD_RESET_GLOBAL = "RESET_global";
+
+ /** @hide - Private call() method to reset to defaults the 'secure' table */
+ public static final String CALL_METHOD_RESET_SECURE = "RESET_secure";
+
/**
* Activity Extra: Limit available options in launched activity based on the given authority.
* <p>
@@ -1472,6 +1498,55 @@
"android.settings.extra.do_not_disturb_mode_minutes";
/**
+ * Reset mode: reset to defaults only settings changed by the
+ * calling package. If there is a default set the setting
+ * will be set to it, otherwise the setting will be deleted.
+ * This is the only type of reset available to non-system clients.
+ * @hide
+ */
+ public static final int RESET_MODE_PACKAGE_DEFAULTS = 1;
+
+ /**
+ * Reset mode: reset all settings set by untrusted packages, which is
+ * packages that aren't a part of the system, to the current defaults.
+ * If there is a default set the setting will be set to it, otherwise
+ * the setting will be deleted. This mode is only available to the system.
+ * @hide
+ */
+ public static final int RESET_MODE_UNTRUSTED_DEFAULTS = 2;
+
+ /**
+ * Reset mode: delete all settings set by untrusted packages, which is
+ * packages that aren't a part of the system. If a setting is set by an
+ * untrusted package it will be deleted if its default is not provided
+ * by the system, otherwise the setting will be set to its default.
+ * This mode is only available to the system.
+ * @hide
+ */
+ public static final int RESET_MODE_UNTRUSTED_CHANGES = 3;
+
+ /**
+ * Reset mode: reset all settings to defaults specified by trusted
+ * packages, which is packages that are a part of the system, and
+ * delete all settings set by untrusted packages. If a setting has
+ * a default set by a system package it will be set to the default,
+ * otherwise the setting will be deleted. This mode is only available
+ * to the system.
+ * @hide
+ */
+ public static final int RESET_MODE_TRUSTED_DEFAULTS = 4;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ RESET_MODE_PACKAGE_DEFAULTS,
+ RESET_MODE_UNTRUSTED_DEFAULTS,
+ RESET_MODE_UNTRUSTED_CHANGES,
+ RESET_MODE_TRUSTED_DEFAULTS
+ })
+ public @interface ResetMode{}
+
+ /**
* Activity Extra: Number of certificates
* <p>
* This can be passed as an extra field to the {@link #ACTION_MONITORING_CERT_INFO}
@@ -1574,22 +1649,44 @@
}
}
+ private static final class ContentProviderHolder {
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final Uri mUri;
+ @GuardedBy("mLock")
+ private IContentProvider mContentProvider;
+
+ public ContentProviderHolder(Uri uri) {
+ mUri = uri;
+ }
+
+ public IContentProvider getProvider(ContentResolver contentResolver) {
+ synchronized (mLock) {
+ if (mContentProvider == null) {
+ mContentProvider = contentResolver
+ .acquireProvider(mUri.getAuthority());
+ }
+ return mContentProvider;
+ }
+ }
+ }
+
// Thread-safe.
private static class NameValueCache {
private static final boolean DEBUG = false;
- private final Uri mUri;
-
private static final String[] SELECT_VALUE_PROJECTION = new String[] {
Settings.NameValueTable.VALUE
};
+
private static final String NAME_EQ_PLACEHOLDER = "name=?";
// Must synchronize on 'this' to access mValues and mValuesVersion.
private final HashMap<String, String> mValues = new HashMap<>();
- // Initially null; set lazily and held forever. Synchronized on 'this'.
- private IContentProvider mContentProvider = null;
+ private final Uri mUri;
+ private final ContentProviderHolder mProviderHolder;
// The method we'll call (or null, to not use) on the provider
// for the fast path of retrieving settings.
@@ -1599,30 +1696,27 @@
@GuardedBy("this")
private GenerationTracker mGenerationTracker;
- public NameValueCache(Uri uri, String getCommand, String setCommand) {
+ public NameValueCache(Uri uri, String getCommand, String setCommand,
+ ContentProviderHolder providerHolder) {
mUri = uri;
mCallGetCommand = getCommand;
mCallSetCommand = setCommand;
- }
-
- private IContentProvider lazyGetProvider(ContentResolver cr) {
- IContentProvider cp = null;
- synchronized (NameValueCache.this) {
- cp = mContentProvider;
- if (cp == null) {
- cp = mContentProvider = cr.acquireProvider(mUri.getAuthority());
- }
- }
- return cp;
+ mProviderHolder = providerHolder;
}
public boolean putStringForUser(ContentResolver cr, String name, String value,
- final int userHandle) {
+ String tag, boolean makeDefault, final int userHandle) {
try {
Bundle arg = new Bundle();
arg.putString(Settings.NameValueTable.VALUE, value);
arg.putInt(CALL_METHOD_USER_KEY, userHandle);
- IContentProvider cp = lazyGetProvider(cr);
+ if (tag != null) {
+ arg.putString(CALL_METHOD_TAG_KEY, tag);
+ }
+ if (makeDefault) {
+ arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true);
+ }
+ IContentProvider cp = mProviderHolder.getProvider(cr);
cp.call(cr.getPackageName(), mCallSetCommand, name, arg);
} catch (RemoteException e) {
Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
@@ -1653,7 +1747,7 @@
+ " by user " + UserHandle.myUserId() + " so skipping cache");
}
- IContentProvider cp = lazyGetProvider(cr);
+ IContentProvider cp = mProviderHolder.getProvider(cr);
// Try the fast path first, not using query(). If this
// fails (alternate Settings provider that doesn't support
@@ -1802,10 +1896,14 @@
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/system");
+ private static final ContentProviderHolder sProviderHolder =
+ new ContentProviderHolder(CONTENT_URI);
+
private static final NameValueCache sNameValueCache = new NameValueCache(
CONTENT_URI,
CALL_METHOD_GET_SYSTEM,
- CALL_METHOD_PUT_SYSTEM);
+ CALL_METHOD_PUT_SYSTEM,
+ sProviderHolder);
private static final HashSet<String> MOVED_TO_SECURE;
static {
@@ -1997,7 +2095,7 @@
+ " to android.provider.Settings.Global, value is unchanged.");
return false;
}
- return sNameValueCache.putStringForUser(resolver, name, value, userHandle);
+ return sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle);
}
/**
@@ -4153,11 +4251,15 @@
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/secure");
+ private static final ContentProviderHolder sProviderHolder =
+ new ContentProviderHolder(CONTENT_URI);
+
// Populated lazily, guarded by class object:
private static final NameValueCache sNameValueCache = new NameValueCache(
CONTENT_URI,
CALL_METHOD_GET_SECURE,
- CALL_METHOD_PUT_SECURE);
+ CALL_METHOD_PUT_SECURE,
+ sProviderHolder);
private static ILockSettings sLockSettings = null;
@@ -4357,6 +4459,13 @@
/** @hide */
public static boolean putStringForUser(ContentResolver resolver, String name, String value,
int userHandle) {
+ return putStringForUser(resolver, name, value, null, false, userHandle);
+ }
+
+ /** @hide */
+ public static boolean putStringForUser(@NonNull ContentResolver resolver,
+ @NonNull String name, @Nullable String value, @Nullable String tag,
+ boolean makeDefault, @UserIdInt int userHandle) {
if (LOCATION_MODE.equals(name)) {
// Map LOCATION_MODE to underlying location provider storage API
return setLocationModeForUser(resolver, Integer.parseInt(value), userHandle);
@@ -4364,9 +4473,113 @@
if (MOVED_TO_GLOBAL.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ " to android.provider.Settings.Global");
- return Global.putStringForUser(resolver, name, value, userHandle);
+ return Global.putStringForUser(resolver, name, value,
+ tag, makeDefault, userHandle);
}
- return sNameValueCache.putStringForUser(resolver, name, value, userHandle);
+ return sNameValueCache.putStringForUser(resolver, name, value, tag,
+ makeDefault, userHandle);
+ }
+
+ /**
+ * Store a name/value pair into the database.
+ * <p>
+ * The method takes an optional tag to associate with the setting
+ * which can be used to clear only settings made by your package and
+ * associated with this tag by passing the tag to {@link
+ * #resetToDefaults(ContentResolver, String)}. Anyone can override
+ * the current tag. Also if another package changes the setting
+ * then the tag will be set to the one specified in the set call
+ * which can be null. Also any of the settings setters that do not
+ * take a tag as an argument effectively clears the tag.
+ * </p><p>
+ * For example, if you set settings A and B with tags T1 and T2 and
+ * another app changes setting A (potentially to the same value), it
+ * can assign to it a token T3 (note that now the package that changed
+ * the setting is not yours). Now if you reset your changes for T1 and
+ * T2 only setting B will be reset and A not (as it was changed by
+ * another package) but since A did not change you are in the desired
+ * initial state. Now if the other app changes the value of A (assuming
+ * you registered an observer in the beginning) you would detect that
+ * the setting was changed by another app and handle this appropriately
+ * (ignore, set back to some value, etc).
+ * </p><p>
+ * Also the method takes an argument whether to make the value the
+ * default for this setting. If the system already specified a default
+ * value, then the one passed in here will <strong>not</strong>
+ * be set as the default.
+ * </p>
+ *
+ * @param resolver to access the database with.
+ * @param name to store.
+ * @param value to associate with the name.
+ * @param tag to associate with the setting.
+ * @param makeDefault whether to make the value the default one.
+ * @return true if the value was set, false on database errors.
+ *
+ * @see #resetToDefaults(ContentResolver, String)
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public static boolean putString(@NonNull ContentResolver resolver,
+ @NonNull String name, @Nullable String value, @Nullable String tag,
+ boolean makeDefault) {
+ return putStringForUser(resolver, name, value, tag, makeDefault,
+ UserHandle.myUserId());
+ }
+
+ /**
+ * Reset the settings to their defaults. This would reset <strong>only</strong>
+ * settings set by the caller's package. Think of it of a way to undo your own
+ * changes to the global settings. Passing in the optional tag will reset only
+ * settings changed by your package and associated with this tag.
+ *
+ * @param resolver Handle to the content resolver.
+ * @param tag Optional tag which should be associated with the settings to reset.
+ *
+ * @see #putString(ContentResolver, String, String, String, boolean)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static void resetToDefaults(@NonNull ContentResolver resolver,
+ @Nullable String tag) {
+ resetToDefaultsAsUser(resolver, tag, RESET_MODE_PACKAGE_DEFAULTS,
+ UserHandle.myUserId());
+ }
+
+ /**
+ * Reset the settings to their defaults for a given user with a specific mode. The
+ * optional tag argument is valid only for {@link #RESET_MODE_PACKAGE_DEFAULTS}
+ * allowing resetting the settings made by a package and associated with the tag.
+ *
+ * @param resolver Handle to the content resolver.
+ * @param tag Optional tag which should be associated with the settings to reset.
+ * @param mode The reset mode.
+ * @param userHandle The user for which to reset to defaults.
+ *
+ * @see #RESET_MODE_PACKAGE_DEFAULTS
+ * @see #RESET_MODE_UNTRUSTED_DEFAULTS
+ * @see #RESET_MODE_UNTRUSTED_CHANGES
+ * @see #RESET_MODE_TRUSTED_DEFAULTS
+ *
+ * @hide
+ */
+ public static void resetToDefaultsAsUser(@NonNull ContentResolver resolver,
+ @Nullable String tag, @ResetMode int mode, @IntRange(from = 0) int userHandle) {
+ try {
+ Bundle arg = new Bundle();
+ arg.putInt(CALL_METHOD_USER_KEY, userHandle);
+ if (tag != null) {
+ arg.putString(CALL_METHOD_TAG_KEY, tag);
+ }
+ arg.putInt(CALL_METHOD_RESET_MODE_KEY, mode);
+ IContentProvider cp = sProviderHolder.getProvider(resolver);
+ cp.call(resolver.getPackageName(), CALL_METHOD_RESET_SECURE, null, arg);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Can't reset do defaults for " + CONTENT_URI, e);
+ }
}
/**
@@ -9133,11 +9346,15 @@
LOW_POWER_MODE_TRIGGER_LEVEL
};
+ private static final ContentProviderHolder sProviderHolder =
+ new ContentProviderHolder(CONTENT_URI);
+
// Populated lazily, guarded by class object:
- private static NameValueCache sNameValueCache = new NameValueCache(
+ private static final NameValueCache sNameValueCache = new NameValueCache(
CONTENT_URI,
CALL_METHOD_GET_GLOBAL,
- CALL_METHOD_PUT_GLOBAL);
+ CALL_METHOD_PUT_GLOBAL,
+ sProviderHolder);
// Certain settings have been moved from global to the per-user secure namespace
private static final HashSet<String> MOVED_TO_SECURE;
@@ -9181,12 +9398,121 @@
*/
public static boolean putString(ContentResolver resolver,
String name, String value) {
- return putStringForUser(resolver, name, value, UserHandle.myUserId());
+ return putStringForUser(resolver, name, value, null, false, UserHandle.myUserId());
+ }
+
+ /**
+ * Store a name/value pair into the database.
+ * <p>
+ * The method takes an optional tag to associate with the setting
+ * which can be used to clear only settings made by your package and
+ * associated with this tag by passing the tag to {@link
+ * #resetToDefaults(ContentResolver, String)}. Anyone can override
+ * the current tag. Also if another package changes the setting
+ * then the tag will be set to the one specified in the set call
+ * which can be null. Also any of the settings setters that do not
+ * take a tag as an argument effectively clears the tag.
+ * </p><p>
+ * For example, if you set settings A and B with tags T1 and T2 and
+ * another app changes setting A (potentially to the same value), it
+ * can assign to it a token T3 (note that now the package that changed
+ * the setting is not yours). Now if you reset your changes for T1 and
+ * T2 only setting B will be reset and A not (as it was changed by
+ * another package) but since A did not change you are in the desired
+ * initial state. Now if the other app changes the value of A (assuming
+ * you registered an observer in the beginning) you would detect that
+ * the setting was changed by another app and handle this appropriately
+ * (ignore, set back to some value, etc).
+ * </p><p>
+ * Also the method takes an argument whether to make the value the
+ * default for this setting. If the system already specified a default
+ * value, then the one passed in here will <strong>not</strong>
+ * be set as the default.
+ * </p>
+ *
+ * @param resolver to access the database with.
+ * @param name to store.
+ * @param value to associate with the name.
+ * @param tag to associated with the setting.
+ * @param makeDefault whether to make the value the default one.
+ * @return true if the value was set, false on database errors.
+ *
+ * @see #resetToDefaults(ContentResolver, String)
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public static boolean putString(@NonNull ContentResolver resolver,
+ @NonNull String name, @Nullable String value, @Nullable String tag,
+ boolean makeDefault) {
+ return putStringForUser(resolver, name, value, tag, makeDefault,
+ UserHandle.myUserId());
+ }
+
+ /**
+ * Reset the settings to their defaults. This would reset <strong>only</strong>
+ * settings set by the caller's package. Think of it of a way to undo your own
+ * changes to the secure settings. Passing in the optional tag will reset only
+ * settings changed by your package and associated with this tag.
+ *
+ * @param resolver Handle to the content resolver.
+ * @param tag Optional tag which should be associated with the settings to reset.
+ *
+ * @see #putString(ContentResolver, String, String, String, boolean)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static void resetToDefaults(@NonNull ContentResolver resolver,
+ @Nullable String tag) {
+ resetToDefaultsAsUser(resolver, tag, RESET_MODE_PACKAGE_DEFAULTS,
+ UserHandle.myUserId());
+ }
+
+ /**
+ * Reset the settings to their defaults for a given user with a specific mode. The
+ * optional tag argument is valid only for {@link #RESET_MODE_PACKAGE_DEFAULTS}
+ * allowing resetting the settings made by a package and associated with the tag.
+ *
+ * @param resolver Handle to the content resolver.
+ * @param tag Optional tag which should be associated with the settings to reset.
+ * @param mode The reset mode.
+ * @param userHandle The user for which to reset to defaults.
+ *
+ * @see #RESET_MODE_PACKAGE_DEFAULTS
+ * @see #RESET_MODE_UNTRUSTED_DEFAULTS
+ * @see #RESET_MODE_UNTRUSTED_CHANGES
+ * @see #RESET_MODE_TRUSTED_DEFAULTS
+ *
+ * @hide
+ */
+ public static void resetToDefaultsAsUser(@NonNull ContentResolver resolver,
+ @Nullable String tag, @ResetMode int mode, @IntRange(from = 0) int userHandle) {
+ try {
+ Bundle arg = new Bundle();
+ arg.putInt(CALL_METHOD_USER_KEY, userHandle);
+ if (tag != null) {
+ arg.putString(CALL_METHOD_TAG_KEY, tag);
+ }
+ arg.putInt(CALL_METHOD_RESET_MODE_KEY, mode);
+ IContentProvider cp = sProviderHolder.getProvider(resolver);
+ cp.call(resolver.getPackageName(), CALL_METHOD_RESET_GLOBAL, null, arg);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Can't reset do defaults for " + CONTENT_URI, e);
+ }
}
/** @hide */
public static boolean putStringForUser(ContentResolver resolver,
String name, String value, int userHandle) {
+ return putStringForUser(resolver, name, value, null, false, userHandle);
+ }
+
+ /** @hide */
+ public static boolean putStringForUser(@NonNull ContentResolver resolver,
+ @NonNull String name, @Nullable String value, @Nullable String tag,
+ boolean makeDefault, @UserIdInt int userHandle) {
if (LOCAL_LOGV) {
Log.v(TAG, "Global.putString(name=" + name + ", value=" + value
+ " for " + userHandle);
@@ -9195,9 +9521,11 @@
if (MOVED_TO_SECURE.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global"
+ " to android.provider.Settings.Secure, value is unchanged.");
- return Secure.putStringForUser(resolver, name, value, userHandle);
+ return Secure.putStringForUser(resolver, name, value, tag,
+ makeDefault, userHandle);
}
- return sNameValueCache.putStringForUser(resolver, name, value, userHandle);
+ return sNameValueCache.putStringForUser(resolver, name, value, tag,
+ makeDefault, userHandle);
}
/**
@@ -9418,7 +9746,6 @@
return putString(cr, name, Float.toString(value));
}
-
/**
* Subscription to be used for voice call on a multi sim device. The supported values
* are 0 = SUB1, 1 = SUB2 and etc.
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index b0b2065..6276af3 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -246,7 +246,7 @@
}
/**
- * Returns a userHandle for the instance of the app that posted this notification.
+ * Returns a userid for whom this notification is intended.
*
* @deprecated Use {@link #getUser()} instead.
*/
diff --git a/core/java/android/text/method/WordIterator.java b/core/java/android/text/method/WordIterator.java
index 17938a8..33e96a8 100644
--- a/core/java/android/text/method/WordIterator.java
+++ b/core/java/android/text/method/WordIterator.java
@@ -17,6 +17,8 @@
package android.text.method;
import android.annotation.NonNull;
+import android.icu.lang.UCharacter;
+import android.icu.lang.UProperty;
import android.icu.text.BreakIterator;
import android.text.CharSequenceCharacterIterator;
import android.text.Selection;
@@ -321,6 +323,27 @@
return false;
}
+ /**
+ * Indicates if the codepoint is a mid-word-only punctuation.
+ *
+ * At the moment, this is locale-independent, and includes all the characters in
+ * the MidLetter, MidNumLet, and Single_Quote class of Unicode word breaking algorithm (see
+ * UAX #29 "Unicode Text Segmentation" at http://unicode.org/reports/tr29/). These are all the
+ * characters that according to the rules WB6 and WB7 of UAX #29 prevent word breaks if they are
+ * in the middle of a word, but they become word breaks if they happen at the end of a word
+ * (accroding to rule WB999 that breaks word in any place that is not prohibited otherwise).
+ *
+ * @param locale the locale to consider the codepoint in. Presently ignored.
+ * @param codePoint the codepoint to check.
+ * @return True if the codepoint is a mid-word punctuation.
+ */
+ public static boolean isMidWordPunctuation(Locale locale, int codePoint) {
+ final int wb = UCharacter.getIntPropertyValue(codePoint, UProperty.WORD_BREAK);
+ return (wb == UCharacter.WordBreak.MIDLETTER
+ || wb == UCharacter.WordBreak.MIDNUMLET
+ || wb == UCharacter.WordBreak.SINGLE_QUOTE);
+ }
+
private boolean isPunctuationStartBoundary(int offset) {
return isOnPunctuation(offset) && !isAfterPunctuation(offset);
}
@@ -331,13 +354,13 @@
private static boolean isPunctuation(int cp) {
final int type = Character.getType(cp);
- return (type == Character.CONNECTOR_PUNCTUATION ||
- type == Character.DASH_PUNCTUATION ||
- type == Character.END_PUNCTUATION ||
- type == Character.FINAL_QUOTE_PUNCTUATION ||
- type == Character.INITIAL_QUOTE_PUNCTUATION ||
- type == Character.OTHER_PUNCTUATION ||
- type == Character.START_PUNCTUATION);
+ return (type == Character.CONNECTOR_PUNCTUATION
+ || type == Character.DASH_PUNCTUATION
+ || type == Character.END_PUNCTUATION
+ || type == Character.FINAL_QUOTE_PUNCTUATION
+ || type == Character.INITIAL_QUOTE_PUNCTUATION
+ || type == Character.OTHER_PUNCTUATION
+ || type == Character.START_PUNCTUATION);
}
private boolean isAfterLetterOrDigit(int offset) {
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index 6196a97..99f6c2a9 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -56,6 +56,7 @@
/** A previously logged event read from the logs. Instances are thread safe. */
public static final class Event {
private final ByteBuffer mBuffer;
+ private Exception mLastWtf;
// Layout of event log entry received from Android logger.
// see system/core/include/log/logger.h
@@ -116,13 +117,19 @@
offset = V1_PAYLOAD_START;
}
mBuffer.limit(offset + mBuffer.getShort(LENGTH_OFFSET));
+ if ((offset + DATA_OFFSET) >= mBuffer.limit()) {
+ // no payload
+ return null;
+ }
mBuffer.position(offset + DATA_OFFSET); // Just after the tag.
return decodeObject();
} catch (IllegalArgumentException e) {
Log.wtf(TAG, "Illegal entry payload: tag=" + getTag(), e);
+ mLastWtf = e;
return null;
} catch (BufferUnderflowException e) {
Log.wtf(TAG, "Truncated entry payload: tag=" + getTag(), e);
+ mLastWtf = e;
return null;
}
}
@@ -148,6 +155,7 @@
return new String(mBuffer.array(), start, length, "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.wtf(TAG, "UTF-8 is not supported", e);
+ mLastWtf = e;
return null;
}
@@ -173,6 +181,24 @@
byte[] bytes = mBuffer.array();
return Arrays.copyOf(bytes, bytes.length);
}
+
+ /**
+ * Retreive the last WTF error generated by this object.
+ * @hide
+ */
+ //VisibleForTesting
+ public Exception getLastError() {
+ return mLastWtf;
+ }
+
+ /**
+ * Clear the error state for this object.
+ * @hide
+ */
+ //VisibleForTesting
+ public void clearError() {
+ mLastWtf = null;
+ }
}
// We assume that the native methods deal with any concurrency issues.
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index 78d3b7b..0216a07 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -579,7 +579,7 @@
throws SignatureNotFoundException {
// Look up the offset of ZIP Central Directory.
long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
- if (centralDirOffset >= eocdOffset) {
+ if (centralDirOffset > eocdOffset) {
throw new SignatureNotFoundException(
"ZIP Central Directory offset out of range: " + centralDirOffset
+ ". ZIP End of Central Directory offset: " + eocdOffset);
diff --git a/core/java/android/util/apk/ZipUtils.java b/core/java/android/util/apk/ZipUtils.java
index cdbac18..fa5477e 100644
--- a/core/java/android/util/apk/ZipUtils.java
+++ b/core/java/android/util/apk/ZipUtils.java
@@ -160,7 +160,7 @@
}
int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT16_MAX_VALUE);
int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE;
- for (int expectedCommentLength = 0; expectedCommentLength < maxCommentLength;
+ for (int expectedCommentLength = 0; expectedCommentLength <= maxCommentLength;
expectedCommentLength++) {
int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength;
if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 12658bd..0650875 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3286,7 +3286,9 @@
@ViewDebug.FlagToString(mask = PFLAG_DIRTY_MASK, equals = PFLAG_DIRTY_OPAQUE, name = "DIRTY_OPAQUE"),
@ViewDebug.FlagToString(mask = PFLAG_DIRTY_MASK, equals = PFLAG_DIRTY, name = "DIRTY")
}, formatToHexString = true)
- int mPrivateFlags;
+
+ /* @hide */
+ public int mPrivateFlags;
int mPrivateFlags2;
int mPrivateFlags3;
@@ -14026,8 +14028,9 @@
* invalidated as well. This is usually true for a full
* invalidate, but may be set to false if the View's contents or
* dimensions have not changed.
+ * @hide
*/
- void invalidate(boolean invalidateCache) {
+ public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 7340cf7..c3bdb10 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -5311,11 +5311,96 @@
}
/**
+ * HW-only, Rect-ignoring invalidation path.
+ *
+ * Returns false if this path was unable to complete successfully. This means
+ * it hit a ViewParent it doesn't recognize and needs to fall back to calculating
+ * damage area.
+ *
+ * Hardware acceleration ignores damage rectangles, since native computes damage for everything
+ * drawn by HWUI (and SW layer / drawing cache doesn't keep track of damage area).
+ *
+ * Ignores opaque dirty optimizations, always using the full PFLAG_DIRTY flag.
+ *
+ * Ignores FLAG_OPTIMIZE_INVALIDATE, since we're not computing a rect,
+ * so no point in optimizing that.
+ * @hide
+ */
+ public boolean tryInvalidateChildHardware(View child) {
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo == null || !attachInfo.mHardwareAccelerated) {
+ return false;
+ }
+
+ // verify it's ViewGroups up to a ViewRootImpl
+ ViewRootImpl viewRoot = null;
+ ViewParent parent = getParent();
+ while (parent != null) {
+ if (parent instanceof ViewGroup) {
+ parent = parent.getParent();
+ } else if (parent instanceof ViewRootImpl) {
+ viewRoot = (ViewRootImpl) parent;
+ break;
+ } else {
+ // unknown parent type, abort
+ return false;
+ }
+ }
+ if (viewRoot == null) {
+ // unable to find ViewRoot
+ return false;
+ }
+
+ final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0;
+
+ if (child.mLayerType != LAYER_TYPE_NONE) {
+ mPrivateFlags |= PFLAG_INVALIDATED;
+ }
+
+ parent = this;
+ do {
+ if (parent != viewRoot) {
+ // Note: we cast here without checking isinstance, to avoid cost of isinstance again
+ ViewGroup viewGroup = (ViewGroup) parent;
+ if (drawAnimation) {
+ viewGroup.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
+ }
+
+ // We lazily use PFLAG_DIRTY, since computing opaque isn't worth the potential
+ // optimization in provides in a DisplayList world.
+ viewGroup.mPrivateFlags =
+ (viewGroup.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
+
+ // simplified invalidateChildInParent behavior: clear cache validity to be safe,
+ // and mark inval if in layer
+ viewGroup.mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+ if (viewGroup.mLayerType != LAYER_TYPE_NONE) {
+ viewGroup.mPrivateFlags |= PFLAG_INVALIDATED;
+ }
+ } else {
+ if (drawAnimation) {
+ viewRoot.mIsAnimating = true;
+ }
+ ((ViewRootImpl) parent).invalidate();
+ return true;
+ }
+
+ parent = parent.getParent();
+ } while (parent != null);
+ return true;
+ }
+
+
+ /**
* Don't call or override this method. It is used for the implementation of
* the view hierarchy.
*/
@Override
public final void invalidateChild(View child, final Rect dirty) {
+ if (tryInvalidateChildHardware(child)) {
+ return;
+ }
+
ViewParent parent = this;
final AttachInfo attachInfo = mAttachInfo;
@@ -5323,8 +5408,7 @@
// If the child is drawing an animation, we want to copy this flag onto
// ourselves and the parent to make sure the invalidate request goes
// through
- final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION)
- == PFLAG_DRAW_ANIMATION;
+ final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0;
// Check whether the child that requests the invalidate is fully opaque
// Views being animated or transformed are not considered opaque because we may
@@ -5425,10 +5509,10 @@
*/
@Override
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
- if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
- (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
- if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
- FLAG_OPTIMIZE_INVALIDATE) {
+ if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
+ // either DRAWN, or DRAWING_CACHE_VALID
+ if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))
+ != FLAG_OPTIMIZE_INVALIDATE) {
dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
location[CHILD_TOP_INDEX] - mScrollY);
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
@@ -5443,35 +5527,28 @@
dirty.setEmpty();
}
}
- mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
location[CHILD_LEFT_INDEX] = left;
location[CHILD_TOP_INDEX] = top;
-
- if (mLayerType != LAYER_TYPE_NONE) {
- mPrivateFlags |= PFLAG_INVALIDATED;
- }
-
- return mParent;
-
} else {
- mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;
- location[CHILD_LEFT_INDEX] = mLeft;
- location[CHILD_TOP_INDEX] = mTop;
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
} else {
// in case the dirty rect extends outside the bounds of this container
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}
+ location[CHILD_LEFT_INDEX] = mLeft;
+ location[CHILD_TOP_INDEX] = mTop;
- if (mLayerType != LAYER_TYPE_NONE) {
- mPrivateFlags |= PFLAG_INVALIDATED;
- }
-
- return mParent;
+ mPrivateFlags &= ~PFLAG_DRAWN;
}
+ mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+ if (mLayerType != LAYER_TYPE_NONE) {
+ mPrivateFlags |= PFLAG_INVALIDATED;
+ }
+
+ return mParent;
}
return null;
@@ -5484,7 +5561,7 @@
* damage area
* @hide
*/
- public boolean damageChildDeferred(View child) {
+ public boolean damageChildDeferred() {
ViewParent parent = getParent();
while (parent != null) {
if (parent instanceof ViewGroup) {
@@ -5507,7 +5584,7 @@
* @hide
*/
public void damageChild(View child, final Rect dirty) {
- if (damageChildDeferred(child)) {
+ if (damageChildDeferred()) {
return;
}
diff --git a/core/java/android/view/ViewOverlay.java b/core/java/android/view/ViewOverlay.java
index b770bd5..61cf0c7 100644
--- a/core/java/android/view/ViewOverlay.java
+++ b/core/java/android/view/ViewOverlay.java
@@ -283,8 +283,9 @@
}
}
+ /** @hide */
@Override
- void invalidate(boolean invalidateCache) {
+ public void invalidate(boolean invalidateCache) {
super.invalidate(invalidateCache);
if (mHostView != null) {
mHostView.invalidate(invalidateCache);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2cebeed..f1279c3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -237,7 +237,7 @@
int mWidth;
int mHeight;
Rect mDirty;
- boolean mIsAnimating;
+ public boolean mIsAnimating;
private boolean mDragResizing;
private boolean mInvalidateRootRequested;
@@ -258,7 +258,7 @@
final Rect mTempRect; // used in the transaction to not thrash the heap.
final Rect mVisRect; // used to retrieve visible rect of focused view.
- boolean mTraversalScheduled;
+ public boolean mTraversalScheduled;
int mTraversalBarrier;
boolean mWillDrawSoon;
/** Set to true while in performTraversals for detecting when die(true) is called from internal
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 7c1bcee..8bc988d 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -274,6 +274,7 @@
private TypedArray mWindowStyle;
private Callback mCallback;
private OnWindowDismissedCallback mOnWindowDismissedCallback;
+ private OnWindowSwipeDismissedCallback mOnWindowSwipeDismissedCallback;
private WindowControllerCallback mWindowControllerCallback;
private OnRestrictedCaptionAreaChangedListener mOnRestrictedCaptionAreaChangedListener;
private Rect mRestrictedCaptionAreaRect;
@@ -587,6 +588,18 @@
}
/** @hide */
+ public interface OnWindowSwipeDismissedCallback {
+ /**
+ * Called when a window is swipe dismissed. This informs the callback that the
+ * window is gone, and it should finish itself.
+ * @param finishTask True if the task should also be finished.
+ * @param suppressWindowTransition True if the resulting exit and enter window transition
+ * animations should be suppressed.
+ */
+ void onWindowSwipeDismissed();
+ }
+
+ /** @hide */
public interface WindowControllerCallback {
/**
* Moves the activity from
@@ -880,6 +893,18 @@
}
/** @hide */
+ public final void setOnWindowSwipeDismissedCallback(OnWindowSwipeDismissedCallback sdcb) {
+ mOnWindowSwipeDismissedCallback = sdcb;
+ }
+
+ /** @hide */
+ public final void dispatchOnWindowSwipeDismissed() {
+ if (mOnWindowSwipeDismissedCallback != null) {
+ mOnWindowSwipeDismissedCallback.onWindowSwipeDismissed();
+ }
+ }
+
+ /** @hide */
public final void setWindowControllerCallback(WindowControllerCallback wccb) {
mWindowControllerCallback = wccb;
}
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index 444ebc5..6c6079f 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -361,7 +361,7 @@
measures.add(new Measure(s, MeasureUnit.SECOND));
return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE)
- .formatMeasures((Measure[]) measures.toArray());
+ .formatMeasures(measures.toArray(new Measure[measures.size()]));
}
@Override
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 5fa1d2b..46324a3 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -42,6 +42,7 @@
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
@@ -57,6 +58,7 @@
import com.android.internal.R;
import java.lang.ref.WeakReference;
+import java.util.List;
/**
* <p>
@@ -139,6 +141,12 @@
private Context mContext;
private WindowManager mWindowManager;
+ /**
+ * Keeps track of popup's parent's decor view. This is needed to dispatch
+ * requestKeyboardShortcuts to the owning Activity.
+ */
+ private WeakReference<View> mParentRootView;
+
private boolean mIsShowing;
private boolean mIsTransitioningToDismiss;
private boolean mIsDropdown;
@@ -1119,6 +1127,7 @@
* @param y the popup's y location offset
*/
public void showAtLocation(View parent, int gravity, int x, int y) {
+ mParentRootView = new WeakReference<>(parent.getRootView());
showAtLocation(parent.getWindowToken(), gravity, x, y);
}
@@ -2225,6 +2234,7 @@
mAnchor = new WeakReference<>(anchor);
mAnchorRoot = new WeakReference<>(anchorRoot);
mIsAnchorRootAttached = anchorRoot.isAttachedToWindow();
+ mParentRootView = mAnchorRoot;
mAnchorXoff = xoff;
mAnchorYoff = yoff;
@@ -2422,6 +2432,16 @@
TransitionManager.endTransitions(PopupDecorView.this);
}
};
+
+ @Override
+ public void requestKeyboardShortcuts(List<KeyboardShortcutGroup> list, int deviceId) {
+ if (mParentRootView != null) {
+ View parentRoot = mParentRootView.get();
+ if (parentRoot != null) {
+ parentRoot.requestKeyboardShortcuts(list, deviceId);
+ }
+ }
+ }
}
private class PopupBackgroundView extends FrameLayout {
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index e7031fe..3d4f546 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -277,18 +277,19 @@
// Do not check this word if the user is currently editing it
final boolean isEditing;
- // Defer spell check when typing a word with an interior apostrophe.
- // TODO: a better solution to this would be to make the word
- // iterator locale-sensitive and include the apostrophe in
- // languages that use it (such as English).
- final boolean apostrophe = (selectionStart == end + 1 && editable.charAt(end) == '\'');
- if (mIsSentenceSpellCheckSupported) {
+ // Defer spell check when typing a word ending with a punctuation like an apostrophe
+ // which could end up being a mid-word punctuation.
+ if (selectionStart == end + 1
+ && WordIterator.isMidWordPunctuation(
+ mCurrentLocale, Character.codePointBefore(editable, end + 1))) {
+ isEditing = false;
+ } else if (mIsSentenceSpellCheckSupported) {
// Allow the overlap of the cursor and the first boundary of the spell check span
// no to skip the spell check of the following word because the
// following word will never be spell-checked even if the user finishes composing
- isEditing = !apostrophe && (selectionEnd <= start || selectionStart > end);
+ isEditing = selectionEnd <= start || selectionStart > end;
} else {
- isEditing = !apostrophe && (selectionEnd < start || selectionStart > end);
+ isEditing = selectionEnd < start || selectionStart > end;
}
if (start >= 0 && end > start && isEditing) {
spellCheckSpan.setSpellCheckInProgress(true);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 99dbb1c..6a5bbcc 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -2897,8 +2897,22 @@
mHistoryLastWritten.setTo(mHistoryLastLastWritten);
}
+ boolean recordResetDueToOverflow = false;
final int dataSize = mHistoryBuffer.dataSize();
- if (dataSize >= MAX_HISTORY_BUFFER) {
+ if (dataSize >= MAX_MAX_HISTORY_BUFFER*3) {
+ // Clients can't deal with history buffers this large. This only
+ // really happens when the device is on charger and interacted with
+ // for long periods of time, like in retail mode. Since the device is
+ // most likely charged, when unplugged, stats would have reset anyways.
+ // Reset the stats and mark that we overflowed.
+ // b/32540341
+ resetAllStatsLocked();
+
+ // Mark that we want to set *OVERFLOW* event and the RESET:START
+ // events.
+ recordResetDueToOverflow = true;
+
+ } else if (dataSize >= MAX_HISTORY_BUFFER) {
if (!mHistoryOverflow) {
mHistoryOverflow = true;
addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur);
@@ -2944,9 +2958,12 @@
return;
}
- if (dataSize == 0) {
+ if (dataSize == 0 || recordResetDueToOverflow) {
// The history is currently empty; we need it to start with a time stamp.
cur.currentTime = System.currentTimeMillis();
+ if (recordResetDueToOverflow) {
+ addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_OVERFLOW, cur);
+ }
addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_RESET, cur);
}
addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur);
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index ec068a3..e68ebc4 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2997,6 +2997,7 @@
swipeDismiss.setOnDismissedListener(new SwipeDismissLayout.OnDismissedListener() {
@Override
public void onDismissed(SwipeDismissLayout layout) {
+ dispatchOnWindowSwipeDismissed();
dispatchOnWindowDismissed(false /*finishTask*/, true /*suppressWindowTransition*/);
}
});
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 63b700b..2a8077c 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -590,8 +590,6 @@
setCredentialRequiredToDecrypt(false);
}
- getDevicePolicyManager().setActivePasswordState(new PasswordMetrics(), userHandle);
-
onAfterChangingPassword(userHandle);
}
@@ -644,6 +642,7 @@
+ MIN_LOCK_PATTERN_SIZE + " dots long.");
}
+ setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
getLockSettings().setLockPattern(patternToString(pattern), savedPattern, userId);
DevicePolicyManager dpm = getDevicePolicyManager();
@@ -659,10 +658,6 @@
}
setBoolean(PATTERN_EVER_CHOSEN_KEY, true, userId);
-
- setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
- dpm.setActivePasswordState(new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern.size()), userId);
onAfterChangingPassword(userId);
} catch (RemoteException re) {
Log.e(TAG, "Couldn't save lock pattern " + re);
@@ -775,10 +770,9 @@
+ "of length " + MIN_LOCK_PASSWORD_SIZE);
}
+ final int computedQuality = PasswordMetrics.computeForPassword(password).quality;
+ setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle);
getLockSettings().setLockPassword(password, savedPassword, userHandle);
- getLockSettings().setSeparateProfileChallengeEnabled(userHandle, true, null);
- final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password);
- final int computedQuality = metrics.quality;
// Update the device encryption password.
if (userHandle == UserHandle.USER_SYSTEM
@@ -796,15 +790,6 @@
}
}
- setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle);
- if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
- metrics.quality = Math.max(quality, metrics.quality);
- dpm.setActivePasswordState(metrics, userHandle);
- } else {
- // The password is not anything.
- dpm.setActivePasswordState(new PasswordMetrics(), userHandle);
- }
-
// Add the password to the password history. We assume all
// password hashes have the same length for simplicity of implementation.
String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle);
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 80711aa..8f74bf8 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -628,6 +628,14 @@
jint dstConfigHandle, jboolean isMutable) {
SkBitmap src;
reinterpret_cast<BitmapWrapper*>(srcHandle)->getSkBitmap(&src);
+ if (dstConfigHandle == GraphicsJNI::hardwareLegacyBitmapConfig()) {
+ sk_sp<Bitmap> bitmap(Bitmap::allocateHardwareBitmap(src));
+ if (!bitmap.get()) {
+ return NULL;
+ }
+ return createBitmap(env, bitmap.release(), kBitmapCreateFlag_None);
+ }
+
SkColorType dstCT = GraphicsJNI::legacyBitmapConfigToColorType(dstConfigHandle);
SkBitmap result;
HeapAllocator allocator;
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 69c7054..724fccc 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -395,7 +395,7 @@
SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied);
const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(),
- decodeColorType, alphaType, codec->computeOutputColorSpace(decodeColorType));
+ decodeColorType, alphaType);
// We always decode to sRGB, but only mark the bitmap with a color space if linear
// blending is enabled.
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 1cfbd97..b57f2362 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -240,7 +240,7 @@
t_pri = getpriority(PRIO_PROCESS, t_pid);
if (t_pri <= ANDROID_PRIORITY_AUDIO) {
- int scheduler = sched_getscheduler(t_pid);
+ int scheduler = sched_getscheduler(t_pid) & ~SCHED_RESET_ON_FORK;
if ((scheduler == SCHED_FIFO) || (scheduler == SCHED_RR)) {
// This task wants to stay in its current audio group so it can keep its budget
// don't update its cpuset or cgroup
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index bc040625..a893529 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -101,6 +101,7 @@
<protected-broadcast android:name="android.app.action.BUGREPORT_SHARING_DECLINED" />
<protected-broadcast android:name="android.app.action.BUGREPORT_FAILED" />
<protected-broadcast android:name="android.app.action.BUGREPORT_SHARE" />
+ <protected-broadcast android:name="android.app.action.SHOW_DEVICE_MONITORING_DIALOG" />
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" />
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_DELETED" />
@@ -3435,10 +3436,13 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
- <service
- android:name="com.android.server.pm.BackgroundDexOptService"
- android:exported="true"
- android:permission="android.permission.BIND_JOB_SERVICE">
+ <service android:name="com.android.server.pm.BackgroundDexOptService"
+ android:exported="true"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
+ <service android:name="com.android.server.storage.DiskStatsLoggingService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
</application>
diff --git a/core/res/res/drawable/ic_qs_network_logging.xml b/core/res/res/drawable/ic_qs_network_logging.xml
new file mode 100644
index 0000000..9e082641
--- /dev/null
+++ b/core/res/res/drawable/ic_qs_network_logging.xml
@@ -0,0 +1,29 @@
+<!--
+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.
+-->
+
+<!-- STOPSHIP: Placeholder icon for network logging until the real icon is finalized-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="#4DFFFFFF" >
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M2,24v-4h12v4H2z M2,16v-4h20v4H2z M5,7 12,0 19,7 14,7 14,15 10,15 10,7z"/>
+
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml
new file mode 100644
index 0000000..3190681
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="18"
+ android:viewportHeight="18"
+ android:width="18dp"
+ android:height="18dp">
+ <group
+ android:translateX="386"
+ android:translateY="-298">
+ <!-- TODO(b/33778046): update fillColor below to refer to a theme color attribute instead of hardcoded value. -->
+ <path
+ android:pathData="M-377 308.5c0 -2.5 2 -4.5 4.5 -4.5l3.5 0 0.79999 -1c-0.29999 -0.29999 -3.70001 -3 -8.79999 -3 -5.09998 0 -8.5 2.79999 -8.79999 3l8.79999 11 0 0 0 0 1.60001 -2c-0.9 -0.89999 -1.60001 -2.10001 -1.60001 -3.5z"
+ android:fillColor="#4285F4"
+ android:fillAlpha="0.3" />
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml b/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml
new file mode 100644
index 0000000..202f484
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="18"
+ android:viewportHeight="18"
+ android:width="18dp"
+ android:height="18dp">
+ <group
+ android:translateX="386"
+ android:translateY="-298">
+ <!-- TODO(b/33778046): update fillColor below to refer to a theme color attribute instead of hardcoded value. -->
+ <path
+ android:pathData="M-377 308.5c0 -2.5 2 -4.5 4.5 -4.5l3.5 0 0.79999 -1c-0.29999 -0.29999 -3.70001 -3 -8.79999 -3 -5.09998 0 -8.5 2.79999 -8.79999 3l8.79999 11 0 0 0 0 1.60001 -2c-0.9 -0.89999 -1.60001 -2.10001 -1.60001 -3.5z"
+ android:fillColor="#4285F4"
+ android:fillAlpha="0.3" />
+ <path
+ android:pathData="M-377 308.5c0 -0.20001 0 -0.29999 0.10001 -0.5 0 0 0 0 -0.10001 0 -2.10001 0 -3.60001 1.20001 -3.79999 1.29999L-377 314l1.60001 -2.10001c-0.9 -0.79998 -1.60001 -2 -1.60001 -3.39999z"
+ android:fillColor="#4285F4" />
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml
new file mode 100644
index 0000000..188a3a4
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="18"
+ android:viewportHeight="18"
+ android:width="18dp"
+ android:height="18dp">
+ <group
+ android:translateX="386"
+ android:translateY="-298">
+ <!-- TODO(b/33778046): update fillColor below to refer to a theme color attribute instead of hardcoded value. -->
+ <path
+ android:pathData="M-377 308.5c0 -2.5 2 -4.5 4.5 -4.5l3.5 0 0.79999 -1c-0.29999 -0.29999 -3.70001 -3 -8.79999 -3 -5.09998 0 -8.5 2.79999 -8.79999 3l8.79999 11 0 0 0 0 1.60001 -2c-0.9 -0.89999 -1.60001 -2.10001 -1.60001 -3.5z"
+ android:fillColor="#4285F4"
+ android:fillAlpha="0.3" />
+ <path
+ android:pathData="M-377 308.5c0 -0.89999 0.29999 -1.70001 0.70001 -2.5 -0.20001 0 -0.5 0 -0.70001 0 -2.79999 0 -4.79999 1.60001 -5 1.79999l5 6.20001 0 0 0 0 1.60001 -2c-1 -0.89999 -1.60001 -2.10001 -1.60001 -3.5z"
+ android:fillColor="#4285F4" />
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml
new file mode 100644
index 0000000..28a3846
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="18"
+ android:viewportHeight="18"
+ android:width="18dp"
+ android:height="18dp">
+ <group
+ android:translateX="386"
+ android:translateY="-298">
+ <!-- TODO(b/33778046): update fillColor below to refer to a theme color attribute instead of hardcoded value. -->
+ <path
+ android:pathData="M-377 308.5c0 -2.5 2 -4.5 4.5 -4.5l3.5 0 0.79999 -1c-0.29999 -0.29999 -3.70001 -3 -8.79999 -3 -5.09998 0 -8.5 2.79999 -8.79999 3l8.79999 11 0 0 0 0 1.60001 -2c-0.9 -0.89999 -1.60001 -2.10001 -1.60001 -3.5z"
+ android:fillColor="#4285F4"
+ android:fillAlpha="0.3" />
+ <path
+ android:pathData="M-375.39999 311.89999c-1 -0.79998 -1.60001 -2.1 -1.60001 -3.39999 0 -1.79999 1.10001 -3.39999 2.70001 -4.10001C-375.09998 304.19998 -376 304 -377 304c-3.60001 0 -6 1.89999 -6.29999 2.20001L-377 314l0 0 0 0"
+ android:fillColor="#4285F4" />
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml
new file mode 100644
index 0000000..3a721eb
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="18"
+ android:viewportHeight="18"
+ android:width="18dp"
+ android:height="18dp">
+ <group
+ android:translateX="386"
+ android:translateY="-298">
+ <!-- TODO(b/33778046): update fillColor below to refer to a theme color attribute instead of hardcoded value. -->
+ <path
+ android:pathData="M-377 308.5c0 -2.5 2 -4.5 4.5 -4.5l3.5 0 0.79999 -1c-0.29999 -0.29999 -3.70001 -3 -8.79999 -3 -5.09998 0 -8.5 2.79999 -8.79999 3l8.79999 11 0 0 0 0 1.60001 -2c-0.9 -0.89999 -1.60001 -2.10001 -1.60001 -3.5z"
+ android:fillColor="#4285F4" />
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_4k.xml b/core/res/res/drawable/ic_signal_wifi_badged_4k.xml
new file mode 100644
index 0000000..048db3d
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_4k.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="18"
+ android:viewportHeight="18"
+ android:width="18dp"
+ android:height="18dp">
+ <group
+ android:translateX="386"
+ android:translateY="-298">
+ <!-- TODO(b/33778046): update fillColor below to refer to a theme color attribute instead of hardcoded value. -->
+ <path
+ android:pathData="M-373.04999 308.79999l0.5 0 0 0.89999 -0.5 0 0 1.20001 -1.1 0 0 -1.20001 -1.9 0 -0.1 -0.70001 1.89999 -3.70001 1.10001 0 0 3.50003 0.1 0zm-1.89999 0l0.89999 0 0 -1.9 0 0 -0.89999 1.9z"
+ android:fillColor="#4285F4" />
+ <path
+ android:pathData="M-370.44998 308.70001l-0.5 0.60001 0 1.70001 -1.10001 0 0 -5.70001 1.10001 0 0 2.5 0.39999 -0.60001 1.10001 -1.89999 1.39999 0 -1.6 2.5 1.70001 3.20001 -1.29999 0 -1.20001 -2.30002z"
+ android:fillColor="#4285F4" />
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_hd.xml b/core/res/res/drawable/ic_signal_wifi_badged_hd.xml
new file mode 100644
index 0000000..76c56ff
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_hd.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="18"
+ android:viewportHeight="18"
+ android:width="18dp"
+ android:height="18dp">
+ <group
+ android:translateX="386"
+ android:translateY="-298">
+ <!-- TODO(b/33778046): update fillColor below to refer to a theme color attribute instead of hardcoded value. -->
+ <path
+ android:pathData="M-371.79999 311l-1.1 0 0 -2.29999 -0.79999 0 0 2.29999 -1.10001 0 0 -5.70001 1.10001 0 0 2.39999 0.79999 0 0 -2.39999 1.1 0 0 5.70001z"
+ android:fillColor="#4285F4" />
+ <path
+ android:pathData="M-371.33557 310.98651l0 -5.68701 1.39068 0c0.27848 0 0.53336 0.0532 0.76465 0.16016 0.2313 0.10693 0.42954 0.2622 0.59568 0.46679 0.16519 0.20459 0.29357 0.45557 0.38421 0.75391 0.0906 0.29834 0.13593 0.63867 0.13593 1.02148l0 0.88672c0 0.38281 -0.0453 0.72363 -0.13593 1.021 -0.0906 0.29785 -0.21902 0.5498 -0.38421 0.7539 -0.16614 0.20411 -0.36628 0.35938 -0.59946 0.46485 -0.23316 0.10547 -0.49182 0.1582 -0.77786 0.1582l-1.37369 0zm1.06879 -4.76904l0 3.85107 0.26333 0c0.15491 0 0.28452 -0.0283 0.38971 -0.084 0.10516 -0.0557 0.19077 -0.14356 0.25681 -0.26367 0.066 -0.12012 0.11331 -0.27198 0.14184 -0.4585 0.0285 -0.18652 0.0424 -0.41113 0.0424 -0.67383l0 -0.89453c0 -0.26562 -0.0138 -0.49219 -0.0424 -0.67969 -0.0285 -0.1875 -0.0758 -0.33984 -0.14102 -0.45703 -0.0644 -0.11719 -0.1492 -0.20312 -0.25275 -0.25781 -0.10437 -0.0547 -0.23071 -0.082 -0.37991 -0.082l-0.27801 0z"
+ android:fillColor="#4285F4" />
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_ld.xml b/core/res/res/drawable/ic_signal_wifi_badged_ld.xml
new file mode 100644
index 0000000..20f8983
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_ld.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="18"
+ android:viewportHeight="18"
+ android:width="18dp"
+ android:height="18dp">
+ <group
+ android:translateX="386"
+ android:translateY="-298">
+ <!-- TODO(b/33778046): update fillColor below to refer to a theme color attribute instead of hardcoded value. -->
+ <path
+ android:pathData="M-371.33557 310.98651l0 -5.68701 1.39068 0c0.27848 0 0.53336 0.0532 0.76465 0.16016 0.2313 0.10693 0.42954 0.2622 0.59568 0.46679 0.16519 0.20459 0.29357 0.45557 0.38421 0.75391 0.0906 0.29834 0.13593 0.63867 0.13593 1.02148l0 0.88672c0 0.38281 -0.0453 0.72363 -0.13593 1.021 -0.0906 0.29785 -0.21902 0.5498 -0.38421 0.7539 -0.16614 0.20411 -0.36628 0.35938 -0.59946 0.46485 -0.23316 0.10547 -0.49182 0.1582 -0.77786 0.1582l-1.37369 0zm1.06879 -4.76904l0 3.85107 0.26333 0c0.15491 0 0.28452 -0.0283 0.38971 -0.084 0.10516 -0.0557 0.19077 -0.14356 0.25681 -0.26367 0.066 -0.12012 0.11331 -0.27198 0.14184 -0.4585 0.0285 -0.18652 0.0424 -0.41113 0.0424 -0.67383l0 -0.89453c0 -0.26562 -0.0138 -0.49219 -0.0424 -0.67969 -0.0285 -0.1875 -0.0758 -0.33984 -0.14102 -0.45703 -0.0644 -0.11719 -0.1492 -0.20312 -0.25275 -0.25781 -0.10437 -0.0547 -0.23071 -0.082 -0.37991 -0.082l-0.27801 0z"
+ android:fillColor="#4285F4" />
+ <path
+ android:pathData="M-373.13333 310.13333l1.33334 0 0 0.86667 -2.46667 0 0 -5.66666 1.13333 0 0 4.79999z"
+ android:fillColor="#4285F4" />
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_sd.xml b/core/res/res/drawable/ic_signal_wifi_badged_sd.xml
new file mode 100644
index 0000000..4f4254b
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_sd.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="18"
+ android:viewportHeight="18"
+ android:width="18dp"
+ android:height="18dp">
+ <group
+ android:translateX="386"
+ android:translateY="-298">
+ <!-- TODO(b/33778046): update fillColor below to refer to a theme color attribute instead of hardcoded value. -->
+ <path
+ android:pathData="M-371.33557 310.98651l0 -5.68701 1.39068 0c0.27848 0 0.53336 0.0532 0.76465 0.16016 0.2313 0.10693 0.42954 0.2622 0.59568 0.46679 0.16519 0.20459 0.29357 0.45557 0.38421 0.75391 0.0906 0.29834 0.13593 0.63867 0.13593 1.02148l0 0.88672c0 0.38281 -0.0453 0.72363 -0.13593 1.021 -0.0906 0.29785 -0.21902 0.5498 -0.38421 0.7539 -0.16614 0.20411 -0.36628 0.35938 -0.59946 0.46485 -0.23316 0.10547 -0.49182 0.1582 -0.77786 0.1582l-1.37369 0zm1.06879 -4.76904l0 3.85107 0.26333 0c0.15491 0 0.28452 -0.0283 0.38971 -0.084 0.10516 -0.0557 0.19077 -0.14356 0.25681 -0.26367 0.066 -0.12012 0.11331 -0.27198 0.14184 -0.4585 0.0285 -0.18652 0.0424 -0.41113 0.0424 -0.67383l0 -0.89453c0 -0.26562 -0.0138 -0.49219 -0.0424 -0.67969 -0.0285 -0.1875 -0.0758 -0.33984 -0.14102 -0.45703 -0.0644 -0.11719 -0.1492 -0.20312 -0.25275 -0.25781 -0.10437 -0.0547 -0.23071 -0.082 -0.37991 -0.082l-0.27801 0z"
+ android:fillColor="#4285F4" />
+ <path
+ android:pathData="M-372.87598 309.47461c0 -0.10645 -0.01 -0.20117 -0.0303 -0.28223 -0.0205 -0.0811 -0.0576 -0.15527 -0.11035 -0.22265 -0.0537 -0.0674 -0.12598 -0.12891 -0.21777 -0.18457 -0.0908 -0.0566 -0.20704 -0.11231 -0.34668 -0.16797 -0.24903 -0.0889 -0.47657 -0.18457 -0.68165 -0.28614 -0.20605 -0.10156 -0.38281 -0.2207 -0.53027 -0.35839 -0.14746 -0.13721 -0.26172 -0.29639 -0.34277 -0.47803 -0.0811 -0.18164 -0.12207 -0.39697 -0.12207 -0.646 0 -0.23144 0.042 -0.4414 0.12793 -0.63086 0.085 -0.18945 0.20312 -0.35205 0.35644 -0.48779 0.15235 -0.13623 0.33496 -0.2417 0.54883 -0.31641 0.21289 -0.0752 0.44824 -0.1123 0.70508 -0.1123 0.2666 0 0.50683 0.0425 0.71973 0.12744 0.21386 0.0854 0.39648 0.2041 0.54687 0.35645 0.15137 0.15234 0.26758 0.333 0.34766 0.54101 0.0791 0.2085 0.11914 0.43604 0.11914 0.68262l-1.07422 0c0 -0.11963 -0.0127 -0.22998 -0.0381 -0.33154 -0.0254 -0.10205 -0.0654 -0.18897 -0.12011 -0.26123 -0.0547 -0.0723 -0.125 -0.12891 -0.20997 -0.16993 -0.085 -0.0405 -0.1875 -0.0605 -0.30664 -0.0605 -0.11132 0 -0.208 0.0171 -0.29004 0.0513 -0.0811 0.0342 -0.14843 0.0815 -0.20117 0.14111 -0.0537 0.0596 -0.0928 0.13037 -0.11816 0.21143 -0.0254 0.0815 -0.0381 0.16894 -0.0381 0.26318 0 0.0937 0.0166 0.17725 0.0508 0.24951 0.0342 0.0723 0.0869 0.14014 0.15625 0.20361 0.0703 0.064 0.16016 0.125 0.26856 0.18311 0.10937 0.0586 0.23926 0.11963 0.38965 0.1831 0.24316 0.084 0.46093 0.17823 0.65136 0.2837 0.19043 0.10498 0.35059 0.22998 0.48047 0.37158 0.12891 0.14258 0.22754 0.30762 0.29493 0.49316 0.0674 0.1875 0.10156 0.40235 0.10156 0.64649 0 0.24121 -0.04 0.458 -0.12012 0.64843 -0.0801 0.19043 -0.19434 0.35059 -0.3418 0.48145 -0.14746 0.13086 -0.32617 0.23144 -0.53711 0.30176 -0.21093 0.0693 -0.44726 0.10449 -0.70898 0.10449 -0.23633 0 -0.46777 -0.0361 -0.69531 -0.1084 -0.22754 -0.0723 -0.43067 -0.18359 -0.61035 -0.33203 -0.17872 -0.14844 -0.32325 -0.33691 -0.43262 -0.56543 -0.10938 -0.22852 -0.16309 -0.49805 -0.16309 -0.80859l1.07813 0c0 0.17089 0.0166 0.31445 0.0498 0.43261 0.0332 0.11817 0.084 0.21485 0.15235 0.29004 0.0684 0.0752 0.15429 0.12891 0.25683 0.16211 0.10352 0.0332 0.22461 0.0488 0.36426 0.0488 0.11719 0 0.21582 -0.0156 0.2959 -0.0488 0.0801 -0.0332 0.14355 -0.0781 0.19238 -0.13574 0.0478 -0.0566 0.082 -0.125 0.10254 -0.2041 0.0205 -0.0781 0.0303 -0.16504 0.0303 -0.25879z"
+ android:fillColor="#4285F4" />
+ </group>
+</vector>
diff --git a/core/res/res/values/locale_config.xml b/core/res/res/values/locale_config.xml
index f07fe70..483d05b 100644
--- a/core/res/res/values/locale_config.xml
+++ b/core/res/res/values/locale_config.xml
@@ -484,6 +484,7 @@
<item>yav-CM</item> <!-- Yangben (Cameroon) -->
<item>yo-BJ</item> <!-- Yoruba (Benin) -->
<item>yo-NG</item> <!-- Yoruba (Nigeria) -->
+ <item>yue-HK</item> <!-- Cantonese (Hong Kong) -->
<item>zgh-MA</item> <!-- Standard Moroccan Tamazight (Morocco) -->
<item>zh-Hans-CN</item> <!-- Chinese (Simplified Han,China) -->
<item>zh-Hans-HK</item> <!-- Chinese (Simplified Han,Hong Kong) -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 510e6af..880944e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -392,6 +392,13 @@
This indicates that a work profile has been deleted. [CHAR LIMIT=NONE]-->
<string name="work_profile_deleted_description_dpm_wipe">Your work profile is no longer available on this device.</string>
+ <!-- Content title for a notification. This indicates that network logging was activated by
+ a device owner. [CHAR LIMIT=NONE]-->
+ <string name="network_logging_notification_title">Network traffic is being monitored</string>
+ <!-- Content text for a notification. Tapping opens a dialog with more information on network
+ logging. [CHAR LIMIT=NONE]-->
+ <string name="network_logging_notification_text">Tap for more details</string>
+
<!-- Factory reset warning dialog strings--> <skip />
<!-- Shows up in the dialog's title to warn about an impeding factory reset. [CHAR LIMIT=NONE] -->
<string name="factory_reset_warning">Your device will be erased</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7c50f62..ac502e0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1121,6 +1121,8 @@
<java-symbol type="string" name="work_profile_deleted_description" />
<java-symbol type="string" name="work_profile_deleted_details" />
<java-symbol type="string" name="work_profile_deleted_description_dpm_wipe" />
+ <java-symbol type="string" name="network_logging_notification_title" />
+ <java-symbol type="string" name="network_logging_notification_text" />
<java-symbol type="string" name="factory_reset_warning" />
<java-symbol type="string" name="factory_reset_message" />
<java-symbol type="string" name="lockscreen_transport_play_description" />
@@ -1231,6 +1233,7 @@
<java-symbol type="drawable" name="ic_print" />
<java-symbol type="drawable" name="ic_print_error" />
<java-symbol type="drawable" name="ic_grayedout_printer" />
+ <java-symbol type="drawable" name="ic_qs_network_logging" />
<java-symbol type="drawable" name="jog_dial_arrow_long_left_green" />
<java-symbol type="drawable" name="jog_dial_arrow_long_right_red" />
<java-symbol type="drawable" name="jog_dial_arrow_short_left_and_right" />
@@ -1246,6 +1249,15 @@
<java-symbol type="drawable" name="platlogo" />
<java-symbol type="drawable" name="stat_notify_sync_error" />
<java-symbol type="drawable" name="stat_notify_wifi_in_range" />
+ <java-symbol type="drawable" name="ic_signal_wifi_badged_0_bars" />
+ <java-symbol type="drawable" name="ic_signal_wifi_badged_1_bar" />
+ <java-symbol type="drawable" name="ic_signal_wifi_badged_2_bars" />
+ <java-symbol type="drawable" name="ic_signal_wifi_badged_3_bars" />
+ <java-symbol type="drawable" name="ic_signal_wifi_badged_4_bars" />
+ <java-symbol type="drawable" name="ic_signal_wifi_badged_4k" />
+ <java-symbol type="drawable" name="ic_signal_wifi_badged_hd" />
+ <java-symbol type="drawable" name="ic_signal_wifi_badged_sd" />
+ <java-symbol type="drawable" name="ic_signal_wifi_badged_ld" />
<java-symbol type="drawable" name="stat_notify_rssi_in_range" />
<java-symbol type="drawable" name="stat_sys_gps_on" />
<java-symbol type="drawable" name="stat_sys_tether_wifi" />
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 4699fd5..a0a9e01 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -33,7 +33,9 @@
mockito-target-minus-junit4 \
espresso-core \
ub-uiautomator \
- platform-test-annotations
+ platform-test-annotations \
+ compatibility-device-util
+
LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt telephony-common org.apache.http.legacy
LOCAL_PACKAGE_NAME := FrameworksCoreTests
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index ba1a55d..cae523c 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1107,6 +1107,13 @@
</intent-filter>
</activity>
+ <activity android:name="android.app.Activity" android:label="Empty Activity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
<!-- Activity-level metadata -->
<meta-data android:name="com.android.frameworks.coretests.isApp" android:value="true" />
<meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" />
diff --git a/core/tests/coretests/README b/core/tests/coretests/README
new file mode 100644
index 0000000..4a69843
--- /dev/null
+++ b/core/tests/coretests/README
@@ -0,0 +1,50 @@
+* 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.
+
+
+INTRODUCTION
+
+The Android platform core tests (APCT) consist of unit tests for core platform
+functionality. These differ from CTS in that they are not necessarily testing
+public APIs and are not guaranteed to work outside of AOSP builds.
+
+
+INSTRUCTIONS
+
+To run a test or set of tests, first build the FrameworksCoreTests package:
+
+ make FrameworksCoreTests
+
+Next, install the resulting APK and run tests as you would normal JUnit tests:
+
+ adb install out/target/product/.../data/app/FrameworksCoreTests/FrameworksCoreTests.apk
+ adb shell am instrument -w \
+ com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
+
+To run a tests within a specific package, add the following argument AFTER -w:
+
+ -e package android.content.pm
+
+To run a specific test or method within a test:
+
+ -e class android.content.pm.PackageParserTest
+ -e class android.content.pm.PackageParserTest#testComputeMinSdkVersion
+
+To run tests in debug mode:
+
+ -e debug true
+
+For more arguments, see the guide to command=line testing:
+
+ https://developer.android.com/studio/test/command-line.html
diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
new file mode 100644
index 0000000..2a3c22c
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.os.Build;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PackageParserTest {
+ private static final String RELEASED = null;
+ private static final String OLDER_PRE_RELEASE = "A";
+ private static final String PRE_RELEASE = "B";
+ private static final String NEWER_PRE_RELEASE = "C";
+
+ private static final String[] CODENAMES_RELEASED = { /* empty */ };
+ private static final String[] CODENAMES_PRE_RELEASE = { PRE_RELEASE };
+
+ private static final int OLDER_VERSION = 10;
+ private static final int PLATFORM_VERSION = 20;
+ private static final int NEWER_VERSION = 30;
+
+ private void verifyComputeMinSdkVersion(int minSdkVersion, String minSdkCodename,
+ boolean isPlatformReleased, int expectedMinSdk) {
+ final String[] outError = new String[1];
+ final int result = PackageParser.computeMinSdkVersion(
+ minSdkVersion,
+ minSdkCodename,
+ PLATFORM_VERSION,
+ isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE,
+ outError);
+
+ assertEquals(result, expectedMinSdk);
+
+ if (expectedMinSdk == -1) {
+ assertNotNull(outError[0]);
+ } else {
+ assertNull(outError[0]);
+ }
+ }
+
+ @Test
+ public void testComputeMinSdkVersion_preReleasePlatform() {
+ // Do allow older release minSdkVersion on pre-release platform.
+ // APP: Released API 10
+ // DEV: Pre-release API 20
+ verifyComputeMinSdkVersion(OLDER_VERSION, RELEASED, false, OLDER_VERSION);
+
+ // Do allow same release minSdkVersion on pre-release platform.
+ // APP: Released API 20
+ // DEV: Pre-release API 20
+ verifyComputeMinSdkVersion(PLATFORM_VERSION, RELEASED, false, PLATFORM_VERSION);
+
+ // Don't allow newer release minSdkVersion on pre-release platform.
+ // APP: Released API 30
+ // DEV: Pre-release API 20
+ verifyComputeMinSdkVersion(NEWER_VERSION, RELEASED, false, -1);
+
+ // Don't allow older pre-release minSdkVersion on pre-release platform.
+ // APP: Pre-release API 10
+ // DEV: Pre-release API 20
+ verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1);
+
+ // Do allow same pre-release minSdkVersion on pre-release platform,
+ // but overwrite the specified version with CUR_DEVELOPMENT.
+ // APP: Pre-release API 20
+ // DEV: Pre-release API 20
+ verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false,
+ Build.VERSION_CODES.CUR_DEVELOPMENT);
+
+ // Don't allow newer pre-release minSdkVersion on pre-release platform.
+ // APP: Pre-release API 30
+ // DEV: Pre-release API 20
+ verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1);
+ }
+
+ @Test
+ public void testComputeMinSdkVersion_releasedPlatform() {
+ // Do allow older release minSdkVersion on released platform.
+ // APP: Released API 10
+ // DEV: Released API 20
+ verifyComputeMinSdkVersion(OLDER_VERSION, RELEASED, true, OLDER_VERSION);
+
+ // Do allow same release minSdkVersion on released platform.
+ // APP: Released API 20
+ // DEV: Released API 20
+ verifyComputeMinSdkVersion(PLATFORM_VERSION, RELEASED, true, PLATFORM_VERSION);
+
+ // Don't allow newer release minSdkVersion on released platform.
+ // APP: Released API 30
+ // DEV: Released API 20
+ verifyComputeMinSdkVersion(NEWER_VERSION, RELEASED, true, -1);
+
+ // Don't allow older pre-release minSdkVersion on released platform.
+ // APP: Pre-release API 10
+ // DEV: Released API 20
+ verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1);
+
+ // Don't allow same pre-release minSdkVersion on released platform.
+ // APP: Pre-release API 20
+ // DEV: Released API 20
+ verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1);
+
+ // Don't allow newer pre-release minSdkVersion on released platform.
+ // APP: Pre-release API 30
+ // DEV: Released API 20
+ verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1);
+ }
+
+ private void verifyComputeTargetSdkVersion(int targetSdkVersion, String targetSdkCodename,
+ boolean isPlatformReleased, int expectedTargetSdk) {
+ final String[] outError = new String[1];
+ final int result = PackageParser.computeTargetSdkVersion(
+ targetSdkVersion,
+ targetSdkCodename,
+ PLATFORM_VERSION,
+ isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE,
+ outError);
+
+ assertEquals(result, expectedTargetSdk);
+
+ if (expectedTargetSdk == -1) {
+ assertNotNull(outError[0]);
+ } else {
+ assertNull(outError[0]);
+ }
+ }
+
+ @Test
+ public void testComputeTargetSdkVersion_preReleasePlatform() {
+ // Do allow older release targetSdkVersion on pre-release platform.
+ // APP: Released API 10
+ // DEV: Pre-release API 20
+ verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, OLDER_VERSION);
+
+ // Do allow same release targetSdkVersion on pre-release platform.
+ // APP: Released API 20
+ // DEV: Pre-release API 20
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, PLATFORM_VERSION);
+
+ // Do allow newer release targetSdkVersion on pre-release platform.
+ // APP: Released API 30
+ // DEV: Pre-release API 20
+ verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, NEWER_VERSION);
+
+ // Don't allow older pre-release targetSdkVersion on pre-release platform.
+ // APP: Pre-release API 10
+ // DEV: Pre-release API 20
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1);
+
+ // Do allow same pre-release targetSdkVersion on pre-release platform,
+ // but overwrite the specified version with CUR_DEVELOPMENT.
+ // APP: Pre-release API 20
+ // DEV: Pre-release API 20
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false,
+ Build.VERSION_CODES.CUR_DEVELOPMENT);
+
+ // Don't allow newer pre-release targetSdkVersion on pre-release platform.
+ // APP: Pre-release API 30
+ // DEV: Pre-release API 20
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1);
+ }
+
+ @Test
+ public void testComputeTargetSdkVersion_releasedPlatform() {
+ // Do allow older release targetSdkVersion on released platform.
+ // APP: Released API 10
+ // DEV: Released API 20
+ verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, OLDER_VERSION);
+
+ // Do allow same release targetSdkVersion on released platform.
+ // APP: Released API 20
+ // DEV: Released API 20
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, PLATFORM_VERSION);
+
+ // Do allow newer release targetSdkVersion on released platform.
+ // APP: Released API 30
+ // DEV: Released API 20
+ verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, NEWER_VERSION);
+
+ // Don't allow older pre-release targetSdkVersion on released platform.
+ // APP: Pre-release API 10
+ // DEV: Released API 20
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1);
+
+ // Don't allow same pre-release targetSdkVersion on released platform.
+ // APP: Pre-release API 20
+ // DEV: Released API 20
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1);
+
+ // Don't allow newer pre-release targetSdkVersion on released platform.
+ // APP: Pre-release API 30
+ // DEV: Released API 20
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1);
+ }
+}
diff --git a/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java b/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java
index 9a81401..bdc0200 100644
--- a/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java
+++ b/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java
@@ -77,7 +77,7 @@
final NetworkRecommendationProvider.ResultCallback callback =
new NetworkRecommendationProvider.ResultCallback(mMockRemoteCallback, sequence);
- final RecommendationResult result = new RecommendationResult(null);
+ final RecommendationResult result = RecommendationResult.createDoNotConnectRecommendation();
callback.onResult(result);
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -93,7 +93,7 @@
final NetworkRecommendationProvider.ResultCallback callback =
new NetworkRecommendationProvider.ResultCallback(mMockRemoteCallback, sequence);
- final RecommendationResult result = new RecommendationResult(null);
+ final RecommendationResult result = RecommendationResult.createDoNotConnectRecommendation();
callback.onResult(result);
try {
diff --git a/core/tests/coretests/src/android/net/ScoredNetworkTest.java b/core/tests/coretests/src/android/net/ScoredNetworkTest.java
new file mode 100644
index 0000000..9c3346e
--- /dev/null
+++ b/core/tests/coretests/src/android/net/ScoredNetworkTest.java
@@ -0,0 +1,169 @@
+/*
+ t Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.net;
+
+import static org.junit.Assert.*;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/** Unit tests for {@link ScoredNetwork}. */
+@RunWith(AndroidJUnit4.class)
+public class ScoredNetworkTest {
+
+ private static final int RSSI_START = -110;
+ private static final int TEST_RSSI = -50;
+ private static final byte TEST_SCORE = 5;
+ private static final RssiCurve CURVE =
+ new RssiCurve(RSSI_START, 10, new byte[] {-1, 0, 1, 2, 3, 4, TEST_SCORE, 6, 7});
+
+ private static final byte RANKING_SCORE_OFFSET = 13;
+ private static final Bundle ATTRIBUTES;
+ static {
+ ATTRIBUTES = new Bundle();
+ ATTRIBUTES.putInt(
+ ScoredNetwork.ATTRIBUTES_KEY_RANKING_SCORE_OFFSET, RANKING_SCORE_OFFSET);
+ }
+
+ private static final NetworkKey KEY
+ = new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00"));
+
+ @Test
+ public void calculateRankingOffsetShouldThrowUnsupportedOperationException() {
+ // No curve or ranking score offset set in curve
+ ScoredNetwork scoredNetwork = new ScoredNetwork(KEY, null);
+ try {
+ scoredNetwork.calculateRankingScore(TEST_RSSI);
+ fail("Should have thrown UnsupportedOperationException");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void calculateRankingOffsetWithRssiCurveShouldReturnExpectedScore() {
+ ScoredNetwork scoredNetwork = new ScoredNetwork(KEY, CURVE);
+ assertEquals(TEST_SCORE << Byte.SIZE, scoredNetwork.calculateRankingScore(TEST_RSSI));
+ }
+
+ @Test
+ public void rankingScoresShouldDifferByRankingScoreOffset() {
+ ScoredNetwork scoredNetwork1 = new ScoredNetwork(KEY, CURVE);
+ ScoredNetwork scoredNetwork2
+ = new ScoredNetwork(KEY, CURVE, false /* meteredHint */, ATTRIBUTES);
+ int scoreDifference =
+ scoredNetwork2.calculateRankingScore(TEST_RSSI)
+ - scoredNetwork1.calculateRankingScore(TEST_RSSI);
+ assertEquals(RANKING_SCORE_OFFSET, scoreDifference);
+ }
+
+ @Test
+ public void calculateRankingScoreShouldNotResultInIntegerOverflow() {
+ Bundle attr = new Bundle();
+ attr.putInt(ScoredNetwork.ATTRIBUTES_KEY_RANKING_SCORE_OFFSET, Integer.MAX_VALUE);
+ ScoredNetwork scoredNetwork
+ = new ScoredNetwork(KEY, CURVE, false /* meteredHint */, attr);
+ assertEquals(Integer.MAX_VALUE, scoredNetwork.calculateRankingScore(TEST_RSSI));
+ }
+
+ @Test
+ public void calculateRankingScoreShouldNotResultInIntegerUnderflow() {
+ Bundle attr = new Bundle();
+ attr.putInt(ScoredNetwork.ATTRIBUTES_KEY_RANKING_SCORE_OFFSET, Integer.MIN_VALUE);
+ ScoredNetwork scoredNetwork =
+ new ScoredNetwork(KEY, CURVE, false /* meteredHint */, attr);
+ assertEquals(Integer.MIN_VALUE, scoredNetwork.calculateRankingScore(RSSI_START));
+ }
+
+ @Test
+ public void hasRankingScoreShouldReturnFalse() {
+ ScoredNetwork network = new ScoredNetwork(KEY, null /* rssiCurve */);
+ assertFalse(network.hasRankingScore());
+ }
+
+ @Test
+ public void hasRankingScoreShouldReturnTrueWhenAttributesHasRankingScoreOffset() {
+ ScoredNetwork network =
+ new ScoredNetwork(KEY, null /* rssiCurve */, false /* meteredHint */, ATTRIBUTES);
+ assertTrue(network.hasRankingScore());
+ }
+
+ @Test
+ public void hasRankingScoreShouldReturnTrueWhenCurveIsPresent() {
+ ScoredNetwork network =
+ new ScoredNetwork(KEY, CURVE , false /* meteredHint */);
+ assertTrue(network.hasRankingScore());
+ }
+
+ @Test
+ public void shouldWriteAndReadFromParcelWhenAllFieldsSet() {
+ ScoredNetwork network = new ScoredNetwork(KEY, CURVE, true /* meteredHint */, ATTRIBUTES);
+ ScoredNetwork newNetwork;
+
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ network.writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+ newNetwork = ScoredNetwork.CREATOR.createFromParcel(parcel);
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ }
+ }
+ assertEquals(CURVE.start, newNetwork.rssiCurve.start);
+ assertEquals(CURVE.bucketWidth, newNetwork.rssiCurve.bucketWidth);
+ assertTrue(Arrays.equals(CURVE.rssiBuckets, newNetwork.rssiCurve.rssiBuckets));
+ assertTrue(newNetwork.meteredHint);
+ assertNotNull(newNetwork.attributes);
+ assertEquals(
+ RANKING_SCORE_OFFSET,
+ newNetwork.attributes.getInt(ScoredNetwork.ATTRIBUTES_KEY_RANKING_SCORE_OFFSET));
+ }
+
+ @Test
+ public void shouldWriteAndReadFromParcelWithoutBundle() {
+ ScoredNetwork network = new ScoredNetwork(KEY, CURVE, true /* meteredHint */);
+ ScoredNetwork newNetwork;
+
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ network.writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+ newNetwork = ScoredNetwork.CREATOR.createFromParcel(parcel);
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ }
+ }
+ assertEquals(CURVE.start, newNetwork.rssiCurve.start);
+ assertEquals(CURVE.bucketWidth, newNetwork.rssiCurve.bucketWidth);
+ assertTrue(Arrays.equals(CURVE.rssiBuckets, newNetwork.rssiCurve.rssiBuckets));
+ assertTrue(newNetwork.meteredHint);
+ assertNull(newNetwork.attributes);
+ }
+}
diff --git a/core/tests/coretests/src/android/text/method/WordIteratorTest.java b/core/tests/coretests/src/android/text/method/WordIteratorTest.java
index 28a480d..66cf65f 100644
--- a/core/tests/coretests/src/android/text/method/WordIteratorTest.java
+++ b/core/tests/coretests/src/android/text/method/WordIteratorTest.java
@@ -516,4 +516,34 @@
assertFalse(wordIterator.isOnPunctuation(text.length()));
assertFalse(wordIterator.isOnPunctuation(text.length() + 1));
}
+
+ @SmallTest
+ public void testApostropheMiddleOfWord() {
+ // These tests confirm that the word "isn't" is treated like one word.
+ final String text = "isn't he";
+ WordIterator wordIterator = new WordIterator(Locale.ENGLISH);
+ wordIterator.setCharSequence(text, 0, text.length());
+
+ assertEquals(text.indexOf('i'), wordIterator.preceding(text.indexOf('h')));
+ assertEquals(text.indexOf('t') + 1, wordIterator.following(text.indexOf('i')));
+
+ assertTrue(wordIterator.isBoundary(text.indexOf('i')));
+ assertFalse(wordIterator.isBoundary(text.indexOf('\'')));
+ assertFalse(wordIterator.isBoundary(text.indexOf('t')));
+ assertTrue(wordIterator.isBoundary(text.indexOf('t') + 1));
+ assertTrue(wordIterator.isBoundary(text.indexOf('h')));
+
+ assertEquals(text.indexOf('i'), wordIterator.getBeginning(text.indexOf('i')));
+ assertEquals(text.indexOf('i'), wordIterator.getBeginning(text.indexOf('n')));
+ assertEquals(text.indexOf('i'), wordIterator.getBeginning(text.indexOf('\'')));
+ assertEquals(text.indexOf('i'), wordIterator.getBeginning(text.indexOf('t')));
+ assertEquals(text.indexOf('i'), wordIterator.getBeginning(text.indexOf('t') + 1));
+ assertEquals(text.indexOf('h'), wordIterator.getBeginning(text.indexOf('h')));
+
+ assertEquals(text.indexOf('t') + 1, wordIterator.getEnd(text.indexOf('i')));
+ assertEquals(text.indexOf('t') + 1, wordIterator.getEnd(text.indexOf('n')));
+ assertEquals(text.indexOf('t') + 1, wordIterator.getEnd(text.indexOf('\'')));
+ assertEquals(text.indexOf('t') + 1, wordIterator.getEnd(text.indexOf('t')));
+ assertEquals(text.indexOf('e') + 1, wordIterator.getEnd(text.indexOf('h')));
+ }
}
diff --git a/core/tests/coretests/src/android/view/ViewInvalidateTest.java b/core/tests/coretests/src/android/view/ViewInvalidateTest.java
new file mode 100644
index 0000000..4db70ec
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewInvalidateTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static junit.framework.Assert.assertFalse;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.widget.FrameLayout;
+
+import com.android.compatibility.common.util.WidgetTestUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of invalidates, drawing, and the flags that support them
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class ViewInvalidateTest {
+ @Rule
+ public ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class);
+
+ private static final int INVAL_TEST_FLAG_MASK = View.PFLAG_DIRTY
+ | View.PFLAG_DIRTY_OPAQUE
+ | View.PFLAG_DRAWN
+ | View.PFLAG_DRAWING_CACHE_VALID
+ | View.PFLAG_INVALIDATED
+ | View.PFLAG_DRAW_ANIMATION;
+
+ @Before
+ public void setup() throws Throwable {
+ // separate runnable to initialize, so ref is safe to pass to runOnMainAndDrawSync
+ mActivityRule.runOnUiThread(() -> {
+ mParent = new FrameLayout(getContext());
+ mChild = new View(getContext());
+ });
+
+ // attached view is drawn once
+ WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mParent, () -> {
+ mParent.addView(mChild);
+ getActivity().setContentView(mParent);
+
+ // 'invalidated', but not yet drawn
+ validateInvalFlags(mChild, View.PFLAG_INVALIDATED);
+ });
+ }
+
+ @After
+ public void teardown() {
+ // ensure we don't share views between tests
+ mParent = null;
+ mChild = null;
+ }
+
+ Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+
+ Activity getActivity() {
+ return mActivityRule.getActivity();
+ }
+
+ private ViewGroup mParent;
+ private View mChild;
+
+ private static void validateInvalFlags(View view, int... expectedFlagArray) {
+ int expectedFlags = 0;
+ for (int expectedFlag : expectedFlagArray) {
+ expectedFlags |= expectedFlag;
+ }
+
+ final int observedFlags = view.mPrivateFlags & INVAL_TEST_FLAG_MASK;
+ assertEquals(String.format("expect %x, observed %x", expectedFlags, observedFlags),
+ expectedFlags, observedFlags);
+ }
+
+ private static ViewRootImpl getViewRoot(View view) {
+ ViewParent parent = view.getParent();
+ while (parent != null) {
+ if (parent instanceof ViewRootImpl) {
+ return (ViewRootImpl) parent;
+ }
+ parent = parent.getParent();
+ }
+ return null;
+ }
+
+ @UiThreadTest
+ @Test
+ public void testInvalidate_behavior() throws Throwable {
+ validateInvalFlags(mChild,
+ View.PFLAG_DRAWING_CACHE_VALID,
+ View.PFLAG_DRAWN);
+ validateInvalFlags(mParent,
+ View.PFLAG_DRAWING_CACHE_VALID,
+ View.PFLAG_DRAWN);
+ assertFalse(getViewRoot(mParent).mTraversalScheduled);
+
+ mChild.invalidate();
+
+ // no longer drawn, is now invalidated
+ validateInvalFlags(mChild,
+ View.PFLAG_DIRTY,
+ View.PFLAG_INVALIDATED);
+
+ // parent drawing cache no longer valid, marked dirty
+ validateInvalFlags(mParent,
+ View.PFLAG_DRAWN,
+ View.PFLAG_DIRTY);
+ assertTrue(getViewRoot(mParent).mTraversalScheduled);
+ }
+
+ @UiThreadTest
+ @Test
+ public void testInvalidate_false() {
+ // Invalidate makes it invalid
+ validateInvalFlags(mChild,
+ View.PFLAG_DRAWING_CACHE_VALID,
+ View.PFLAG_DRAWN);
+
+ mChild.invalidate(/*don't invalidate cache*/ false);
+
+ // drawn is cleared, dirty set, nothing else changed
+ validateInvalFlags(mChild,
+ View.PFLAG_DRAWING_CACHE_VALID,
+ View.PFLAG_DIRTY);
+ }
+
+ @Test
+ public void testInvalidate_simple() throws Throwable {
+ // simple invalidate, which marks the view invalid
+ WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mParent, () -> {
+ validateInvalFlags(mChild,
+ View.PFLAG_DRAWING_CACHE_VALID,
+ View.PFLAG_DRAWN);
+
+ mChild.invalidate();
+
+ validateInvalFlags(mChild,
+ View.PFLAG_DIRTY,
+ View.PFLAG_INVALIDATED);
+ });
+
+ // after draw pass, view has drawn, no longer invalid
+ mActivityRule.runOnUiThread(() -> {
+ validateInvalFlags(mChild,
+ View.PFLAG_DRAWING_CACHE_VALID,
+ View.PFLAG_DRAWN);
+ });
+ }
+
+ @UiThreadTest
+ @Test
+ public void testInvalidate_manualUpdateDisplayList() {
+ // Invalidate makes it invalid
+ validateInvalFlags(mChild,
+ View.PFLAG_DRAWING_CACHE_VALID,
+ View.PFLAG_DRAWN);
+
+ mChild.invalidate();
+ validateInvalFlags(mChild,
+ View.PFLAG_DIRTY,
+ View.PFLAG_INVALIDATED);
+
+ // updateDisplayListIfDirty makes it valid again, but invalidate still set,
+ // since it's cleared by View#draw(canvas, parent, drawtime)
+ mChild.updateDisplayListIfDirty();
+ validateInvalFlags(mChild,
+ View.PFLAG_DRAWING_CACHE_VALID,
+ View.PFLAG_DRAWN,
+ View.PFLAG_INVALIDATED);
+ }
+
+ @UiThreadTest
+ @Test
+ public void testInvalidateChild_simple() {
+ validateInvalFlags(mParent,
+ View.PFLAG_DRAWING_CACHE_VALID,
+ View.PFLAG_DRAWN);
+ assertFalse(getViewRoot(mParent).mTraversalScheduled);
+
+ mParent.invalidateChild(mChild, new Rect(0, 0, 1, 1));
+
+ validateInvalFlags(mParent,
+ View.PFLAG_DIRTY,
+ View.PFLAG_DRAWN);
+ assertTrue(getViewRoot(mParent).mTraversalScheduled);
+ }
+
+ @Test
+ public void testInvalidateChild_childHardwareLayer() throws Throwable {
+ WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mParent, () -> {
+ // do in runnable, so tree won't be dirty
+ mChild.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ });
+
+ mActivityRule.runOnUiThread(() -> {
+ validateInvalFlags(mParent,
+ View.PFLAG_DRAWING_CACHE_VALID,
+ View.PFLAG_DRAWN);
+
+ mParent.invalidateChild(mChild, new Rect(0, 0, 1, 1));
+
+ validateInvalFlags(mParent,
+ View.PFLAG_DIRTY,
+ View.PFLAG_DRAWN,
+ View.PFLAG_INVALIDATED);
+ });
+ }
+
+ @UiThreadTest
+ @Test
+ public void testInvalidateChild_legacyAnimation() throws Throwable {
+ mChild.mPrivateFlags |= View.PFLAG_DRAW_ANIMATION;
+
+ validateInvalFlags(mChild,
+ View.PFLAG_DRAW_ANIMATION,
+ View.PFLAG_DRAWING_CACHE_VALID,
+ View.PFLAG_DRAWN);
+ validateInvalFlags(mParent,
+ View.PFLAG_DRAWING_CACHE_VALID,
+ View.PFLAG_DRAWN);
+ assertFalse(getViewRoot(mParent).mIsAnimating);
+
+ mParent.invalidateChild(mChild, new Rect(0, 0, 1, 1));
+
+ validateInvalFlags(mChild,
+ View.PFLAG_DRAW_ANIMATION,
+ View.PFLAG_DRAWING_CACHE_VALID,
+ View.PFLAG_DRAWN);
+ validateInvalFlags(mParent,
+ View.PFLAG_DIRTY,
+ View.PFLAG_DRAW_ANIMATION, // carried up to parent
+ View.PFLAG_DRAWN);
+ assertTrue(getViewRoot(mParent).mIsAnimating);
+ }
+}
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 465e207..d8e6db7 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -294,9 +294,18 @@
<family lang="und-Olck">
<font weight="400" style="normal">NotoSansOlChiki-Regular.ttf</font>
</family>
+ <family lang="und-Phag">
+ <font weight="400" style="normal">NotoSansPhagsPa-Regular.ttf</font>
+ </family>
<family lang="und-Rjng">
<font weight="400" style="normal">NotoSansRejang-Regular.ttf</font>
</family>
+ <family lang="und-Runr">
+ <font weight="400" style="normal">NotoSansRunic-Regular.ttf</font>
+ </family>
+ <family lang="und-Samr">
+ <font weight="400" style="normal">NotoSansSamaritan-Regular.ttf</font>
+ </family>
<family lang="und-Saur">
<font weight="400" style="normal">NotoSansSaurashtra-Regular.ttf</font>
</family>
@@ -306,9 +315,19 @@
<family lang="und-Sylo">
<font weight="400" style="normal">NotoSansSylotiNagri-Regular.ttf</font>
</family>
+ <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
<family lang="und-Syre">
<font weight="400" style="normal">NotoSansSyriacEstrangela-Regular.ttf</font>
</family>
+ <family lang="und-Syrn">
+ <font weight="400" style="normal">NotoSansSyriacEastern-Regular.ttf</font>
+ </family>
+ <family lang="und-Syrj">
+ <font weight="400" style="normal">NotoSansSyriacWestern-Regular.ttf</font>
+ </family>
+ <family lang="und-Tglg">
+ <font weight="400" style="normal">NotoSansTagalog-Regular.ttf</font>
+ </family>
<family lang="und-Tagb">
<font weight="400" style="normal">NotoSansTagbanwa-Regular.ttf</font>
</family>
diff --git a/docs/__DEPRECATED__DO_NOT_EDIT__.txt b/docs/__DEPRECATED__DO_NOT_EDIT__.txt
new file mode 100644
index 0000000..3f8fc80c
--- /dev/null
+++ b/docs/__DEPRECATED__DO_NOT_EDIT__.txt
@@ -0,0 +1,16 @@
+### DEPRECATED: DO NOT EDIT ###
+
+The source files for developer.android.com are NO LONGER MAINTAINED HERE, as
+of 12/2016. Migration of content was completed on 10/16/2016.
+
+All authoring of content has been moved to Piper (go/dac-source).
+
+Exceptions and Caveats:
+
+- Reference documentation is still maintained via building of .java source files,
+ so you may continue to update JavaDoc comments to update documentation.
+
+- Sample code documentation is not maintained in Piper, but is published from
+ a separate code repository.
+
+For answers to further questions, please email: android-writers@google.com
diff --git a/docs/html/__DEPRECATED__DO_NOT_EDIT__.txt b/docs/html/__DEPRECATED__DO_NOT_EDIT__.txt
new file mode 100644
index 0000000..3f8fc80c
--- /dev/null
+++ b/docs/html/__DEPRECATED__DO_NOT_EDIT__.txt
@@ -0,0 +1,16 @@
+### DEPRECATED: DO NOT EDIT ###
+
+The source files for developer.android.com are NO LONGER MAINTAINED HERE, as
+of 12/2016. Migration of content was completed on 10/16/2016.
+
+All authoring of content has been moved to Piper (go/dac-source).
+
+Exceptions and Caveats:
+
+- Reference documentation is still maintained via building of .java source files,
+ so you may continue to update JavaDoc comments to update documentation.
+
+- Sample code documentation is not maintained in Piper, but is published from
+ a separate code repository.
+
+For answers to further questions, please email: android-writers@google.com
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index a041a28..b6db327 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -592,9 +592,13 @@
* @param isMutable True if the resulting bitmap should be mutable (i.e.
* its pixels can be modified)
* @return the new bitmap, or null if the copy could not be made.
+ * @throws IllegalArgumentException if config is {@link Config#HARDWARE} and isMutable is true
*/
public Bitmap copy(Config config, boolean isMutable) {
checkRecycled("Can't copy a recycled bitmap");
+ if (config == Config.HARDWARE && isMutable) {
+ throw new IllegalArgumentException("Hardware bitmaps are always immutable");
+ }
Bitmap b = nativeCopy(mNativePtr, config.nativeInt, isMutable);
if (b != null) {
b.setPremultiplied(mRequestPremultiplied);
@@ -1419,10 +1423,14 @@
* @param y The y coordinate (0...height-1) of the pixel to return
* @return The argb {@link Color} at the specified coordinate
* @throws IllegalArgumentException if x, y exceed the bitmap's bounds
+ * @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
*/
@ColorInt
public int getPixel(int x, int y) {
checkRecycled("Can't call getPixel() on a recycled bitmap");
+ if (getConfig() == Config.HARDWARE) {
+ throw new IllegalStateException("Can't access pixels in hardware Bitmaps");
+ }
checkPixelAccess(x, y);
return nativeGetPixel(mNativePtr, x, y);
}
@@ -1449,10 +1457,14 @@
* bounds of the bitmap, or if abs(stride) < width.
* @throws ArrayIndexOutOfBoundsException if the pixels array is too small
* to receive the specified number of pixels.
+ * @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
*/
public void getPixels(@ColorInt int[] pixels, int offset, int stride,
int x, int y, int width, int height) {
checkRecycled("Can't call getPixels() on a recycled bitmap");
+ if (getConfig() == Config.HARDWARE) {
+ throw new IllegalStateException("Can't access pixels in hardware Bitmaps");
+ }
if (width == 0 || height == 0) {
return; // nothing to do
}
@@ -1608,6 +1620,10 @@
/**
* Write the bitmap and its pixels to the parcel. The bitmap can be
* rebuilt from the parcel by calling CREATOR.createFromParcel().
+ *
+ * If this bitmap is {@link Config#HARDWARE}, it may be unparceled with a different pixel
+ * format (e.g. 565, 8888), but the content will be preserved to the best quality permitted
+ * by the final pixel format
* @param p Parcel object to write the bitmap data into
*/
public void writeToParcel(Parcel p, int flags) {
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index a5517f0..64a726b 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -51,8 +51,8 @@
/**
* If set, decode methods that take the Options object will attempt to
* reuse this bitmap when loading content. If the decode operation
- * cannot use this bitmap, the decode method will return
- * <code>null</code> and will throw an IllegalArgumentException. The
+ * cannot use this bitmap, the decode method will throw an
+ * {@link java.lang.IllegalArgumentException}. The
* current implementation necessitates that the reused bitmap be
* mutable, and the resulting reused bitmap will continue to remain
* mutable even when decoding a resource which would normally result in
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index fc873c4..7815ae1 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1357,7 +1357,7 @@
/**
* Return the paint's text size.
*
- * @return the paint's text size.
+ * @return the paint's text size in pixel units.
*/
public float getTextSize() {
return nGetTextSize(mNativePaint);
@@ -1366,7 +1366,7 @@
/**
* Set the paint's text size. This value must be > 0
*
- * @param textSize set the paint's text size.
+ * @param textSize set the paint's text size in pixel units.
*/
public void setTextSize(float textSize) {
nSetTextSize(mNativePaint, textSize);
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index a9058b1..f6585d6 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -98,8 +98,6 @@
// TODO: handle SRGB sanely
static PixelFormat internalFormatToPixelFormat(GLint internalFormat) {
switch (internalFormat) {
- case GL_ALPHA:
- return PIXEL_FORMAT_TRANSPARENT;
case GL_LUMINANCE:
return PIXEL_FORMAT_RGBA_8888;
case GL_SRGB8_ALPHA8:
@@ -217,8 +215,8 @@
}
const SkImageInfo& info = skBitmap.info();
- if (info.colorType() == kUnknown_SkColorType) {
- ALOGW("unable to create hardware bitmap of configuration");
+ if (info.colorType() == kUnknown_SkColorType || info.colorType() == kAlpha_8_SkColorType) {
+ ALOGW("unable to create hardware bitmap of colortype: %d", info.colorType());
return nullptr;
}
@@ -251,7 +249,7 @@
if (!uploadBitmapToGraphicBuffer(caches, bitmap, *buffer, format, type)) {
return nullptr;
}
- return sk_sp<Bitmap>(new Bitmap(buffer.get(), info));
+ return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info()));
}
sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(SkBitmap& bitmap) {
@@ -313,7 +311,8 @@
return nullptr;
}
SkImageInfo info = SkImageInfo::Make(graphicBuffer->getWidth(), graphicBuffer->getHeight(),
- kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+ kRGBA_8888_SkColorType, kPremul_SkAlphaType,
+ SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named));
return sk_sp<Bitmap>(new Bitmap(graphicBuffer.get(), info));
}
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index 03f0b4d..23a8655 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -63,6 +63,7 @@
private enum Result { DISMISSED, UNWANTED, WANTED_AS_IS };
private URL mUrl;
+ private String mUserAgent;
private Network mNetwork;
private CaptivePortal mCaptivePortal;
private NetworkCallback mNetworkCallback;
@@ -76,6 +77,8 @@
mCm = ConnectivityManager.from(this);
mNetwork = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_NETWORK);
mCaptivePortal = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
+ mUserAgent = getIntent().getParcelableExtra(
+ ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT);
mUrl = getUrl();
if (mUrl == null) {
// getUrl() failed to parse the url provided in the intent: bail out in a way that
@@ -252,6 +255,7 @@
}
private void testForCaptivePortal() {
+ // TODO: reuse NetworkMonitor facilities for consistent captive portal detection.
new Thread(new Runnable() {
public void run() {
// Give time for captive portal to open.
@@ -262,11 +266,14 @@
HttpURLConnection urlConnection = null;
int httpResponseCode = 500;
try {
- urlConnection = (HttpURLConnection) mUrl.openConnection();
+ urlConnection = (HttpURLConnection) mNetwork.openConnection(mUrl);
urlConnection.setInstanceFollowRedirects(false);
urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
urlConnection.setUseCaches(false);
+ if (mUserAgent != null) {
+ urlConnection.setRequestProperty("User-Agent", mUserAgent);
+ }
urlConnection.getInputStream();
httpResponseCode = urlConnection.getResponseCode();
} catch (IOException e) {
diff --git a/packages/PrintRecommendationService/AndroidManifest.xml b/packages/PrintRecommendationService/AndroidManifest.xml
index c6736d7..2e9342c 100644
--- a/packages/PrintRecommendationService/AndroidManifest.xml
+++ b/packages/PrintRecommendationService/AndroidManifest.xml
@@ -18,11 +18,11 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.printservice.recommendation"
- android:versionCode="1"
- android:versionName="1.0.0">
+ android:versionCode="2"
+ android:versionName="1.1.0">
<uses-sdk android:minSdkVersion="24"
- android:targetSdkVersion="24" />
+ android:targetSdkVersion="25" />
<uses-permission android:name="android.permission.INTERNET" />
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
index 0aa76a0..64f21b5 100755
--- a/packages/SettingsLib/res/values/config.xml
+++ b/packages/SettingsLib/res/values/config.xml
@@ -38,6 +38,12 @@
<!-- Intent key for package name values -->
<string name="config_helpIntentNameKey" translatable="false"></string>
+ <!-- Intent key for the package name keys -->
+ <string name="config_feedbackIntentExtraKey" translatable="false"></string>
+
+ <!-- Intent key for package name values -->
+ <string name="config_feedbackIntentNameKey" translatable="false"></string>
+
<!-- The apps that need to be hided when they are disabled -->
<string-array name="config_hideWhenDisabled_packageNames"></string-array>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
index c3acf0b..b037a3da 100644
--- a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
@@ -185,12 +185,18 @@
{resources.getString(R.string.config_helpPackageNameKey)};
String[] packageNameValue =
{resources.getString(R.string.config_helpPackageNameValue)};
- String intentExtraKey =
+ String helpIntentExtraKey =
resources.getString(R.string.config_helpIntentExtraKey);
- String intentNameKey =
+ String helpIntentNameKey =
resources.getString(R.string.config_helpIntentNameKey);
- intent.putExtra(intentExtraKey, packageNameKey);
- intent.putExtra(intentNameKey, packageNameValue);
+ String feedbackIntentExtraKey =
+ resources.getString(R.string.config_feedbackIntentExtraKey);
+ String feedbackIntentNameKey =
+ resources.getString(R.string.config_feedbackIntentNameKey);
+ intent.putExtra(helpIntentExtraKey, packageNameKey);
+ intent.putExtra(helpIntentNameKey, packageNameValue);
+ intent.putExtra(feedbackIntentExtraKey, packageNameKey);
+ intent.putExtra(feedbackIntentNameKey, packageNameValue);
}
intent.putExtra(EXTRA_THEME, 1 /* Light, dark action bar */);
TypedArray array = context.obtainStyledAttributes(new int[]{android.R.attr.colorPrimary});
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
index f3658c3..6ccba92 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
@@ -50,19 +50,25 @@
private final Map<String, DashboardCategory> mCategoryByKeyMap;
private List<DashboardCategory> mCategories;
+ private String mExtraAction;
public static CategoryManager get(Context context) {
+ return get(context, null);
+ }
+
+ public static CategoryManager get(Context context, String action) {
if (sInstance == null) {
- sInstance = new CategoryManager(context);
+ sInstance = new CategoryManager(context, action);
}
return sInstance;
}
- CategoryManager(Context context) {
+ CategoryManager(Context context, String action) {
mTileByComponentCache = new ArrayMap<>();
mCategoryByKeyMap = new ArrayMap<>();
mInterestingConfigChanges = new InterestingConfigChanges();
mInterestingConfigChanges.applyNewConfig(context.getResources());
+ mExtraAction = action;
}
public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) {
@@ -111,7 +117,7 @@
}
mCategoryByKeyMap.clear();
mCategories = TileUtils.getCategories(context, mTileByComponentCache,
- false /* categoryDefinedInManifest */);
+ false /* categoryDefinedInManifest */, mExtraAction);
for (DashboardCategory category : mCategories) {
mCategoryByKeyMap.put(category.key, category);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index d12e8c0..6c5a09d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -144,6 +144,19 @@
*/
public static List<DashboardCategory> getCategories(Context context,
Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest) {
+ return getCategories(context, cache, categoryDefinedInManifest, null);
+ }
+
+ /**
+ * Build a list of DashboardCategory.
+ * @param categoryDefinedInManifest If true, an dummy activity must exists in manifest to
+ * represent this category (eg: .Settings$DeviceSettings)
+ * @param extraAction additional intent filter action to be used to build the dashboard
+ * categories
+ */
+ public static List<DashboardCategory> getCategories(Context context,
+ Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest,
+ String extraAction) {
final long startTime = System.currentTimeMillis();
boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0)
!= 0;
@@ -162,6 +175,9 @@
if (setup) {
getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
getTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false);
+ if (extraAction != null) {
+ getTilesForAction(context, user, extraAction, cache, null, tiles, false);
+ }
}
}
diff --git a/packages/SettingsLib/tests/robotests/Android.mk b/packages/SettingsLib/tests/robotests/Android.mk
index c1108f6..208fa6d 100644
--- a/packages/SettingsLib/tests/robotests/Android.mk
+++ b/packages/SettingsLib/tests/robotests/Android.mk
@@ -26,7 +26,7 @@
LOCAL_PRIVILEGED_MODULE := true
LOCAL_JAVA_LIBRARIES := \
- junit4-target \
+ junit \
platform-robolectric-prebuilt
LOCAL_STATIC_JAVA_LIBRARIES := \
@@ -55,7 +55,7 @@
truth-prebuilt
LOCAL_JAVA_LIBRARIES := \
- junit4-target \
+ junit \
platform-robolectric-prebuilt
LOCAL_INSTRUMENTATION_FOR := SettingsLibShell
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/HelpUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/HelpUtilsTest.java
new file mode 100644
index 0000000..5d843c1
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/HelpUtilsTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.settingslib;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link HelpUtils}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class HelpUtilsTest {
+ private static final String PACKAGE_NAME_KEY = "package-name-key";
+ private static final String PACKAGE_NAME_VALUE = "package-name-value";
+ private static final String HELP_INTENT_EXTRA_KEY = "help-intent-extra";
+ private static final String HELP_INTENT_NAME_KEY = "help-intent-name";
+ private static final String FEEDBACK_INTENT_EXTRA_KEY = "feedback-intent-extra";
+ private static final String FEEDBACK_INTENT_NAME_KEY = "feedback-intent-name";
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getResources().getString(R.string.config_helpPackageNameKey))
+ .thenReturn(PACKAGE_NAME_KEY);
+ when(mContext.getResources().getString(R.string.config_helpPackageNameValue))
+ .thenReturn(PACKAGE_NAME_VALUE);
+ when(mContext.getResources().getString(R.string.config_helpIntentExtraKey))
+ .thenReturn(HELP_INTENT_EXTRA_KEY);
+ when(mContext.getResources().getString(R.string.config_helpIntentNameKey))
+ .thenReturn(HELP_INTENT_NAME_KEY);
+ when(mContext.getResources().getString(R.string.config_feedbackIntentExtraKey))
+ .thenReturn(FEEDBACK_INTENT_EXTRA_KEY);
+ when(mContext.getResources().getString(R.string.config_feedbackIntentNameKey))
+ .thenReturn(FEEDBACK_INTENT_NAME_KEY);
+
+ }
+
+ @Test
+ public void addIntentParameters_configTrue_argumentTrue() {
+ when(mContext.getResources().getBoolean(R.bool.config_sendPackageName)).thenReturn(true);
+ Intent intent = new Intent();
+
+ HelpUtils.addIntentParameters(
+ mContext, intent, null /* backupContext */, true /* sendPackageName */);
+
+ assertThat(intent.getStringArrayExtra(HELP_INTENT_EXTRA_KEY)).asList()
+ .containsExactly(PACKAGE_NAME_KEY);
+ assertThat(intent.getStringArrayExtra(HELP_INTENT_NAME_KEY)).asList()
+ .containsExactly(PACKAGE_NAME_VALUE);
+ assertThat(intent.getStringArrayExtra(FEEDBACK_INTENT_EXTRA_KEY)).asList()
+ .containsExactly(PACKAGE_NAME_KEY);
+ assertThat(intent.getStringArrayExtra(FEEDBACK_INTENT_NAME_KEY)).asList()
+ .containsExactly(PACKAGE_NAME_VALUE);
+ }
+
+ @Test
+ public void addIntentParameters_configTrue_argumentFalse() {
+ when(mContext.getResources().getBoolean(R.bool.config_sendPackageName)).thenReturn(true);
+ Intent intent = new Intent();
+
+ HelpUtils.addIntentParameters(
+ mContext, intent, null /* backupContext */, false /* sendPackageName */);
+
+ assertThat(intent.hasExtra(HELP_INTENT_EXTRA_KEY)).isFalse();
+ assertThat(intent.hasExtra(HELP_INTENT_NAME_KEY)).isFalse();
+ assertThat(intent.hasExtra(FEEDBACK_INTENT_EXTRA_KEY)).isFalse();
+ assertThat(intent.hasExtra(FEEDBACK_INTENT_NAME_KEY)).isFalse();
+ }
+
+ @Test
+ public void addIntentParameters_configFalse_argumentTrue() {
+ when(mContext.getResources().getBoolean(R.bool.config_sendPackageName)).thenReturn(false);
+ Intent intent = new Intent();
+
+ HelpUtils.addIntentParameters(
+ mContext, intent, null /* backupContext */, true /* sendPackageName */);
+
+ assertThat(intent.hasExtra(HELP_INTENT_EXTRA_KEY)).isFalse();
+ assertThat(intent.hasExtra(HELP_INTENT_NAME_KEY)).isFalse();
+ assertThat(intent.hasExtra(FEEDBACK_INTENT_EXTRA_KEY)).isFalse();
+ assertThat(intent.hasExtra(FEEDBACK_INTENT_NAME_KEY)).isFalse();
+ }
+
+ @Test
+ public void addIntentParameters_configFalse_argumentFalse() {
+ when(mContext.getResources().getBoolean(R.bool.config_sendPackageName)).thenReturn(false);
+ Intent intent = new Intent();
+
+ HelpUtils.addIntentParameters(
+ mContext, intent, null /* backupContext */, false /* sendPackageName */);
+
+ assertThat(intent.hasExtra(HELP_INTENT_EXTRA_KEY)).isFalse();
+ assertThat(intent.hasExtra(HELP_INTENT_NAME_KEY)).isFalse();
+ assertThat(intent.hasExtra(FEEDBACK_INTENT_EXTRA_KEY)).isFalse();
+ assertThat(intent.hasExtra(FEEDBACK_INTENT_NAME_KEY)).isFalse();
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 86b210a..c6c6aad 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -26,6 +26,8 @@
import android.content.res.Resources;
import android.os.Bundle;
import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings.Global;
import android.util.ArrayMap;
import android.util.Pair;
@@ -34,6 +36,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@@ -46,6 +49,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
@@ -59,6 +63,8 @@
private PackageManager mPackageManager;
@Mock
private Resources mResources;
+ @Mock
+ private UserManager mUserManager;
@Before
public void setUp() throws NameNotFoundException {
@@ -127,6 +133,30 @@
assertThat(outTiles.isEmpty()).isTrue();
}
+ @Test
+ public void getCategories_shouldHandleExtraIntentAction() {
+ final String testCategory = "category1";
+ final String testAction = "action1";
+ Map<Pair<String, String>, Tile> cache = new ArrayMap<>();
+ List<ResolveInfo> info = new ArrayList<>();
+ info.add(newInfo(true, testCategory));
+ Global.putInt(mContext.getContentResolver(), Global.DEVICE_PROVISIONED, 1);
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ List<UserHandle> userHandleList = new ArrayList<>();
+ userHandleList.add(UserHandle.CURRENT);
+ when(mUserManager.getUserProfiles()).thenReturn(userHandleList);
+
+ when(mPackageManager.queryIntentActivitiesAsUser(argThat(new ArgumentMatcher<Intent>() {
+ public boolean matches(Object event) {
+ return testAction.equals(((Intent) event).getAction());
+ }
+ }), anyInt(), anyInt())).thenReturn(info);
+
+ List<DashboardCategory> categoryList = TileUtils.getCategories(
+ mContext, cache, false /* categoryDefinedInManifest */, testAction);
+
+ assertThat(categoryList.get(0).tiles.get(0).category).isEqualTo(testCategory);
+ }
private ResolveInfo newInfo(boolean systemApp, String category) {
return newInfo(systemApp, category, null);
diff --git a/packages/SettingsProvider/Android.mk b/packages/SettingsProvider/Android.mk
index 2b833b2..710214c 100644
--- a/packages/SettingsProvider/Android.mk
+++ b/packages/SettingsProvider/Android.mk
@@ -3,7 +3,7 @@
LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := $(call all-subdir-java-files) \
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
src/com/android/providers/settings/EventLogTags.logtags
LOCAL_JAVA_LIBRARIES := telephony-common ims-common
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index c149876..3e62158 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -84,6 +84,10 @@
import java.util.Set;
import java.util.regex.Pattern;
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SYSTEM_UID;
+import static android.os.Process.SHELL_UID;
+
/**
* <p>
* This class is a content provider that publishes the system settings.
@@ -147,6 +151,7 @@
private static final int MUTATION_OPERATION_INSERT = 1;
private static final int MUTATION_OPERATION_DELETE = 2;
private static final int MUTATION_OPERATION_UPDATE = 3;
+ private static final int MUTATION_OPERATION_RESET = 4;
private static final String[] ALL_COLUMNS = new String[] {
Settings.NameValueTable._ID,
@@ -292,13 +297,17 @@
case Settings.CALL_METHOD_PUT_GLOBAL: {
String value = getSettingValue(args);
- insertGlobalSetting(name, value, requestingUserId, false);
+ String tag = getSettingTag(args);
+ final boolean makeDefault = getSettingMakeDefault(args);
+ insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false);
break;
}
case Settings.CALL_METHOD_PUT_SECURE: {
String value = getSettingValue(args);
- insertSecureSetting(name, value, requestingUserId, false);
+ String tag = getSettingTag(args);
+ final boolean makeDefault = getSettingMakeDefault(args);
+ insertSecureSetting(name, value, tag, makeDefault, requestingUserId, false);
break;
}
@@ -308,6 +317,20 @@
break;
}
+ case Settings.CALL_METHOD_RESET_GLOBAL: {
+ final int mode = getResetModeEnforcingPermission(args);
+ String tag = getSettingTag(args);
+ resetGlobalSetting(requestingUserId, mode, tag);
+ break;
+ }
+
+ case Settings.CALL_METHOD_RESET_SECURE: {
+ final int mode = getResetModeEnforcingPermission(args);
+ String tag = getSettingTag(args);
+ resetSecureSetting(requestingUserId, mode, tag);
+ break;
+ }
+
default: {
Slog.w(LOG_TAG, "call() with invalid method: " + method);
} break;
@@ -399,13 +422,15 @@
switch (table) {
case TABLE_GLOBAL: {
- if (insertGlobalSetting(name, value, UserHandle.getCallingUserId(), false)) {
+ if (insertGlobalSetting(name, value, null, false,
+ UserHandle.getCallingUserId(), false)) {
return Uri.withAppendedPath(Settings.Global.CONTENT_URI, name);
}
} break;
case TABLE_SECURE: {
- if (insertSecureSetting(name, value, UserHandle.getCallingUserId(), false)) {
+ if (insertSecureSetting(name, value, null, false,
+ UserHandle.getCallingUserId(), false)) {
return Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
}
} break;
@@ -503,12 +528,14 @@
switch (args.table) {
case TABLE_GLOBAL: {
final int userId = UserHandle.getCallingUserId();
- return updateGlobalSetting(args.name, value, userId, false) ? 1 : 0;
+ return updateGlobalSetting(args.name, value, null, false,
+ userId, false) ? 1 : 0;
}
case TABLE_SECURE: {
final int userId = UserHandle.getCallingUserId();
- return updateSecureSetting(args.name, value, userId, false) ? 1 : 0;
+ return updateSecureSetting(args.name, value, null, false,
+ userId, false) ? 1 : 0;
}
case TABLE_SYSTEM: {
@@ -586,10 +613,9 @@
SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
if (globalSettings != null) {
dumpSettingsLocked(globalSettings, pw);
+ pw.println();
+ globalSettings.dumpHistoricalOperations(pw);
}
- pw.println();
-
- globalSettings.dumpHistoricalOperations(pw);
}
pw.println("SECURE SETTINGS (user " + userId + ")");
@@ -597,20 +623,18 @@
SETTINGS_TYPE_SECURE, userId);
if (secureSettings != null) {
dumpSettingsLocked(secureSettings, pw);
+ pw.println();
+ secureSettings.dumpHistoricalOperations(pw);
}
- pw.println();
-
- secureSettings.dumpHistoricalOperations(pw);
pw.println("SYSTEM SETTINGS (user " + userId + ")");
SettingsState systemSettings = mSettingsRegistry.getSettingsLocked(
SETTINGS_TYPE_SYSTEM, userId);
if (systemSettings != null) {
dumpSettingsLocked(systemSettings, pw);
+ pw.println();
+ systemSettings.dumpHistoricalOperations(pw);
}
- pw.println();
-
- systemSettings.dumpHistoricalOperations(pw);
}
private void dumpSettingsLocked(SettingsState settingsState, PrintWriter pw) {
@@ -624,9 +648,16 @@
pw.print("_id:"); pw.print(toDumpString(setting.getId()));
pw.print(" name:"); pw.print(toDumpString(name));
if (setting.getPackageName() != null) {
- pw.print(" pkg:"); pw.print(toDumpString(setting.getPackageName()));
+ pw.print(" pkg:"); pw.print(setting.getPackageName());
}
pw.print(" value:"); pw.print(toDumpString(setting.getValue()));
+ if (setting.getDefaultValue() != null) {
+ pw.print(" default:"); pw.print(setting.getDefaultValue());
+ pw.print(" defaultSystemSet:"); pw.print(setting.isDefaultSystemSet());
+ }
+ if (setting.getTag() != null) {
+ pw.print(" tag:"); pw.print(setting.getTag());
+ }
pw.println();
}
}
@@ -691,73 +722,79 @@
// value with a forced update to ensure that all cross profile dependencies
// are taken into account. Also make sure the settings update to.. the same
// value passes the security checks, so clear binder calling id.
- if (newRestrictions.containsKey(UserManager.DISALLOW_SHARE_LOCATION)
- != prevRestrictions.containsKey(UserManager.DISALLOW_SHARE_LOCATION)) {
+ if (newRestrictions.getBoolean(UserManager.DISALLOW_SHARE_LOCATION)
+ != prevRestrictions.getBoolean(UserManager.DISALLOW_SHARE_LOCATION)) {
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
Setting setting = getSecureSetting(
Settings.Secure.LOCATION_PROVIDERS_ALLOWED, userId);
updateSecureSetting(Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- setting != null ? setting.getValue() : null, userId, true);
+ setting != null ? setting.getValue() : null, null,
+ true, userId, true);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
- if (newRestrictions.containsKey(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES)
- != prevRestrictions.containsKey(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES)) {
+ if (newRestrictions.getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES)
+ != prevRestrictions.getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES)) {
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
Setting setting = getGlobalSetting(Settings.Global.INSTALL_NON_MARKET_APPS);
+ String value = setting != null ? setting.getValue() : null;
updateGlobalSetting(Settings.Global.INSTALL_NON_MARKET_APPS,
- setting != null ? setting.getValue() : null, userId, true);
+ value, null, true, userId, true);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
- if (newRestrictions.containsKey(UserManager.DISALLOW_DEBUGGING_FEATURES)
- != prevRestrictions.containsKey(UserManager.DISALLOW_DEBUGGING_FEATURES)) {
+ if (newRestrictions.getBoolean(UserManager.DISALLOW_DEBUGGING_FEATURES)
+ != prevRestrictions.getBoolean(UserManager.DISALLOW_DEBUGGING_FEATURES)) {
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
Setting setting = getGlobalSetting(Settings.Global.ADB_ENABLED);
+ String value = setting != null ? setting.getValue() : null;
updateGlobalSetting(Settings.Global.ADB_ENABLED,
- setting != null ? setting.getValue() : null, userId, true);
+ value, null, true, userId, true);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
- if (newRestrictions.containsKey(UserManager.ENSURE_VERIFY_APPS)
- != prevRestrictions.containsKey(UserManager.ENSURE_VERIFY_APPS)) {
+ if (newRestrictions.getBoolean(UserManager.ENSURE_VERIFY_APPS)
+ != prevRestrictions.getBoolean(UserManager.ENSURE_VERIFY_APPS)) {
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
Setting enable = getGlobalSetting(
Settings.Global.PACKAGE_VERIFIER_ENABLE);
+ String enableValue = enable != null ? enable.getValue() : null;
updateGlobalSetting(Settings.Global.PACKAGE_VERIFIER_ENABLE,
- enable != null ? enable.getValue() : null, userId, true);
+ enableValue, null, true, userId, true);
Setting include = getGlobalSetting(
Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB);
+ String includeValue = include != null ? include.getValue() : null;
updateGlobalSetting(Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB,
- include != null ? include.getValue() : null, userId, true);
+ includeValue, null, true, userId, true);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
- if (newRestrictions.containsKey(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)
- != prevRestrictions.containsKey(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
+ if (newRestrictions.getBoolean(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)
+ != prevRestrictions.getBoolean(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
Setting setting = getGlobalSetting(
Settings.Global.PREFERRED_NETWORK_MODE);
+ String value = setting != null ? setting.getValue() : null;
updateGlobalSetting(Settings.Global.PREFERRED_NETWORK_MODE,
- setting != null ? setting.getValue() : null, userId, true);
+ value, null, true, userId, true);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -806,34 +843,49 @@
}
}
- private boolean updateGlobalSetting(String name, String value, int requestingUserId,
- boolean forceNotify) {
+ private boolean updateGlobalSetting(String name, String value, String tag,
+ boolean makeDefault, int requestingUserId, boolean forceNotify) {
if (DEBUG) {
- Slog.v(LOG_TAG, "updateGlobalSetting(" + name + ", " + value + ")");
+ Slog.v(LOG_TAG, "updateGlobalSetting(" + name + ", " + value + ", "
+ + ", " + tag + ", " + makeDefault + ", " + requestingUserId
+ + ", " + forceNotify + ")");
}
- return mutateGlobalSetting(name, value, requestingUserId, MUTATION_OPERATION_UPDATE,
- forceNotify);
+ return mutateGlobalSetting(name, value, tag, makeDefault, requestingUserId,
+ MUTATION_OPERATION_UPDATE, forceNotify, 0);
}
- private boolean insertGlobalSetting(String name, String value, int requestingUserId,
- boolean forceNotify) {
+ private boolean insertGlobalSetting(String name, String value, String tag,
+ boolean makeDefault, int requestingUserId, boolean forceNotify) {
if (DEBUG) {
- Slog.v(LOG_TAG, "insertGlobalSetting(" + name + ", " + value + ")");
+ Slog.v(LOG_TAG, "insertGlobalSetting(" + name + ", " + value + ", "
+ + ", " + tag + ", " + makeDefault + ", " + requestingUserId
+ + ", " + forceNotify + ")");
}
- return mutateGlobalSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT,
- forceNotify);
+ return mutateGlobalSetting(name, value, tag, makeDefault, requestingUserId,
+ MUTATION_OPERATION_INSERT, forceNotify, 0);
}
private boolean deleteGlobalSetting(String name, int requestingUserId, boolean forceNotify) {
if (DEBUG) {
- Slog.v(LOG_TAG, "deleteGlobalSettingLocked(" + name + ")");
+ Slog.v(LOG_TAG, "deleteGlobalSetting(" + name + ", " + requestingUserId
+ + ", " + forceNotify + ")");
}
- return mutateGlobalSetting(name, null, requestingUserId, MUTATION_OPERATION_DELETE,
- forceNotify);
+ return mutateGlobalSetting(name, null, null, false, requestingUserId,
+ MUTATION_OPERATION_DELETE, forceNotify, 0);
}
- private boolean mutateGlobalSetting(String name, String value, int requestingUserId,
- int operation, boolean forceNotify) {
+ private void resetGlobalSetting(int requestingUserId, int mode, String tag) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "resetGlobalSetting(" + requestingUserId + ", "
+ + mode + ", " + tag + ")");
+ }
+ mutateGlobalSetting(null, null, tag, false, requestingUserId,
+ MUTATION_OPERATION_RESET, false, mode);
+ }
+
+ private boolean mutateGlobalSetting(String name, String value, String tag,
+ boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
+ int mode) {
// Make sure the caller can change the settings - treated as secure.
enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
@@ -842,7 +894,7 @@
// If this is a setting that is currently restricted for this user, do not allow
// unrestricting changes.
- if (isGlobalOrSecureSettingRestrictedForUser(name, callingUserId, value,
+ if (name != null && isGlobalOrSecureSettingRestrictedForUser(name, callingUserId, value,
Binder.getCallingUid())) {
return false;
}
@@ -851,9 +903,9 @@
synchronized (mLock) {
switch (operation) {
case MUTATION_OPERATION_INSERT: {
- return mSettingsRegistry
- .insertSettingLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM,
- name, value, getCallingPackage(), forceNotify);
+ return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_GLOBAL,
+ UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
+ getCallingPackage(), forceNotify);
}
case MUTATION_OPERATION_DELETE: {
@@ -862,10 +914,15 @@
}
case MUTATION_OPERATION_UPDATE: {
- return mSettingsRegistry
- .updateSettingLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM,
- name, value, getCallingPackage(), forceNotify);
+ return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_GLOBAL,
+ UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
+ getCallingPackage(), forceNotify);
}
+
+ case MUTATION_OPERATION_RESET: {
+ mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_GLOBAL,
+ UserHandle.USER_SYSTEM, getCallingPackage(), mode, tag);
+ } return true;
}
}
@@ -934,39 +991,53 @@
}
}
- private boolean insertSecureSetting(String name, String value, int requestingUserId,
- boolean forceNotify) {
+ private boolean insertSecureSetting(String name, String value, String tag,
+ boolean makeDefault, int requestingUserId, boolean forceNotify) {
if (DEBUG) {
Slog.v(LOG_TAG, "insertSecureSetting(" + name + ", " + value + ", "
- + requestingUserId + ")");
+ + ", " + tag + ", " + makeDefault + ", " + requestingUserId
+ + ", " + forceNotify + ")");
}
- return mutateSecureSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT,
- forceNotify);
+ return mutateSecureSetting(name, value, tag, makeDefault, requestingUserId,
+ MUTATION_OPERATION_INSERT, forceNotify, 0);
}
private boolean deleteSecureSetting(String name, int requestingUserId, boolean forceNotify) {
if (DEBUG) {
- Slog.v(LOG_TAG, "deleteSecureSetting(" + name + ", " + requestingUserId + ")");
+ Slog.v(LOG_TAG, "deleteSecureSetting(" + name + ", " + requestingUserId
+ + ", " + forceNotify + ")");
}
- return mutateSecureSetting(name, null, requestingUserId, MUTATION_OPERATION_DELETE,
- forceNotify);
+ return mutateSecureSetting(name, null, null, false, requestingUserId,
+ MUTATION_OPERATION_DELETE, forceNotify, 0);
}
- private boolean updateSecureSetting(String name, String value, int requestingUserId,
- boolean forceNotify) {
+ private boolean updateSecureSetting(String name, String value, String tag,
+ boolean makeDefault, int requestingUserId, boolean forceNotify) {
if (DEBUG) {
Slog.v(LOG_TAG, "updateSecureSetting(" + name + ", " + value + ", "
- + requestingUserId + ")");
+ + ", " + tag + ", " + makeDefault + ", " + requestingUserId
+ + ", " + forceNotify +")");
}
- return mutateSecureSetting(name, value, requestingUserId, MUTATION_OPERATION_UPDATE,
- forceNotify);
+ return mutateSecureSetting(name, value, tag, makeDefault, requestingUserId,
+ MUTATION_OPERATION_UPDATE, forceNotify, 0);
}
- private boolean mutateSecureSetting(String name, String value, int requestingUserId,
- int operation, boolean forceNotify) {
+ private void resetSecureSetting(int requestingUserId, int mode, String tag) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "resetSecureSetting(" + requestingUserId + ", "
+ + mode + ", " + tag + ")");
+ }
+
+ mutateSecureSetting(null, null, tag, false, requestingUserId,
+ MUTATION_OPERATION_RESET, false, mode);
+ }
+
+ private boolean mutateSecureSetting(String name, String value, String tag,
+ boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
+ int mode) {
// Make sure the caller can change the settings.
enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
@@ -975,7 +1046,7 @@
// If this is a setting that is currently restricted for this user, do not allow
// unrestricting changes.
- if (isGlobalOrSecureSettingRestrictedForUser(name, callingUserId, value,
+ if (name != null && isGlobalOrSecureSettingRestrictedForUser(name, callingUserId, value,
Binder.getCallingUid())) {
return false;
}
@@ -990,7 +1061,8 @@
// Special cases for location providers (sigh).
if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
- return updateLocationProvidersAllowedLocked(value, owningUserId, forceNotify);
+ return updateLocationProvidersAllowedLocked(value, tag, owningUserId, makeDefault,
+ forceNotify);
}
// Mutate the value.
@@ -998,7 +1070,8 @@
switch (operation) {
case MUTATION_OPERATION_INSERT: {
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
- owningUserId, name, value, getCallingPackage(), forceNotify);
+ owningUserId, name, value, tag, makeDefault,
+ getCallingPackage(), forceNotify);
}
case MUTATION_OPERATION_DELETE: {
@@ -1008,8 +1081,14 @@
case MUTATION_OPERATION_UPDATE: {
return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SECURE,
- owningUserId, name, value, getCallingPackage(), forceNotify);
+ owningUserId, name, value, tag, makeDefault,
+ getCallingPackage(), forceNotify);
}
+
+ case MUTATION_OPERATION_RESET: {
+ mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SECURE,
+ UserHandle.USER_SYSTEM, getCallingPackage(), mode, tag);
+ } return true;
}
}
@@ -1138,7 +1217,7 @@
case MUTATION_OPERATION_INSERT: {
validateSystemSettingValue(name, value);
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SYSTEM,
- owningUserId, name, value, getCallingPackage(), false);
+ owningUserId, name, value, null, false, getCallingPackage(), false);
}
case MUTATION_OPERATION_DELETE: {
@@ -1149,7 +1228,7 @@
case MUTATION_OPERATION_UPDATE: {
validateSystemSettingValue(name, value);
return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SYSTEM,
- owningUserId, name, value, getCallingPackage(), false);
+ owningUserId, name, value, null, false, getCallingPackage(), false);
}
}
@@ -1240,7 +1319,8 @@
case Settings.Secure.ALWAYS_ON_VPN_APP:
case Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN:
// Whitelist system uid (ConnectivityService) and root uid to change always-on vpn
- if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) {
+ final int appId = UserHandle.getAppId(callingUid);
+ if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID) {
return false;
}
restriction = UserManager.DISALLOW_CONFIG_VPN;
@@ -1294,9 +1374,10 @@
String name, int userId) {
// System/root/shell can mutate whatever secure settings they want.
final int callingUid = Binder.getCallingUid();
- if (callingUid == android.os.Process.SYSTEM_UID
- || callingUid == Process.SHELL_UID
- || callingUid == Process.ROOT_UID) {
+ final int appId = UserHandle.getAppId(callingUid);
+ if (appId == android.os.Process.SYSTEM_UID
+ || appId == Process.SHELL_UID
+ || appId == Process.ROOT_UID) {
return;
}
@@ -1392,8 +1473,8 @@
*
* @returns whether the enabled location providers changed.
*/
- private boolean updateLocationProvidersAllowedLocked(String value, int owningUserId,
- boolean forceNotify) {
+ private boolean updateLocationProvidersAllowedLocked(String value, String tag,
+ int owningUserId, boolean makeDefault, boolean forceNotify) {
if (TextUtils.isEmpty(value)) {
return false;
}
@@ -1466,7 +1547,7 @@
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
owningUserId, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, newProviders,
- getCallingPackage(), forceNotify);
+ tag, makeDefault, getCallingPackage(), forceNotify);
}
private static void warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
@@ -1509,8 +1590,8 @@
}
Bundle result = new Bundle();
result.putString(Settings.NameValueTable.VALUE,
- setting != null && !setting.isNull() ? setting.getValue() : null);
- mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getkey());
+ !setting.isNull() ? setting.getValue() : null);
+ mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getKey());
return result;
}
@@ -1528,6 +1609,51 @@
return (args != null) ? args.getString(Settings.NameValueTable.VALUE) : null;
}
+ private static String getSettingTag(Bundle args) {
+ return (args != null) ? args.getString(Settings.CALL_METHOD_TAG_KEY) : null;
+ }
+
+ private static boolean getSettingMakeDefault(Bundle args) {
+ return (args != null) && args.getBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY);
+ }
+
+ private static int getResetModeEnforcingPermission(Bundle args) {
+ final int mode = (args != null) ? args.getInt(Settings.CALL_METHOD_RESET_MODE_KEY) : 0;
+ switch (mode) {
+ case Settings.RESET_MODE_UNTRUSTED_DEFAULTS: {
+ if (!isCallerSystemOrShellOrRootOnDebuggableBuild()) {
+ throw new SecurityException("Only system, shell/root on a "
+ + "debuggable build can reset to untrusted defaults");
+ }
+ return mode;
+ }
+ case Settings.RESET_MODE_UNTRUSTED_CHANGES: {
+ if (!isCallerSystemOrShellOrRootOnDebuggableBuild()) {
+ throw new SecurityException("Only system, shell/root on a "
+ + "debuggable build can reset untrusted changes");
+ }
+ return mode;
+ }
+ case Settings.RESET_MODE_TRUSTED_DEFAULTS: {
+ if (!isCallerSystemOrShellOrRootOnDebuggableBuild()) {
+ throw new SecurityException("Only system, shell/root on a "
+ + "debuggable build can reset to trusted defaults");
+ }
+ return mode;
+ }
+ case Settings.RESET_MODE_PACKAGE_DEFAULTS: {
+ return mode;
+ }
+ }
+ throw new IllegalArgumentException("Invalid reset mode: " + mode);
+ }
+
+ private static boolean isCallerSystemOrShellOrRootOnDebuggableBuild() {
+ final int appId = UserHandle.getAppId(Binder.getCallingUid());
+ return appId == SYSTEM_UID || (Build.IS_DEBUGGABLE
+ && (appId == SHELL_UID || appId == ROOT_UID));
+ }
+
private static String getValidTableOrThrow(Uri uri) {
if (uri.getPathSegments().size() > 0) {
String table = uri.getPathSegments().get(0);
@@ -1767,8 +1893,8 @@
private void ensureSettingsStateLocked(int key) {
if (mSettingsStates.get(key) == null) {
final int maxBytesPerPackage = getMaxBytesPerPackageForType(getTypeFromKey(key));
- SettingsState settingsState = new SettingsState(mLock, getSettingsFile(key), key,
- maxBytesPerPackage, mHandlerThread.getLooper());
+ SettingsState settingsState = new SettingsState(getContext(), mLock,
+ getSettingsFile(key), key, maxBytesPerPackage, mHandlerThread.getLooper());
mSettingsStates.put(key, settingsState);
}
}
@@ -1815,12 +1941,15 @@
}
public boolean insertSettingLocked(int type, int userId, String name, String value,
- String packageName, boolean forceNotify) {
+ String tag, boolean makeDefault, String packageName, boolean forceNotify) {
final int key = makeKey(type, userId);
+ boolean success = false;
SettingsState settingsState = peekSettingsStateLocked(key);
- final boolean success = settingsState != null
- && settingsState.insertSettingLocked(name, value, packageName);
+ if (settingsState != null) {
+ success = settingsState.insertSettingLocked(name, value,
+ tag, makeDefault, packageName);
+ }
if (forceNotify || success) {
notifyForSettingsChange(key, name);
@@ -1831,11 +1960,11 @@
public boolean deleteSettingLocked(int type, int userId, String name, boolean forceNotify) {
final int key = makeKey(type, userId);
+ boolean success = false;
SettingsState settingsState = peekSettingsStateLocked(key);
- if (settingsState == null) {
- return false;
+ if (settingsState != null) {
+ success = settingsState.deleteSettingLocked(name);
}
- final boolean success = settingsState.deleteSettingLocked(name);
if (forceNotify || success) {
notifyForSettingsChange(key, name);
@@ -1854,12 +1983,15 @@
}
public boolean updateSettingLocked(int type, int userId, String name, String value,
- String packageName, boolean forceNotify) {
+ String tag, boolean makeDefault, String packageName, boolean forceNotify) {
final int key = makeKey(type, userId);
+ boolean success = false;
SettingsState settingsState = peekSettingsStateLocked(key);
- final boolean success = settingsState != null
- && settingsState.updateSettingLocked(name, value, packageName);
+ if (settingsState != null) {
+ success = settingsState.updateSettingLocked(name, value, tag,
+ makeDefault, packageName);
+ }
if (forceNotify || success) {
notifyForSettingsChange(key, name);
@@ -1868,6 +2000,72 @@
return success;
}
+ public void resetSettingsLocked(int type, int userId, String packageName, int mode,
+ String tag) {
+ final int key = makeKey(type, userId);
+ SettingsState settingsState = peekSettingsStateLocked(key);
+ if (settingsState == null) {
+ return;
+ }
+
+ switch (mode) {
+ case Settings.RESET_MODE_PACKAGE_DEFAULTS: {
+ for (String name : settingsState.getSettingNamesLocked()) {
+ Setting setting = settingsState.getSettingLocked(name);
+ if (packageName.equals(setting.getPackageName())) {
+ if (tag != null && !tag.equals(setting.getTag())) {
+ continue;
+ }
+ if (settingsState.resetSettingLocked(name, packageName)) {
+ notifyForSettingsChange(key, name);
+ }
+ }
+ }
+ } break;
+
+ case Settings.RESET_MODE_UNTRUSTED_DEFAULTS: {
+ for (String name : settingsState.getSettingNamesLocked()) {
+ Setting setting = settingsState.getSettingLocked(name);
+ if (!SettingsState.isSystemPackage(getContext(),
+ setting.getPackageName())) {
+ if (settingsState.resetSettingLocked(name, packageName)) {
+ notifyForSettingsChange(key, name);
+ }
+ }
+ }
+ } break;
+
+ case Settings.RESET_MODE_UNTRUSTED_CHANGES: {
+ for (String name : settingsState.getSettingNamesLocked()) {
+ Setting setting = settingsState.getSettingLocked(name);
+ if (!SettingsState.isSystemPackage(getContext(),
+ setting.getPackageName())) {
+ if (setting.isDefaultSystemSet()) {
+ if (settingsState.resetSettingLocked(name, packageName)) {
+ notifyForSettingsChange(key, name);
+ }
+ } else if (settingsState.deleteSettingLocked(name)) {
+ notifyForSettingsChange(key, name);
+ }
+ }
+ }
+ } break;
+
+ case Settings.RESET_MODE_TRUSTED_DEFAULTS: {
+ for (String name : settingsState.getSettingNamesLocked()) {
+ Setting setting = settingsState.getSettingLocked(name);
+ if (setting.isDefaultSystemSet()) {
+ if (settingsState.resetSettingLocked(name, packageName)) {
+ notifyForSettingsChange(key, name);
+ }
+ } else if (settingsState.deleteSettingLocked(name)) {
+ notifyForSettingsChange(key, name);
+ }
+ }
+ } break;
+ }
+ }
+
public void onPackageRemovedLocked(String packageName, int userId) {
// Global and secure settings are signature protected. Apps signed
// by the platform certificate are generally not uninstalled and
@@ -2005,7 +2203,7 @@
while (!cursor.isAfterLast()) {
String name = cursor.getString(nameColumnIdx);
String value = cursor.getString(valueColumnIdx);
- settingsState.insertSettingLocked(name, value,
+ settingsState.insertSettingLocked(name, value, null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
cursor.moveToNext();
}
@@ -2037,7 +2235,7 @@
String androidId = Long.toHexString(new SecureRandom().nextLong());
secureSettings.insertSettingLocked(Settings.Secure.ANDROID_ID, androidId,
- SettingsState.SYSTEM_PACKAGE_NAME);
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
Slog.d(LOG_TAG, "Generated and saved new ANDROID_ID [" + androidId
+ "] for user " + userId);
@@ -2221,7 +2419,8 @@
String reason = "Settings rebuilt! Current version: "
+ curVersion + " while expected: " + newVersion;
getGlobalSettingsLocked().insertSettingLocked(
- Settings.Global.DATABASE_DOWNGRADE_REASON, reason, "android");
+ Settings.Global.DATABASE_DOWNGRADE_REASON,
+ reason, null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
// Set the global settings version if owner.
@@ -2290,11 +2489,11 @@
if (userId == UserHandle.USER_SYSTEM) {
final SettingsState globalSettings = getGlobalSettingsLocked();
globalSettings.updateSettingLocked(Settings.Global.ZEN_MODE,
- Integer.toString(Settings.Global.ZEN_MODE_OFF),
- SettingsState.SYSTEM_PACKAGE_NAME);
+ Integer.toString(Settings.Global.ZEN_MODE_OFF), null,
+ true, SettingsState.SYSTEM_PACKAGE_NAME);
globalSettings.updateSettingLocked(Settings.Global.MODE_RINGER,
- Integer.toString(AudioManager.RINGER_MODE_NORMAL),
- SettingsState.SYSTEM_PACKAGE_NAME);
+ Integer.toString(AudioManager.RINGER_MODE_NORMAL), null,
+ true, SettingsState.SYSTEM_PACKAGE_NAME);
}
currentVersion = 119;
}
@@ -2304,7 +2503,7 @@
SettingsState secureSettings = getSecureSettingsLocked(userId);
secureSettings.insertSettingLocked(Settings.Secure.DOUBLE_TAP_TO_WAKE,
getContext().getResources().getBoolean(
- R.bool.def_double_tap_to_wake) ? "1" : "0",
+ R.bool.def_double_tap_to_wake) ? "1" : "0", null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
currentVersion = 120;
@@ -2329,8 +2528,7 @@
currentSetting.isNull()) {
secureSettings.insertSettingLocked(
Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
- defaultComponent,
- SettingsState.SYSTEM_PACKAGE_NAME);
+ defaultComponent, null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
currentVersion = 122;
}
@@ -2347,7 +2545,7 @@
Settings.Global.ADD_USERS_WHEN_LOCKED,
getContext().getResources().getBoolean(
R.bool.def_add_users_from_lockscreen) ? "1" : "0",
- SettingsState.SYSTEM_PACKAGE_NAME);
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
}
currentVersion = 123;
@@ -2358,7 +2556,7 @@
String defaultDisabledProfiles = (getContext().getResources().getString(
R.string.def_bluetooth_disabled_profiles));
globalSettings.insertSettingLocked(Settings.Global.BLUETOOTH_DISABLED_PROFILES,
- defaultDisabledProfiles, SettingsState.SYSTEM_PACKAGE_NAME);
+ defaultDisabledProfiles, null, true, SettingsState.SYSTEM_PACKAGE_NAME);
currentVersion = 124;
}
@@ -2373,7 +2571,7 @@
Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
getContext().getResources().getBoolean(
R.bool.def_show_ime_with_hard_keyboard) ? "1" : "0",
- SettingsState.SYSTEM_PACKAGE_NAME);
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
currentVersion = 125;
}
@@ -2400,7 +2598,7 @@
}
secureSettings.insertSettingLocked(
Settings.Secure.ENABLED_VR_LISTENERS, b.toString(),
- SettingsState.SYSTEM_PACKAGE_NAME);
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
}
@@ -2420,7 +2618,7 @@
final SettingsState secureSettings = getSecureSettingsLocked(userId);
secureSettings.insertSettingLocked(
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
- showNotifications.getValue(),
+ showNotifications.getValue(), null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -2430,7 +2628,7 @@
final SettingsState secureSettings = getSecureSettingsLocked(userId);
secureSettings.insertSettingLocked(
Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
- allowPrivate.getValue(),
+ allowPrivate.getValue(), null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
}
}
@@ -2456,7 +2654,7 @@
if (policyAccess.isNull()) {
systemSecureSettings.insertSettingLocked(
Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
- defaultPolicyAccess,
+ defaultPolicyAccess, null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
} else {
StringBuilder currentSetting =
@@ -2465,7 +2663,7 @@
currentSetting.append(defaultPolicyAccess);
systemSecureSettings.updateSettingLocked(
Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
- currentSetting.toString(),
+ currentSetting.toString(), null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
}
}
@@ -2485,7 +2683,7 @@
Settings.Secure.LONG_PRESS_TIMEOUT,
String.valueOf(getContext().getResources().getInteger(
R.integer.def_long_press_timeout_millis)),
- SettingsState.SYSTEM_PACKAGE_NAME);
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
currentVersion = 130;
}
@@ -2498,9 +2696,9 @@
if (dozeExplicitlyDisabled) {
secureSettings.insertSettingLocked(Settings.Secure.DOZE_PULSE_ON_PICK_UP,
- "0", SettingsState.SYSTEM_PACKAGE_NAME);
+ "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME);
secureSettings.insertSettingLocked(Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP,
- "0", SettingsState.SYSTEM_PACKAGE_NAME);
+ "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
currentVersion = 131;
}
@@ -2515,7 +2713,7 @@
Settings.Secure.MULTI_PRESS_TIMEOUT,
String.valueOf(getContext().getResources().getInteger(
R.integer.def_multi_press_timeout_millis)),
- SettingsState.SYSTEM_PACKAGE_NAME);
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
currentVersion = 132;
@@ -2527,9 +2725,8 @@
String defaultSyncParentSounds = (getContext().getResources()
.getBoolean(R.bool.def_sync_parent_sounds) ? "1" : "0");
systemSecureSettings.insertSettingLocked(
- Settings.Secure.SYNC_PARENT_SOUNDS,
- defaultSyncParentSounds,
- SettingsState.SYSTEM_PACKAGE_NAME);
+ Settings.Secure.SYNC_PARENT_SOUNDS, defaultSyncParentSounds,
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
currentVersion = 133;
}
@@ -2541,7 +2738,8 @@
String defaultEndButtonBehavior = Integer.toString(getContext()
.getResources().getInteger(R.integer.def_end_button_behavior));
systemSettings.insertSettingLocked(Settings.System.END_BUTTON_BEHAVIOR,
- defaultEndButtonBehavior, SettingsState.SYSTEM_PACKAGE_NAME);
+ defaultEndButtonBehavior, null, true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
}
currentVersion = 134;
}
@@ -2565,13 +2763,13 @@
// A scorer was set so enable recommendations.
globalSettings.insertSettingLocked(
Global.NETWORK_RECOMMENDATIONS_ENABLED,
- "1",
+ "1", null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
// and clear the scorer setting since it's no longer needed.
globalSettings.insertSettingLocked(
Global.NETWORK_SCORER_APP,
- null,
+ null, null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
index cbeb878..0808051 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
@@ -97,6 +97,7 @@
PUT,
DELETE,
LIST,
+ RESET,
}
int mUser = -1; // unspecified
@@ -104,7 +105,10 @@
String mTable = null;
String mKey = null;
String mValue = null;
-
+ String mPackageName = null;
+ String mToken = null;
+ int mResetMode = -1;
+ boolean mMakeDefault;
MyShellCommand(SettingsProvider provider, boolean dumping) {
mProvider = provider;
@@ -142,6 +146,8 @@
mVerb = CommandVerb.DELETE;
} else if ("list".equalsIgnoreCase(arg)) {
mVerb = CommandVerb.LIST;
+ } else if ("reset".equalsIgnoreCase(arg)) {
+ mVerb = CommandVerb.RESET;
} else {
// invalid
perr.println("Invalid command: " + arg);
@@ -159,6 +165,35 @@
valid = true;
break;
}
+ } else if (mVerb == CommandVerb.RESET) {
+ if ("untrusted_defaults".equalsIgnoreCase(arg)) {
+ mResetMode = Settings.RESET_MODE_UNTRUSTED_DEFAULTS;
+ } else if ("untrusted_clear".equalsIgnoreCase(arg)) {
+ mResetMode = Settings.RESET_MODE_UNTRUSTED_CHANGES;
+ } else if ("trusted_defaults".equalsIgnoreCase(arg)) {
+ mResetMode = Settings.RESET_MODE_TRUSTED_DEFAULTS;
+ } else {
+ mPackageName = arg;
+ mResetMode = Settings.RESET_MODE_PACKAGE_DEFAULTS;
+ if (peekNextArg() == null) {
+ valid = true;
+ } else {
+ mToken = getNextArg();
+ if (peekNextArg() == null) {
+ valid = true;
+ } else {
+ perr.println("Too many arguments");
+ return -1;
+ }
+ }
+ break;
+ }
+ if (peekNextArg() == null) {
+ valid = true;
+ } else {
+ perr.println("Too many arguments");
+ return -1;
+ }
} else if (mVerb == CommandVerb.GET || mVerb == CommandVerb.DELETE) {
mKey = arg;
if (peekNextArg() == null) {
@@ -171,8 +206,32 @@
} else if (mKey == null) {
mKey = arg;
// keep going; there's another PUT arg
- } else { // PUT, final arg
+ } else if (mValue == null) {
mValue = arg;
+ // keep going; there may be another PUT arg
+ } else if (mToken == null) {
+ mToken = arg;
+ if ("default".equalsIgnoreCase(mToken)) {
+ mToken = null;
+ mMakeDefault = true;
+ if (peekNextArg() == null) {
+ valid = true;
+ } else {
+ perr.println("Too many arguments");
+ return -1;
+ }
+ break;
+ }
+ if (peekNextArg() == null) {
+ valid = true;
+ break;
+ }
+ } else { // PUT, final arg
+ if (!"default".equalsIgnoreCase(arg)) {
+ perr.println("Argument expected to be 'default'");
+ return -1;
+ }
+ mMakeDefault = true;
if (peekNextArg() == null) {
valid = true;
} else {
@@ -214,7 +273,7 @@
pout.println(getForUser(iprovider, mUser, mTable, mKey));
break;
case PUT:
- putForUser(iprovider, mUser, mTable, mKey, mValue);
+ putForUser(iprovider, mUser, mTable, mKey, mValue, mToken, mMakeDefault);
break;
case DELETE:
pout.println("Deleted "
@@ -225,6 +284,9 @@
pout.println(line);
}
break;
+ case RESET:
+ resetForUser(iprovider, mUser, mTable, mToken);
+ break;
default:
perr.println("Unspecified command");
return -1;
@@ -286,11 +348,15 @@
return result;
}
- void putForUser(IContentProvider provider, int userHandle,
- final String table, final String key, final String value) {
+ void putForUser(IContentProvider provider, int userHandle, final String table,
+ final String key, final String value, String token, boolean makeDefault) {
final String callPutCommand;
- if ("system".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_SYSTEM;
- else if ("secure".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_SECURE;
+ if ("system".equals(table)) {
+ callPutCommand = Settings.CALL_METHOD_PUT_SYSTEM;
+ makeDefault = false;
+ getOutPrintWriter().println("Ignored makeDefault - "
+ + "doesn't apply to system settings");
+ } else if ("secure".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_SECURE;
else if ("global".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_GLOBAL;
else {
getErrPrintWriter().println("Invalid table; no put performed");
@@ -301,6 +367,10 @@
Bundle arg = new Bundle();
arg.putString(Settings.NameValueTable.VALUE, value);
arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
+ arg.putString(Settings.CALL_METHOD_TAG_KEY, token);
+ if (makeDefault) {
+ arg.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
+ }
provider.call(resolveCallingPackage(), callPutCommand, key, arg);
} catch (RemoteException e) {
throw new RuntimeException("Failed in IPC", e);
@@ -327,6 +397,29 @@
return num;
}
+ void resetForUser(IContentProvider provider, int userHandle,
+ String table, String token) {
+ final String callResetCommand;
+ if ("secure".equals(table)) callResetCommand = Settings.CALL_METHOD_RESET_SECURE;
+ else if ("global".equals(table)) callResetCommand = Settings.CALL_METHOD_RESET_GLOBAL;
+ else {
+ getErrPrintWriter().println("Invalid table; no reset performed");
+ return;
+ }
+
+ try {
+ Bundle arg = new Bundle();
+ arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
+ arg.putInt(Settings.CALL_METHOD_RESET_MODE_KEY, mResetMode);
+ arg.putString(Settings.CALL_METHOD_TAG_KEY, token);
+ String packageName = mPackageName != null ? mPackageName : resolveCallingPackage();
+ arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
+ provider.call(packageName, callResetCommand, null, arg);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed in IPC", e);
+ }
+ }
+
public static String resolveCallingPackage() {
switch (Binder.getCallingUid()) {
case Process.ROOT_UID: {
@@ -360,14 +453,18 @@
pw.println(" Print this help text.");
pw.println(" get [--user <USER_ID> | current] NAMESPACE KEY");
pw.println(" Retrieve the current value of KEY.");
- pw.println(" put [--user <USER_ID> | current] NAMESPACE KEY VALUE");
+ pw.println(" put [--user <USER_ID> | current] NAMESPACE KEY VALUE [TOKEN] [default]");
pw.println(" Change the contents of KEY to VALUE.");
+ pw.println(" TOKEN to associate with the setting.");
+ pw.println(" {default} to set as the default, case-insensitive only for global/secure namespace");
pw.println(" delete NAMESPACE KEY");
pw.println(" Delete the entry for KEY.");
+ pw.println(" reset [--user <USER_ID> | current] NAMESPACE {PACKAGE_NAME | RESET_MODE}");
+ pw.println(" Reset the global/secure table for a package with mode.");
+ pw.println(" RESET_MODE is one of {untrusted_defaults, untrusted_clear, trusted_defaults}, case-insensitive");
pw.println(" list NAMESPACE");
pw.println(" Print all defined keys.");
- pw.println();
- pw.println(" NAMESPACE is one of {system, secure, global}, case-insensitive");
+ pw.println(" NAMESPACE is one of {system, secure, global}, case-insensitive");
}
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index d682fe9..4367df8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -16,20 +16,29 @@
package com.android.providers.settings;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.Signature;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Base64;
import android.util.Slog;
+import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
import libcore.io.IoUtils;
import libcore.util.Objects;
import org.xmlpull.v1.XmlPullParser;
@@ -46,6 +55,8 @@
import java.util.ArrayList;
import java.util.List;
+import static android.os.Process.FIRST_APPLICATION_UID;
+
/**
* This class contains the state for one type of settings. It is responsible
* for saving the state asynchronously to an XML file after a mutation and
@@ -63,6 +74,8 @@
private static final String LOG_TAG = "SettingsState";
+ static final String SYSTEM_PACKAGE_NAME = "android";
+
static final int SETTINGS_VERSION_NEW_ENCODING = 121;
private static final long WRITE_SETTINGS_DELAY_MILLIS = 200;
@@ -71,27 +84,32 @@
public static final int MAX_BYTES_PER_APP_PACKAGE_UNLIMITED = -1;
public static final int MAX_BYTES_PER_APP_PACKAGE_LIMITED = 20000;
- public static final String SYSTEM_PACKAGE_NAME = "android";
-
public static final int VERSION_UNDEFINED = -1;
private static final String TAG_SETTINGS = "settings";
private static final String TAG_SETTING = "setting";
private static final String ATTR_PACKAGE = "package";
+ private static final String ATTR_DEFAULT_SYS_SET = "defaultSysSet";
+ private static final String ATTR_TAG = "tag";
+ private static final String ATTR_TAG_BASE64 = "tagBase64";
private static final String ATTR_VERSION = "version";
private static final String ATTR_ID = "id";
private static final String ATTR_NAME = "name";
- /** Non-binary value will be written in this attribute. */
+ /**
+ * Non-binary value will be written in this attributes.
+ */
private static final String ATTR_VALUE = "value";
+ private static final String ATTR_DEFAULT_VALUE = "defaultValue";
/**
- * KXmlSerializer won't like some characters. We encode such characters in base64 and
- * store in this attribute.
- * NOTE: A null value will have NEITHER ATTR_VALUE nor ATTR_VALUE_BASE64.
+ * KXmlSerializer won't like some characters. We encode such characters
+ * in base64 and store in this attribute.
+ * NOTE: A null value will have *neither* ATTR_VALUE nor ATTR_VALUE_BASE64.
*/
private static final String ATTR_VALUE_BASE64 = "valueBase64";
+ private static final String ATTR_DEFAULT_VALUE_BASE64 = "defaultValueBase64";
// This was used in version 120 and before.
private static final String NULL_VALUE_OLD_STYLE = "null";
@@ -101,12 +119,29 @@
private static final String HISTORICAL_OPERATION_DELETE = "delete";
private static final String HISTORICAL_OPERATION_PERSIST = "persist";
private static final String HISTORICAL_OPERATION_INITIALIZE = "initialize";
+ private static final String HISTORICAL_OPERATION_RESET = "reset";
+
+ private static final String SHELL_PACKAGE_NAME = "shell";
+ private static final String ROOT_PACKAGE_NAME = "root";
+
+ private static final String NULL_VALUE = "null";
+
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static final SparseIntArray sSystemUids = new SparseIntArray();
+
+ @GuardedBy("sLock")
+ private static Signature sSystemSignature;
private final Object mLock;
private final Handler mHandler;
@GuardedBy("mLock")
+ private final Context mContext;
+
+ @GuardedBy("mLock")
private final ArrayMap<String, Setting> mSettings = new ArrayMap<>();
@GuardedBy("mLock")
@@ -118,7 +153,7 @@
@GuardedBy("mLock")
private final File mStatePersistFile;
- private final Setting mNullSetting = new Setting(null, null, null) {
+ private final Setting mNullSetting = new Setting(null, null, false, null, null) {
@Override
public boolean isNull() {
return true;
@@ -149,11 +184,12 @@
@GuardedBy("mLock")
private int mNextHistoricalOpIdx;
- public SettingsState(Object lock, File file, int key, int maxBytesPerAppPackage,
- Looper looper) {
+ public SettingsState(Context context, Object lock, File file, int key,
+ int maxBytesPerAppPackage, Looper looper) {
// It is important that we use the same lock as the settings provider
// to ensure multiple mutations on this state are atomicaly persisted
// as the async persistence should be blocked while we make changes.
+ mContext = context;
mLock = lock;
mStatePersistFile = file;
mKey = key;
@@ -241,37 +277,41 @@
}
// The settings provider must hold its lock when calling here.
- public boolean updateSettingLocked(String name, String value, String packageName) {
+ public boolean updateSettingLocked(String name, String value, String tag,
+ boolean makeValue, String packageName) {
if (!hasSettingLocked(name)) {
return false;
}
- return insertSettingLocked(name, value, packageName);
+ return insertSettingLocked(name, value, tag, makeValue, packageName);
}
// The settings provider must hold its lock when calling here.
- public boolean insertSettingLocked(String name, String value, String packageName) {
+ public boolean insertSettingLocked(String name, String value, String tag,
+ boolean makeDefault, String packageName) {
if (TextUtils.isEmpty(name)) {
return false;
}
Setting oldState = mSettings.get(name);
String oldValue = (oldState != null) ? oldState.value : null;
+ String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null;
Setting newState;
if (oldState != null) {
- if (!oldState.update(value, packageName)) {
+ if (!oldState.update(value, makeDefault, packageName, tag)) {
return false;
}
newState = oldState;
} else {
- newState = new Setting(name, value, packageName);
+ newState = new Setting(name, value, makeDefault, packageName, tag);
mSettings.put(name, newState);
}
addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, newState);
- updateMemoryUsagePerPackageLocked(packageName, oldValue, value);
+ updateMemoryUsagePerPackageLocked(packageName, oldValue, value,
+ oldDefaultValue, newState.getDefaultValue());
scheduleWriteIfNeededLocked();
@@ -292,7 +332,8 @@
Setting oldState = mSettings.remove(name);
- updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value, null);
+ updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value,
+ null, oldState.defaultValue, null);
addHistoricalOperationLocked(HISTORICAL_OPERATION_DELETE, oldState);
@@ -302,6 +343,35 @@
}
// The settings provider must hold its lock when calling here.
+ public boolean resetSettingLocked(String name, String packageName) {
+ if (TextUtils.isEmpty(name) || !hasSettingLocked(name)) {
+ return false;
+ }
+
+ Setting setting = mSettings.get(name);
+
+ Setting oldSetting = new Setting(setting);
+ String oldValue = setting.getValue();
+ String oldDefaultValue = setting.getDefaultValue();
+
+ if (!setting.reset(packageName)) {
+ return false;
+ }
+
+ String newValue = setting.getValue();
+ String newDefaultValue = setting.getDefaultValue();
+
+ updateMemoryUsagePerPackageLocked(setting.packageName, oldValue,
+ newValue, oldDefaultValue, newDefaultValue);
+
+ addHistoricalOperationLocked(HISTORICAL_OPERATION_RESET, oldSetting);
+
+ scheduleWriteIfNeededLocked();
+
+ return true;
+ }
+
+ // The settings provider must hold its lock when calling here.
public void destroyLocked(Runnable callback) {
mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
if (callback != null) {
@@ -364,7 +434,7 @@
}
private void updateMemoryUsagePerPackageLocked(String packageName, String oldValue,
- String newValue) {
+ String newValue, String oldDefaultValue, String newDefaultValue) {
if (mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED) {
return;
}
@@ -375,7 +445,10 @@
final int oldValueSize = (oldValue != null) ? oldValue.length() : 0;
final int newValueSize = (newValue != null) ? newValue.length() : 0;
- final int deltaSize = newValueSize - oldValueSize;
+ final int oldDefaultValueSize = (oldDefaultValue != null) ? oldDefaultValue.length() : 0;
+ final int newDefaultValueSize = (newDefaultValue != null) ? newDefaultValue.length() : 0;
+ final int deltaSize = newValueSize + newDefaultValueSize
+ - oldValueSize - oldDefaultValueSize;
Integer currentSize = mPackageToMemoryUsage.get(packageName);
final int newSize = Math.max((currentSize != null)
@@ -469,7 +542,8 @@
Setting setting = settings.valueAt(i);
writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(),
- setting.getValue(), setting.getPackageName());
+ setting.getValue(), setting.getDefaultValue(), setting.getPackageName(),
+ setting.getTag(), setting.isDefaultSystemSet());
if (DEBUG_PERSISTENCE) {
Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "=" + setting.getValue());
@@ -496,7 +570,8 @@
}
static void writeSingleSetting(int version, XmlSerializer serializer, String id,
- String name, String value, String packageName) throws IOException {
+ String name, String value, String defaultValue, String packageName,
+ String tag, boolean defaultSysSet) throws IOException {
if (id == null || isBinary(id) || name == null || isBinary(name)
|| packageName == null || isBinary(packageName)) {
// This shouldn't happen.
@@ -505,38 +580,46 @@
serializer.startTag(null, TAG_SETTING);
serializer.attribute(null, ATTR_ID, id);
serializer.attribute(null, ATTR_NAME, name);
- setValueAttribute(version, serializer, value);
+ setValueAttribute(ATTR_VALUE, ATTR_VALUE_BASE64,
+ version, serializer, value);
serializer.attribute(null, ATTR_PACKAGE, packageName);
+ if (defaultValue != null) {
+ setValueAttribute(ATTR_DEFAULT_VALUE, ATTR_DEFAULT_VALUE_BASE64,
+ version, serializer, defaultValue);
+ serializer.attribute(null, ATTR_DEFAULT_SYS_SET, Boolean.toString(defaultSysSet));
+ setValueAttribute(ATTR_TAG, ATTR_TAG_BASE64,
+ version, serializer, tag);
+ }
serializer.endTag(null, TAG_SETTING);
}
- static void setValueAttribute(int version, XmlSerializer serializer, String value)
- throws IOException {
+ static void setValueAttribute(String attr, String attrBase64, int version,
+ XmlSerializer serializer, String value) throws IOException {
if (version >= SETTINGS_VERSION_NEW_ENCODING) {
if (value == null) {
// Null value -> No ATTR_VALUE nor ATTR_VALUE_BASE64.
} else if (isBinary(value)) {
- serializer.attribute(null, ATTR_VALUE_BASE64, base64Encode(value));
+ serializer.attribute(null, attrBase64, base64Encode(value));
} else {
- serializer.attribute(null, ATTR_VALUE, value);
+ serializer.attribute(null, attr, value);
}
} else {
// Old encoding.
if (value == null) {
- serializer.attribute(null, ATTR_VALUE, NULL_VALUE_OLD_STYLE);
+ serializer.attribute(null, attr, NULL_VALUE_OLD_STYLE);
} else {
- serializer.attribute(null, ATTR_VALUE, value);
+ serializer.attribute(null, attr, value);
}
}
}
- private String getValueAttribute(XmlPullParser parser) {
+ private String getValueAttribute(XmlPullParser parser, String attr, String base64Attr) {
if (mVersion >= SETTINGS_VERSION_NEW_ENCODING) {
- final String value = parser.getAttributeValue(null, ATTR_VALUE);
+ final String value = parser.getAttributeValue(null, attr);
if (value != null) {
return value;
}
- final String base64 = parser.getAttributeValue(null, ATTR_VALUE_BASE64);
+ final String base64 = parser.getAttributeValue(null, base64Attr);
if (base64 != null) {
return base64Decode(base64);
}
@@ -544,7 +627,7 @@
return null;
} else {
// Old encoding.
- final String stored = parser.getAttributeValue(null, ATTR_VALUE);
+ final String stored = parser.getAttributeValue(null, attr);
if (NULL_VALUE_OLD_STYLE.equals(stored)) {
return null;
} else {
@@ -575,7 +658,7 @@
} catch (XmlPullParserException | IOException e) {
String message = "Failed parsing settings file: " + mStatePersistFile;
Slog.wtf(LOG_TAG, message);
- throw new IllegalStateException(message , e);
+ throw new IllegalStateException(message, e);
} finally {
IoUtils.closeQuietly(in);
}
@@ -615,9 +698,19 @@
if (tagName.equals(TAG_SETTING)) {
String id = parser.getAttributeValue(null, ATTR_ID);
String name = parser.getAttributeValue(null, ATTR_NAME);
- String value = getValueAttribute(parser);
+ String value = getValueAttribute(parser, ATTR_VALUE, ATTR_VALUE_BASE64);
String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
- mSettings.put(name, new Setting(name, value, packageName, id));
+ String defaultValue = getValueAttribute(parser, ATTR_DEFAULT_VALUE,
+ ATTR_DEFAULT_VALUE_BASE64);
+ String tag = null;
+ boolean fromSystem = false;
+ if (defaultValue != null) {
+ fromSystem = Boolean.parseBoolean(parser.getAttributeValue(
+ null, ATTR_DEFAULT_SYS_SET));
+ tag = getValueAttribute(parser, ATTR_TAG, ATTR_TAG_BASE64);
+ }
+ mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag,
+ fromSystem, id));
if (DEBUG_PERSISTENCE) {
Slog.i(LOG_TAG, "[RESTORED] " + name + "=" + value);
@@ -664,37 +757,54 @@
class Setting {
private String name;
private String value;
+ private String defaultValue;
private String packageName;
private String id;
+ private String tag;
+ // Whether the default is set by the system
+ private boolean defaultSystemSet;
public Setting(Setting other) {
name = other.name;
value = other.value;
+ defaultValue = other.defaultValue;
packageName = other.packageName;
id = other.id;
+ defaultSystemSet = other.defaultSystemSet;
+ tag = other.tag;
}
- public Setting(String name, String value, String packageName) {
- init(name, value, packageName, String.valueOf(mNextId++));
+ public Setting(String name, String value, boolean makeDefault, String packageName,
+ String tag) {
+ this.name = name;
+ update(value, makeDefault, packageName, tag);
}
- public Setting(String name, String value, String packageName, String id) {
+ public Setting(String name, String value, String defaultValue,
+ String packageName, String tag, boolean fromSystem, String id) {
mNextId = Math.max(mNextId, Long.parseLong(id) + 1);
- init(name, value, packageName, id);
+ if (NULL_VALUE.equals(value)) {
+ value = null;
+ }
+ init(name, value, tag, defaultValue, packageName, fromSystem, id);
}
- private void init(String name, String value, String packageName, String id) {
+ private void init(String name, String value, String tag, String defaultValue,
+ String packageName, boolean fromSystem, String id) {
this.name = name;
this.value = value;
+ this.tag = tag;
+ this.defaultValue = defaultValue;
this.packageName = packageName;
this.id = id;
+ this.defaultSystemSet = fromSystem;
}
public String getName() {
return name;
}
- public int getkey() {
+ public int getKey() {
return mKey;
}
@@ -702,10 +812,22 @@
return value;
}
+ public String getTag() {
+ return tag;
+ }
+
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
public String getPackageName() {
return packageName;
}
+ public boolean isDefaultSystemSet() {
+ return defaultSystemSet;
+ }
+
public String getId() {
return id;
}
@@ -714,18 +836,62 @@
return false;
}
- public boolean update(String value, String packageName) {
- if (Objects.equal(value, this.value)) {
+ /** @return whether the value changed */
+ public boolean reset(String packageName) {
+ return update(this.defaultValue, false, packageName, null);
+ }
+
+ public boolean update(String value, boolean setDefault, String packageName, String tag) {
+ if (NULL_VALUE.equals(value)) {
+ value = null;
+ }
+
+ final boolean callerSystem = !isNull() && isSystemPackage(mContext, packageName);
+ // Settings set by the system are always defaults.
+ if (callerSystem) {
+ setDefault = true;
+ }
+
+ String defaultValue = this.defaultValue;
+ boolean defaultSystemSet = this.defaultSystemSet;
+ if (setDefault) {
+ if (!Objects.equal(value, this.defaultValue)
+ && (!defaultSystemSet || callerSystem)) {
+ defaultValue = value;
+ // Default null means no default, so the tag is irrelevant
+ // since it is used to reset a settings subset their defaults.
+ // Also it is irrelevant if the system set the canonical default.
+ if (defaultValue == null) {
+ tag = null;
+ defaultSystemSet = false;
+ }
+ }
+ if (!defaultSystemSet && value != null) {
+ if (callerSystem) {
+ defaultSystemSet = true;
+ }
+ }
+ }
+
+ // Is something gonna change?
+ if (Objects.equal(value, this.value)
+ && Objects.equal(defaultValue, this.defaultValue)
+ && Objects.equal(packageName, this.packageName)
+ && Objects.equal(tag, this.tag)
+ && defaultSystemSet == this.defaultSystemSet) {
return false;
}
- this.value = value;
- this.packageName = packageName;
- this.id = String.valueOf(mNextId++);
+
+ init(name, value, tag, defaultValue, packageName, defaultSystemSet,
+ String.valueOf(mNextId++));
return true;
}
public String toString() {
- return "Setting{name=" + value + " from " + packageName + "}";
+ return "Setting{name=" + name + " value=" + value
+ + (defaultValue != null ? " default=" + defaultValue : "")
+ + " packageName=" + packageName + " tag=" + tag
+ + " defaultSystemSet=" + defaultSystemSet + "}";
}
}
@@ -782,4 +948,74 @@
}
return sb.toString();
}
+
+ public static boolean isSystemPackage(Context context, String packageName) {
+ synchronized (sLock) {
+ if (SYSTEM_PACKAGE_NAME.equals(packageName)) {
+ return true;
+ }
+
+ // Shell and Root are not considered a part of the system
+ if (SHELL_PACKAGE_NAME.equals(packageName)
+ || ROOT_PACKAGE_NAME.equals(packageName)) {
+ return false;
+ }
+
+ final int uid;
+ try {
+ uid = context.getPackageManager().getPackageUid(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+
+ // If the system or a special system UID (like telephony), done.
+ if (UserHandle.getAppId(uid) < FIRST_APPLICATION_UID) {
+ sSystemUids.put(uid, uid);
+ return true;
+ }
+
+ // If already known system component, done.
+ if (sSystemUids.indexOfKey(uid) >= 0) {
+ return true;
+ }
+
+ // If SetupWizard, done.
+ PackageManagerInternal packageManagerInternal = LocalServices.getService(
+ PackageManagerInternal.class);
+ if (packageName.equals(packageManagerInternal.getSetupWizardPackageName())) {
+ sSystemUids.put(uid, uid);
+ return true;
+ }
+
+ // If a persistent system app, done.
+ PackageInfo packageInfo;
+ try {
+ packageInfo = context.getPackageManager().getPackageInfo(
+ packageName, PackageManager.GET_SIGNATURES);
+ if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0
+ && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ sSystemUids.put(uid, uid);
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+
+ // Last check if system signed.
+ if (sSystemSignature == null) {
+ try {
+ sSystemSignature = context.getPackageManager().getPackageInfo(
+ SYSTEM_PACKAGE_NAME, PackageManager.GET_SIGNATURES).signatures[0];
+ } catch (PackageManager.NameNotFoundException e) {
+ /* impossible */
+ return false;
+ }
+ }
+ if (sSystemSignature.equals(packageInfo.signatures[0])) {
+ sSystemUids.put(uid, uid);
+ return true;
+ }
+ return false;
+ }
+ }
}
diff --git a/packages/SettingsProvider/test/Android.mk b/packages/SettingsProvider/test/Android.mk
index ef863e7..918410e 100644
--- a/packages/SettingsProvider/test/Android.mk
+++ b/packages/SettingsProvider/test/Android.mk
@@ -2,11 +2,15 @@
include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+
# Note we statically link SettingsState to do some unit tests. It's not accessible otherwise
# because this test is not an instrumentation test. (because the target runs in the system process.)
LOCAL_SRC_FILES := $(call all-subdir-java-files) \
../src/com/android/providers/settings/SettingsState.java
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
LOCAL_PACKAGE_NAME := SettingsProviderTest
LOCAL_MODULE_TAGS := tests
diff --git a/packages/SettingsProvider/test/AndroidManifest.xml b/packages/SettingsProvider/test/AndroidManifest.xml
index 7a86b5f..71e0b15 100644
--- a/packages/SettingsProvider/test/AndroidManifest.xml
+++ b/packages/SettingsProvider/test/AndroidManifest.xml
@@ -29,7 +29,8 @@
</application>
<instrumentation
- android:name="android.test.InstrumentationTestRunner"
+ android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.providers.setting.test"
android:label="Settings Provider Tests" />
+
</manifest>
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java
index 8e56f47..0454b51 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java
@@ -25,14 +25,21 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import libcore.io.Streams;
+import org.junit.runner.RunWith;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.List;
/**
* Base class for the SettingContentProvider tests.
*/
-abstract class BaseSettingsProviderTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+abstract class BaseSettingsProviderTest {
protected static final int SETTING_TYPE_GLOBAL = 1;
protected static final int SETTING_TYPE_SECURE = 2;
protected static final int SETTING_TYPE_SYSTEM = 3;
@@ -48,23 +55,7 @@
Settings.NameValueTable.NAME, Settings.NameValueTable.VALUE
};
- protected int mSecondaryUserId = UserHandle.USER_SYSTEM;
-
- @Override
- public void setContext(Context context) {
- super.setContext(context);
-
- UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- List<UserInfo> users = userManager.getUsers();
- final int userCount = users.size();
- for (int i = 0; i < userCount; i++) {
- UserInfo user = users.get(i);
- if (!user.isPrimary() && !user.isManagedProfile()) {
- mSecondaryUserId = user.id;
- break;
- }
- }
- }
+ private int mSecondaryUserId = Integer.MIN_VALUE;
protected void setStringViaFrontEndApiSetting(int type, String name, String value, int userId) {
ContentResolver contentResolver = getContext().getContentResolver();
@@ -176,6 +167,163 @@
return null;
}
+ protected static void resetSettingsViaShell(int type, int resetMode) throws IOException {
+ final String modeString;
+ switch (resetMode) {
+ case Settings.RESET_MODE_UNTRUSTED_DEFAULTS: {
+ modeString = "untrusted_defaults";
+ } break;
+
+ case Settings.RESET_MODE_UNTRUSTED_CHANGES: {
+ modeString = "untrusted_clear";
+ } break;
+
+ case Settings.RESET_MODE_TRUSTED_DEFAULTS: {
+ modeString = "trusted_defaults";
+ } break;
+
+ default: {
+ throw new IllegalArgumentException("Invalid reset mode: " + resetMode);
+ }
+ }
+
+ switch (type) {
+ case SETTING_TYPE_GLOBAL: {
+ executeShellCommand("settings reset global " + modeString);
+ } break;
+
+ case SETTING_TYPE_SECURE: {
+ executeShellCommand("settings reset secure " + modeString);
+ } break;
+
+ default: {
+ throw new IllegalArgumentException("Invalid type: " + type);
+ }
+ }
+ }
+
+ protected static void resetToDefaultsViaShell(int type, String packageName) throws IOException {
+ resetToDefaultsViaShell(type, packageName, null);
+ }
+
+ protected static void resetToDefaultsViaShell(int type, String packageName, String token)
+ throws IOException {
+ switch (type) {
+ case SETTING_TYPE_GLOBAL: {
+ executeShellCommand("settings reset global " + packageName + " " + token);
+ } break;
+
+ case SETTING_TYPE_SECURE: {
+ executeShellCommand("settings reset secure " + packageName + " " + token);
+ } break;
+
+ case SETTING_TYPE_SYSTEM: {
+ executeShellCommand("settings reset system " + packageName + " " + token);
+ } break;
+
+ default: {
+ throw new IllegalArgumentException("Invalid type: " + type);
+ }
+ }
+ }
+
+ protected String getSetting(int type, String name) {
+ switch (type) {
+ case SETTING_TYPE_GLOBAL: {
+ return Settings.Global.getString(getContext().getContentResolver(), name);
+ }
+
+ case SETTING_TYPE_SECURE: {
+ return Settings.Secure.getString(getContext().getContentResolver(), name);
+ }
+
+ case SETTING_TYPE_SYSTEM: {
+ return Settings.System.getString(getContext().getContentResolver(), name);
+ }
+
+ default: {
+ throw new IllegalArgumentException("Invalid type: " + type);
+ }
+ }
+ }
+
+ protected void putSetting(int type, String name, String value) {
+ switch (type) {
+ case SETTING_TYPE_GLOBAL: {
+ Settings.Global.putString(getContext().getContentResolver(), name, value);
+ } break;
+
+ case SETTING_TYPE_SECURE: {
+ Settings.Secure.putString(getContext().getContentResolver(), name, value);
+ } break;
+
+ case SETTING_TYPE_SYSTEM: {
+ Settings.System.putString(getContext().getContentResolver(), name, value);
+ } break;
+
+ default: {
+ throw new IllegalArgumentException("Invalid type: " + type);
+ }
+ }
+ }
+
+ protected static void setSettingViaShell(int type, String name, String value,
+ boolean makeDefault) throws IOException {
+ setSettingViaShell(type, name, value, null, makeDefault);
+ }
+
+ protected static void setSettingViaShell(int type, String name, String value,
+ String token, boolean makeDefault) throws IOException {
+ switch (type) {
+ case SETTING_TYPE_GLOBAL: {
+ executeShellCommand("settings put global " + name + " "
+ + value + (token != null ? " " + token : "")
+ + (makeDefault ? " default" : ""));
+
+ } break;
+
+ case SETTING_TYPE_SECURE: {
+ executeShellCommand("settings put secure " + name + " "
+ + value + (token != null ? " " + token : "")
+ + (makeDefault ? " default" : ""));
+ } break;
+
+ case SETTING_TYPE_SYSTEM: {
+ executeShellCommand("settings put system " + name + " "
+ + value + (token != null ? " " + token : "")
+ + (makeDefault ? " default" : ""));
+ } break;
+
+ default: {
+ throw new IllegalArgumentException("Invalid type: " + type);
+ }
+ }
+ }
+
+ protected Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ protected int getSecondaryUserId() {
+ if (mSecondaryUserId == Integer.MIN_VALUE) {
+ UserManager userManager = (UserManager) getContext()
+ .getSystemService(Context.USER_SERVICE);
+ List<UserInfo> users = userManager.getUsers();
+ final int userCount = users.size();
+ for (int i = 0; i < userCount; i++) {
+ UserInfo user = users.get(i);
+ if (!user.isPrimary() && !user.isManagedProfile()) {
+ mSecondaryUserId = user.id;
+ return mSecondaryUserId;
+ }
+ }
+ }
+ if (mSecondaryUserId == Integer.MIN_VALUE) {
+ mSecondaryUserId = UserHandle.USER_SYSTEM;
+ }
+ return mSecondaryUserId;
+ }
+
protected static Uri getBaseUriForType(int type) {
switch (type) {
case SETTING_TYPE_GLOBAL: {
@@ -195,4 +343,10 @@
}
}
}
+
+ protected static void executeShellCommand(String command) throws IOException {
+ InputStream is = new FileInputStream(InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation().executeShellCommand(command).getFileDescriptor());
+ Streams.readFully(is);
+ }
}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderPerformanceTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderPerformanceTest.java
index a09d5fe..d34b9ed 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderPerformanceTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderPerformanceTest.java
@@ -16,9 +16,13 @@
package com.android.providers.settings;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
+import org.junit.Test;
/**
* Performance tests for the SettingContentProvider.
@@ -32,6 +36,7 @@
private static final long MAX_AVERAGE_SET_AND_GET_SETTING_DURATION_MILLIS = 20;
+ @Test
public void testSetAndGetPerformanceForGlobalViaFrontEndApi() throws Exception {
// Start with a clean slate.
insertStringViaProviderApi(SETTING_TYPE_GLOBAL,
@@ -76,6 +81,7 @@
< MAX_AVERAGE_SET_AND_GET_SETTING_DURATION_MILLIS);
}
+ @Test
public void testSetAndGetPerformanceForGlobalViaProviderApi() throws Exception {
// Start with a clean slate.
deleteStringViaProviderApi(SETTING_TYPE_GLOBAL, FAKE_SETTING_NAME);
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
index 8ca1b46..e2a8fba 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
@@ -16,6 +16,11 @@
package com.android.providers.settings;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.fail;
+
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.ContentObserver;
@@ -27,6 +32,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
+import org.junit.Test;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -46,57 +52,70 @@
private final Object mLock = new Object();
+ @Test
public void testSetAndGetGlobalViaFrontEndApiForSystemUser() throws Exception {
performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
}
+ @Test
public void testSetAndGetGlobalViaFrontEndApiForNonSystemUser() throws Exception {
- if (mSecondaryUserId == UserHandle.USER_SYSTEM) {
+ final int secondaryUserId = getSecondaryUserId();
+ if (secondaryUserId == UserHandle.USER_SYSTEM) {
Log.w(LOG_TAG, "No secondary user. Skipping "
+ "testSetAndGetGlobalViaFrontEndApiForNonSystemUser");
return;
}
- performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_GLOBAL, mSecondaryUserId);
+ performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_GLOBAL, secondaryUserId);
}
+ @Test
public void testSetAndGetSecureViaFrontEndApiForSystemUser() throws Exception {
performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SECURE, UserHandle.USER_SYSTEM);
}
+ @Test
public void testSetAndGetSecureViaFrontEndApiForNonSystemUser() throws Exception {
- if (mSecondaryUserId == UserHandle.USER_SYSTEM) {
+ final int secondaryUserId = getSecondaryUserId();
+ if (secondaryUserId == UserHandle.USER_SYSTEM) {
Log.w(LOG_TAG, "No secondary user. Skipping "
+ "testSetAndGetSecureViaFrontEndApiForNonSystemUser");
return;
}
- performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SECURE, mSecondaryUserId);
+ performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SECURE, secondaryUserId);
}
+ @Test
public void testSetAndGetSystemViaFrontEndApiForSystemUser() throws Exception {
performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SYSTEM, UserHandle.USER_SYSTEM);
}
+ @Test
public void testSetAndGetSystemViaFrontEndApiForNonSystemUser() throws Exception {
- if (mSecondaryUserId == UserHandle.USER_SYSTEM) {
+ final int secondaryUserId = getSecondaryUserId();
+ if (secondaryUserId == UserHandle.USER_SYSTEM) {
Log.w(LOG_TAG, "No secondary user. Skipping "
+ "testSetAndGetSystemViaFrontEndApiForNonSystemUser");
return;
}
- performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SYSTEM, mSecondaryUserId);
+ performSetAndGetSettingTestViaFrontEndApi(SETTING_TYPE_SYSTEM, secondaryUserId);
}
+ @Test
public void testSetAndGetGlobalViaProviderApi() throws Exception {
performSetAndGetSettingTestViaProviderApi(SETTING_TYPE_GLOBAL);
}
+ @Test
public void testSetAndGetSecureViaProviderApi() throws Exception {
performSetAndGetSettingTestViaProviderApi(SETTING_TYPE_SECURE);
}
+ @Test
public void testSetAndGetSystemViaProviderApi() throws Exception {
performSetAndGetSettingTestViaProviderApi(SETTING_TYPE_SYSTEM);
}
+ @Test
public void testSelectAllGlobalViaProviderApi() throws Exception {
setSettingViaProviderApiAndAssertSuccessfulChange(SETTING_TYPE_GLOBAL,
FAKE_SETTING_NAME, FAKE_SETTING_VALUE, false);
@@ -108,6 +127,7 @@
}
}
+ @Test
public void testSelectAllSecureViaProviderApi() throws Exception {
setSettingViaProviderApiAndAssertSuccessfulChange(SETTING_TYPE_SECURE,
FAKE_SETTING_NAME, FAKE_SETTING_VALUE, false);
@@ -119,6 +139,7 @@
}
}
+ @Test
public void testSelectAllSystemViaProviderApi() throws Exception {
setSettingViaProviderApiAndAssertSuccessfulChange(SETTING_TYPE_SYSTEM,
FAKE_SETTING_NAME, FAKE_SETTING_VALUE, true);
@@ -130,30 +151,37 @@
}
}
+ @Test
public void testQueryUpdateDeleteGlobalViaProviderApi() throws Exception {
doTestQueryUpdateDeleteGlobalViaProviderApiForType(SETTING_TYPE_GLOBAL);
}
+ @Test
public void testQueryUpdateDeleteSecureViaProviderApi() throws Exception {
doTestQueryUpdateDeleteGlobalViaProviderApiForType(SETTING_TYPE_SECURE);
}
+ @Test
public void testQueryUpdateDeleteSystemViaProviderApi() throws Exception {
doTestQueryUpdateDeleteGlobalViaProviderApiForType(SETTING_TYPE_SYSTEM);
}
+ @Test
public void testBulkInsertGlobalViaProviderApi() throws Exception {
toTestBulkInsertViaProviderApiForType(SETTING_TYPE_GLOBAL);
}
+ @Test
public void testBulkInsertSystemViaProviderApi() throws Exception {
toTestBulkInsertViaProviderApiForType(SETTING_TYPE_SYSTEM);
}
+ @Test
public void testBulkInsertSecureViaProviderApi() throws Exception {
toTestBulkInsertViaProviderApiForType(SETTING_TYPE_SECURE);
}
+ @Test
public void testAppCannotRunsSystemOutOfMemoryWritingSystemSettings() throws Exception {
int insertedCount = 0;
try {
@@ -164,6 +192,8 @@
}
fail("Adding app specific settings must be bound.");
} catch (Exception e) {
+ // expected
+ } finally {
for (; insertedCount >= 0; insertedCount--) {
Log.w(LOG_TAG, "Removing app specific setting: " + insertedCount);
deleteStringViaProviderApi(SETTING_TYPE_SYSTEM,
@@ -172,18 +202,22 @@
}
}
+ @Test
public void testQueryStringInBracketsGlobalViaProviderApiForType() throws Exception {
doTestQueryStringInBracketsViaProviderApiForType(SETTING_TYPE_GLOBAL);
}
+ @Test
public void testQueryStringInBracketsSecureViaProviderApiForType() throws Exception {
doTestQueryStringInBracketsViaProviderApiForType(SETTING_TYPE_SECURE);
}
+ @Test
public void testQueryStringInBracketsSystemViaProviderApiForType() throws Exception {
doTestQueryStringInBracketsViaProviderApiForType(SETTING_TYPE_SYSTEM);
}
+ @Test
public void testQueryStringWithAppendedNameToUriViaProviderApi() throws Exception {
// Make sure we have a clean slate.
deleteStringViaProviderApi(SETTING_TYPE_SYSTEM, FAKE_SETTING_NAME);
@@ -206,6 +240,228 @@
}
}
+ @Test
+ public void testResetModePackageDefaultsGlobal() throws Exception {
+ testResetModePackageDefaultsCommon(SETTING_TYPE_GLOBAL);
+ }
+
+ @Test
+ public void testResetModePackageDefaultsSecure() throws Exception {
+ testResetModePackageDefaultsCommon(SETTING_TYPE_SECURE);
+ }
+
+ private void testResetModePackageDefaultsCommon(int type) throws Exception {
+ // Make sure we have a clean slate.
+ setSettingViaShell(type, FAKE_SETTING_NAME, null, true);
+ try {
+ // Set a value but don't make it the default
+ setSettingViaShell(type, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE, false);
+
+ // Reset the changes made by the "shell/root" package
+ resetToDefaultsViaShell(type, "shell");
+ resetToDefaultsViaShell(type, "root");
+
+ // Make sure the old APIs don't set defaults
+ assertNull(getSetting(type, FAKE_SETTING_NAME));
+
+ // Set a value and make it the default
+ setSettingViaShell(type, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE, true);
+ // Change the setting from the default
+ setSettingViaShell(type, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE_2, false);
+
+ // Reset the changes made by this package
+ resetToDefaultsViaShell(type, "shell");
+ resetToDefaultsViaShell(type, "root");
+
+ // Make sure the old APIs don't set defaults
+ assertEquals(FAKE_SETTING_VALUE, getSetting(type, FAKE_SETTING_NAME));
+ } finally {
+ // Make sure we have a clean slate.
+ setSettingViaShell(type, FAKE_SETTING_NAME, null, true);
+ }
+ }
+
+ @Test
+ public void testResetModePackageDefaultsWithTokensGlobal() throws Exception {
+ testResetModePackageDefaultsWithTokensCommon(SETTING_TYPE_GLOBAL);
+ }
+
+ @Test
+ public void testResetModePackageDefaultsWithTokensSecure() throws Exception {
+ testResetModePackageDefaultsWithTokensCommon(SETTING_TYPE_SECURE);
+ }
+
+ private void testResetModePackageDefaultsWithTokensCommon(int type) throws Exception {
+ // Make sure we have a clean slate.
+ setSettingViaShell(type, FAKE_SETTING_NAME, null, true);
+ setSettingViaShell(type, FAKE_SETTING_NAME_1, null, true);
+ try {
+ // Set a default value
+ setSettingViaShell(type, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE, true);
+ // Change the default and associate a token
+ setSettingViaShell(type, FAKE_SETTING_NAME,
+ FAKE_SETTING_VALUE_2, "TOKEN1", false);
+
+ // Set a default value
+ setSettingViaShell(type, FAKE_SETTING_NAME_1,
+ FAKE_SETTING_VALUE, "TOKEN2", true);
+ // Change the default and associate a token
+ setSettingViaShell(type, FAKE_SETTING_NAME_1,
+ FAKE_SETTING_VALUE_2, "TOKEN2", false);
+
+ // Reset settings associated with TOKEN1
+ resetToDefaultsViaShell(type, "shell", "TOKEN1");
+ resetToDefaultsViaShell(type, "root", "TOKEN1");
+
+ // Make sure TOKEN1 settings are reset
+ assertEquals(FAKE_SETTING_VALUE, getSetting(type,
+ FAKE_SETTING_NAME));
+
+ // Make sure TOKEN2 settings are not reset
+ assertEquals(FAKE_SETTING_VALUE_2, getSetting(type,
+ FAKE_SETTING_NAME_1));
+
+ // Reset settings associated with TOKEN2
+ resetToDefaultsViaShell(type, "shell", "TOKEN2");
+ resetToDefaultsViaShell(type, "root", "TOKEN2");
+
+ // Make sure TOKEN2 settings are reset
+ assertEquals(FAKE_SETTING_VALUE, getSetting(type,
+ FAKE_SETTING_NAME_1));
+ } finally {
+ // Make sure we have a clean slate.
+ setSettingViaShell(type, FAKE_SETTING_NAME, null, true);
+ setSettingViaShell(type, FAKE_SETTING_NAME_1, null, true);
+ }
+ }
+
+ @Test
+ public void testResetModeUntrustedDefaultsGlobal() throws Exception {
+ testResetModeUntrustedDefaultsCommon(SETTING_TYPE_GLOBAL);
+ }
+
+ @Test
+ public void testResetModeUntrustedDefaultsSecure() throws Exception {
+ testResetModeUntrustedDefaultsCommon(SETTING_TYPE_SECURE);
+ }
+
+ private void testResetModeUntrustedDefaultsCommon(int type) throws Exception {
+ // Make sure we have a clean slate.
+ putSetting(type, FAKE_SETTING_NAME, null);
+ setSettingViaShell(type, FAKE_SETTING_NAME_1, null, true);
+ try {
+ // Set a default setting as a trusted component
+ putSetting(type, FAKE_SETTING_NAME, FAKE_SETTING_VALUE);
+ // Change the setting as a trusted component
+ putSetting(type, FAKE_SETTING_NAME, FAKE_SETTING_VALUE_2);
+
+ // Set a default setting as an untrusted component
+ setSettingViaShell(type, FAKE_SETTING_NAME_1,
+ FAKE_SETTING_VALUE, true);
+ // Change the setting as an untrusted component
+ setSettingViaShell(type, FAKE_SETTING_NAME_1,
+ FAKE_SETTING_VALUE_2, false);
+
+ // Reset the untrusted changes to defaults
+ resetSettingsViaShell(type,
+ Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
+
+ // Check whether only the untrusted changes set to defaults
+ assertEquals(FAKE_SETTING_VALUE_2, getSetting(type, FAKE_SETTING_NAME));
+ assertEquals(FAKE_SETTING_VALUE, getSetting(type, FAKE_SETTING_NAME_1));
+ } finally {
+ // Make sure we have a clean slate.
+ putSetting(type, FAKE_SETTING_NAME, null);
+ setSettingViaShell(type, FAKE_SETTING_NAME_1, null, true);
+ }
+ }
+
+ @Test
+ public void testResetModeUntrustedClearGlobal() throws Exception {
+ testResetModeUntrustedClearCommon(SETTING_TYPE_GLOBAL);
+ }
+
+ @Test
+ public void testResetModeUntrustedClearSecure() throws Exception {
+ testResetModeUntrustedClearCommon(SETTING_TYPE_SECURE);
+ }
+
+ private void testResetModeUntrustedClearCommon(int type) throws Exception {
+ // Make sure we have a clean slate.
+ putSetting(type, FAKE_SETTING_NAME, null);
+ setSettingViaShell(type, FAKE_SETTING_NAME_1, null, true);
+ try {
+ // Set a default setting as a trusted component
+ putSetting(type, FAKE_SETTING_NAME, FAKE_SETTING_VALUE);
+ // Change the setting as a trusted component
+ putSetting(type, FAKE_SETTING_NAME, FAKE_SETTING_VALUE_2);
+
+ // Set a default setting as an untrusted component
+ setSettingViaShell(type, FAKE_SETTING_NAME_1,
+ FAKE_SETTING_VALUE, true);
+ // Change the setting as an untrusted component
+ setSettingViaShell(type, FAKE_SETTING_NAME_1,
+ FAKE_SETTING_VALUE_2, false);
+
+ // Clear the untrusted changes
+ resetSettingsViaShell(type,
+ Settings.RESET_MODE_UNTRUSTED_CHANGES);
+
+ // Check whether only the untrusted changes set to defaults
+ assertEquals(FAKE_SETTING_VALUE_2, getSetting(type, FAKE_SETTING_NAME));
+ assertNull(getSetting(type, FAKE_SETTING_NAME_1));
+ } finally {
+ // Make sure we have a clean slate.
+ putSetting(type, FAKE_SETTING_NAME, null);
+ setSettingViaShell(type, FAKE_SETTING_NAME_1, null, true);
+ }
+ }
+
+ @Test
+ public void testResetModeTrustedDefaultsGlobal() throws Exception {
+ testResetModeTrustedDefaultsCommon(SETTING_TYPE_GLOBAL);
+ }
+
+ @Test
+ public void testResetModeTrustedDefaultsSecure() throws Exception {
+ testResetModeTrustedDefaultsCommon(SETTING_TYPE_SECURE);
+ }
+
+ private void testResetModeTrustedDefaultsCommon(int type) throws Exception {
+ // Make sure we have a clean slate.
+ putSetting(type, FAKE_SETTING_NAME, null);
+ setSettingViaShell(type, FAKE_SETTING_NAME_1, null, true);
+ try {
+ // Set a default setting as a trusted component
+ putSetting(type, FAKE_SETTING_NAME, FAKE_SETTING_VALUE);
+ // Change the setting as a trusted component
+ setSettingViaShell(type, FAKE_SETTING_NAME, FAKE_SETTING_VALUE_2, false);
+
+ // Set a default setting as an untrusted component
+ setSettingViaShell(type, FAKE_SETTING_NAME_1,
+ FAKE_SETTING_VALUE, true);
+ // Change the setting as an untrusted component
+ setSettingViaShell(type, FAKE_SETTING_NAME_1,
+ FAKE_SETTING_VALUE_2, false);
+
+ // Reset to trusted defaults
+ resetSettingsViaShell(type,
+ Settings.RESET_MODE_TRUSTED_DEFAULTS);
+
+ // Check whether snapped to trusted defaults
+ assertEquals(FAKE_SETTING_VALUE, getSetting(type, FAKE_SETTING_NAME));
+ assertNull(getSetting(type, FAKE_SETTING_NAME_1));
+ } finally {
+ // Make sure we have a clean slate.
+ putSetting(type, FAKE_SETTING_NAME, null);
+ setSettingViaShell(type, FAKE_SETTING_NAME_1, null, true);
+ }
+ }
+
private void doTestQueryStringInBracketsViaProviderApiForType(int type) {
// Make sure we have a clean slate.
deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
@@ -341,22 +597,16 @@
private void setSettingViaFrontEndApiAndAssertSuccessfulChange(final int type,
final String name, final String value, final int userId) throws Exception {
- setSettingAndAssertSuccessfulChange(new Runnable() {
- @Override
- public void run() {
- setStringViaFrontEndApiSetting(type, name, value, userId);
- }
+ setSettingAndAssertSuccessfulChange(() -> {
+ setStringViaFrontEndApiSetting(type, name, value, userId);
}, type, name, value, userId);
}
private void setSettingViaProviderApiAndAssertSuccessfulChange(final int type,
final String name, final String value, final boolean withTableRowUri)
throws Exception {
- setSettingAndAssertSuccessfulChange(new Runnable() {
- @Override
- public void run() {
- insertStringViaProviderApi(type, name, value, withTableRowUri);
- }
+ setSettingAndAssertSuccessfulChange(() -> {
+ insertStringViaProviderApi(type, name, value, withTableRowUri);
}, type, name, value, UserHandle.USER_SYSTEM);
}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 9964467..3f68554 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -99,10 +99,10 @@
checkWriteSingleSetting(serializer, CRAZY_STRING, null);
SettingsState.writeSingleSetting(
SettingsState.SETTINGS_VERSION_NEW_ENCODING,
- serializer, null, "k", "v", "package");
+ serializer, null, "k", "v", null, "package", null, false);
SettingsState.writeSingleSetting(
SettingsState.SETTINGS_VERSION_NEW_ENCODING,
- serializer, "1", "k", "v", null);
+ serializer, "1", "k", "v", null, null, null, false);
}
private void checkWriteSingleSetting(XmlSerializer serializer, String key, String value)
@@ -115,7 +115,7 @@
// Make sure the XML serializer won't crash.
SettingsState.writeSingleSetting(
SettingsState.SETTINGS_VERSION_NEW_ENCODING,
- serializer, "1", key, value, "package");
+ serializer, "1", key, value, null, "package", null, false);
}
/**
@@ -126,19 +126,19 @@
file.delete();
final Object lock = new Object();
- final SettingsState ssWriter = new SettingsState(lock, file, 1,
+ final SettingsState ssWriter = new SettingsState(getContext(), lock, file, 1,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
ssWriter.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING);
- ssWriter.insertSettingLocked("k1", "\u0000", "package");
- ssWriter.insertSettingLocked("k2", "abc", "p2");
- ssWriter.insertSettingLocked("k3", null, "p2");
- ssWriter.insertSettingLocked("k4", CRAZY_STRING, "p3");
+ ssWriter.insertSettingLocked("k1", "\u0000", null, false, "package");
+ ssWriter.insertSettingLocked("k2", "abc", null, false, "p2");
+ ssWriter.insertSettingLocked("k3", null, null, false, "p2");
+ ssWriter.insertSettingLocked("k4", CRAZY_STRING, null, false, "p3");
synchronized (lock) {
ssWriter.persistSyncLocked();
}
- final SettingsState ssReader = new SettingsState(lock, file, 1,
+ final SettingsState ssReader = new SettingsState(getContext(), lock, file, 1,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
synchronized (lock) {
assertEquals("\u0000", ssReader.getSettingLocked("k1").getValue());
@@ -165,7 +165,7 @@
"</settings>");
os.close();
- final SettingsState ss = new SettingsState(lock, file, 1,
+ final SettingsState ss = new SettingsState(getContext(), lock, file, 1,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
synchronized (lock) {
SettingsState.Setting s;
diff --git a/packages/SystemUI/res/color/segmented_button_text_selector.xml b/packages/SystemUI/res/color/segmented_button_text_selector.xml
index 909a6dd..537cbb8 100644
--- a/packages/SystemUI/res/color/segmented_button_text_selector.xml
+++ b/packages/SystemUI/res/color/segmented_button_text_selector.xml
@@ -16,8 +16,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_selected="true" android:color="@color/segmented_button_selected"/>
- <item android:color="@color/segmented_button_unselected"/>
-
+ <item android:state_selected="true" android:color="?android:attr/textColorPrimary"/>
+ <item android:alpha="0.58" android:color="?android:attr/colorForeground"/>
</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_access_alarms_small.xml b/packages/SystemUI/res/drawable/ic_access_alarms_small.xml
index cf64689..994274a 100644
--- a/packages/SystemUI/res/drawable/ic_access_alarms_small.xml
+++ b/packages/SystemUI/res/drawable/ic_access_alarms_small.xml
@@ -17,7 +17,8 @@
android:width="16dp"
android:height="16dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#64ffffff"
diff --git a/packages/SystemUI/res/drawable/ic_qs_network_logging.xml b/packages/SystemUI/res/drawable/ic_qs_network_logging.xml
index 1340ae1..87b5a14 100644
--- a/packages/SystemUI/res/drawable/ic_qs_network_logging.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_network_logging.xml
@@ -24,6 +24,6 @@
android:tint="#4DFFFFFF" >
<path
android:fillColor="#FFFFFFFF"
- android:pathData="M7,18v-2h6v2H7z M7,14v-2h10v2H7z M8.5,9 12,5.5 15.5,9 13,9 13,13 11,13 11,9z"/>
+ android:pathData="M2,24v-4h12v4H2z M2,16v-4h20v4H2z M5,7 12,0 19,7 14,7 14,15 10,15 10,7z"/>
</vector>
diff --git a/packages/SystemUI/res/layout-sw410dp/status_bar_alarm_group.xml b/packages/SystemUI/res/layout-sw410dp/status_bar_alarm_group.xml
index a726161..efdfae7 100644
--- a/packages/SystemUI/res/layout-sw410dp/status_bar_alarm_group.xml
+++ b/packages/SystemUI/res/layout-sw410dp/status_bar_alarm_group.xml
@@ -67,7 +67,7 @@
android:paddingTop="3dp"
android:drawablePadding="8dp"
android:drawableStart="@drawable/ic_access_alarms_small"
- android:textColor="#64ffffff"
+ android:textColor="?android:attr/textColorSecondary"
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
android:gravity="top"
android:background="?android:attr/selectableItemBackground"
diff --git a/packages/SystemUI/res/layout/battery_detail.xml b/packages/SystemUI/res/layout/battery_detail.xml
index 8abfcf6..6162d65 100644
--- a/packages/SystemUI/res/layout/battery_detail.xml
+++ b/packages/SystemUI/res/layout/battery_detail.xml
@@ -41,7 +41,7 @@
android:layout_marginEnd="24dp"
systemui:sideLabels="@array/battery_labels"
android:colorAccent="?android:attr/colorAccent"
- systemui:textColor="#66FFFFFF" />
+ systemui:textColor="?android:attr/textColorSecondary" />
<com.android.systemui.ResizingSpace
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
index 69e52c5..d49aff9 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
@@ -14,7 +14,8 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.systemui.statusbar.KeyboardShortcutAppItemLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -37,6 +38,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="12dp"
+ android:paddingBottom="4dp"
android:textColor="@color/ksh_keyword_color"
android:textSize="16sp"
android:maxLines="5"
@@ -55,4 +57,4 @@
android:scrollHorizontally="false"
android:layout_centerVertical="true"
android:focusable="true"/>
-</RelativeLayout>
+</com.android.systemui.statusbar.KeyboardShortcutAppItemLayout>
diff --git a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml
new file mode 100644
index 0000000..2ba04fd
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/scrollView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="?android:attr/dialogPreferredPadding"
+ android:paddingRight="?android:attr/dialogPreferredPadding"
+ android:paddingLeft="?android:attr/dialogPreferredPadding"
+ android:paddingBottom="?android:attr/dialogPreferredPadding"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/device_owner_warning"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@android:style/TextAppearance.Material.Subhead"
+ android:textColor="?android:attr/textColorPrimaryInverse"
+ />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <ImageView
+ android:id="@+id/vpn_icon"
+ android:layout_width="@dimen/qs_footer_dialog_icon_size"
+ android:layout_height="wrap_content"
+ android:paddingTop="?android:attr/dialogPreferredPadding"
+ android:layout_marginStart="@dimen/qs_footer_dialog_icon_margin"
+ android:layout_marginEnd="@dimen/qs_footer_dialog_icon_margin"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_qs_vpn"
+ android:tint="?android:attr/textColorPrimaryInverse"
+ android:adjustViewBounds="true"/>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/vpn_subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="?android:attr/dialogPreferredPadding"
+ android:text="@string/monitoring_subtitle_vpn"
+ style="@android:style/TextAppearance.Material.Title"
+ android:textColor="?android:attr/textColorPrimaryInverse"
+ />
+ <TextView
+ android:id="@+id/vpn_warning"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@null"
+ style="@android:style/TextAppearance.Material.Subhead"
+ android:textColor="?android:attr/textColorPrimaryInverse"
+ />
+ </LinearLayout>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <ImageView
+ android:id="@+id/network_logging_icon"
+ android:layout_width="@dimen/qs_footer_dialog_icon_size"
+ android:layout_height="wrap_content"
+ android:paddingTop="?android:attr/dialogPreferredPadding"
+ android:layout_marginStart="@dimen/qs_footer_dialog_icon_margin"
+ android:layout_marginEnd="@dimen/qs_footer_dialog_icon_margin"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_qs_network_logging"
+ android:tint="?android:attr/textColorPrimaryInverse"
+ android:alpha="@dimen/qs_footer_dialog_network_logging_icon_alpha"
+ android:adjustViewBounds="true"/>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/network_logging_subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="?android:attr/dialogPreferredPadding"
+ android:text="@string/monitoring_subtitle_network_logging"
+ style="@android:style/TextAppearance.Material.Title"
+ android:textColor="?android:attr/textColorPrimaryInverse"
+ />
+ <TextView
+ android:id="@+id/network_logging_warning"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/monitoring_description_network_logging"
+ style="@android:style/TextAppearance.Material.Subhead"
+ android:textColor="?android:attr/textColorPrimaryInverse"
+ />
+ </LinearLayout>
+ </LinearLayout>
+ </LinearLayout>
+</ScrollView>
diff --git a/packages/SystemUI/res/layout/status_bar_alarm_group.xml b/packages/SystemUI/res/layout/status_bar_alarm_group.xml
index 701b621..03585b8 100644
--- a/packages/SystemUI/res/layout/status_bar_alarm_group.xml
+++ b/packages/SystemUI/res/layout/status_bar_alarm_group.xml
@@ -65,7 +65,7 @@
android:paddingTop="3dp"
android:drawablePadding="8dp"
android:drawableStart="@drawable/ic_access_alarms_small"
- android:textColor="#64ffffff"
+ android:textColor="?android:attr/textColorSecondary"
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
android:gravity="top"
android:background="?android:attr/selectableItemBackground"
diff --git a/packages/SystemUI/res/layout/status_bar_notification_shelf.xml b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml
index 7bfbd3c..6db16fe 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_shelf.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml
@@ -19,6 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_shelf_height"
+ android:contentDescription="@string/notification_shelf_content_description"
android:focusable="true"
android:clickable="true"
>
diff --git a/packages/SystemUI/res/values-sw900dp/config.xml b/packages/SystemUI/res/values-sw900dp/config.xml
index 182fa36..d8f9ef4 100644
--- a/packages/SystemUI/res/values-sw900dp/config.xml
+++ b/packages/SystemUI/res/values-sw900dp/config.xml
@@ -19,6 +19,6 @@
<resources>
<!-- Nav bar button default ordering/layout -->
- <string name="config_navBarLayout" translatable="false">space[.2],back,home;space;menu_ime,recent,space[.2]</string>
+ <string name="config_navBarLayout" translatable="false">back,home;space;menu_ime,recent</string>
</resources>
diff --git a/packages/SystemUI/res/values-sw900dp/dimens.xml b/packages/SystemUI/res/values-sw900dp/dimens.xml
index 72e10c2..2cff976 100644
--- a/packages/SystemUI/res/values-sw900dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw900dp/dimens.xml
@@ -17,7 +17,6 @@
-->
<resources>
- <!-- All ryu nav buttons are th same size -->
<dimen name="button_size">80dp</dimen>
<dimen name="navigation_side_padding">@dimen/button_size</dimen>
<dimen name="navigation_key_width">@dimen/button_size</dimen>
@@ -27,14 +26,6 @@
<dimen name="key_button_ripple_max_width">76dp</dimen>
<!-- The padding around the navigation buttons -->
- <dimen name="navigation_key_padding">5dp</dimen>
+ <dimen name="navigation_key_padding">0dp</dimen>
- <!-- The inner radius of the halo. -->
- <dimen name="halo_inner_radius">12dp</dimen>
-
- <!-- The thickness of the halo. -->
- <dimen name="halo_thickness">1dp</dimen>
-
- <!-- The diameter of the halo. This is 2*(halo_inner_radius + halo_thickness). -->
- <dimen name="halo_diameter">26dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index e41991a..c7149df 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -120,8 +120,6 @@
<color name="screen_pinning_request_window_bg">#80000000</color>
<color name="segmented_buttons_background">#14FFFFFF</color><!-- 8% white -->
- <color name="segmented_button_selected">#FFFFFFFF</color>
- <color name="segmented_button_unselected">@*android:color/quaternary_device_default_settings</color>
<color name="dark_mode_icon_color_single_tone">#99000000</color>
<color name="dark_mode_icon_color_dual_tone_background">#3d000000</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5f9ade5..ff4ec5b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -258,6 +258,13 @@
<!-- How far the expanded QS panel peeks from the header in collapsed state. -->
<dimen name="qs_peek_height">0dp</dimen>
+ <!-- How large the icons in the quick settings footer dialog are -->
+ <dimen name="qs_footer_dialog_icon_size">24sp</dimen>
+ <!-- Left and right margin of the icons -->
+ <dimen name="qs_footer_dialog_icon_margin">8sp</dimen>
+ <!-- Alpha value of network logging icon -->
+ <dimen name="qs_footer_dialog_network_logging_icon_alpha">0.3</dimen>
+
<!-- Zen mode panel: condition item button padding -->
<dimen name="zen_mode_condition_detail_button_padding">8dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens_grid.xml b/packages/SystemUI/res/values/dimens_grid.xml
index 43e152a..94ffdb1 100644
--- a/packages/SystemUI/res/values/dimens_grid.xml
+++ b/packages/SystemUI/res/values/dimens_grid.xml
@@ -17,7 +17,9 @@
-->
<resources>
<dimen name="recents_grid_padding_left_right">32dp</dimen>
- <dimen name="recents_grid_padding_top_bottom">84dp</dimen>
+ <dimen name="recents_grid_padding_top_bottom">150dp</dimen>
<dimen name="recents_grid_padding_task_view">20dp</dimen>
+ <dimen name="recents_grid_task_view_header_height">44dp</dimen>
+ <dimen name="recents_grid_task_view_header_button_padding">8dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 31b83cc..645ad6f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -408,6 +408,9 @@
<!-- Content description of the button for showing a notifications panel in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_notifications_button">Notifications.</string>
+ <!-- Content description of overflow icon container of the notifications for accessibility (not shown on the screen)[CHAR LIMIT=NONE] -->
+ <string name="notification_shelf_content_description">Notification overflow container</string>
+
<!-- Content description of the button for removing a notification in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_remove_notification">Clear notification.</string>
@@ -1010,6 +1013,13 @@
<!-- Monitoring dialog title for normal devices [CHAR LIMIT=35]-->
<string name="monitoring_title">Network monitoring</string>
+ <!-- STOPSHIP Monitoring strings still need to be finalized and approved -->
+ <!-- Monitoring dialog subtitle for the section describing VPN [CHAR LIMIT=TODO]-->
+ <string name="monitoring_subtitle_vpn">VPN</string>
+
+ <!-- Monitoring dialog subtitle for the section describing network logging [CHAR LIMIT=TODO]-->
+ <string name="monitoring_subtitle_network_logging">Network Logging</string>
+
<!-- Monitoring dialog disable vpn button [CHAR LIMIT=30] -->
<string name="disable_vpn">Disable VPN</string>
@@ -1025,15 +1035,25 @@
<!-- Monitoring dialog: Part of text body explaining what a Device Owner app can do [CHAR LIMIT=130] -->
<string name="monitoring_description_do_body">Your administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.</string>
- <!-- Monitoring dialog: Part of text body explaining that a VPN is connected and what it can do, for devices managed by a Device Owner app [CHAR LIMIT=130] -->
- <string name="monitoring_description_do_body_vpn">You\'re connected to <xliff:g id="vpn_app">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string>
-
<!-- Monitoring dialog: Space that separates the body text and the "learn more" link that follows it. [CHAR LIMIT=5] -->
<string name="monitoring_description_do_learn_more_separator">" "</string>
<!-- Monitoring dialog: Link to learn more about what a Device Owner app can do [CHAR LIMIT=55] -->
<string name="monitoring_description_do_learn_more">Learn more</string>
+ <!-- Monitoring dialog: Part of text body explaining that a VPN is connected and what it can do, for devices managed by a Device Owner app [CHAR LIMIT=130] -->
+ <string name="monitoring_description_do_body_vpn">You\'re connected to <xliff:g id="vpn_app">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string>
+
+ <!-- Monitoring dialog: Space that separates the VPN body text and the "Open VPN Settings" link that follows it. [CHAR LIMIT=5] -->
+ <string name="monitoring_description_vpn_settings_separator">" "</string>
+
+ <!-- Monitoring dialog: Link to open the VPN settings page [CHAR LIMIT=TODO] -->
+ <string name="monitoring_description_vpn_settings">Open VPN Settings</string>
+
+ <!-- Monitoring dialog: Network logging text [CHAR LIMIT=TODO] -->
+ <string name="monitoring_description_network_logging">Your admin has turned on network logging, which monitors traffic on your device.\n\nFor more information contact your admin.</string>
+
+
<!-- Monitoring dialog VPN text [CHAR LIMIT=400] -->
<string name="monitoring_description_vpn">You gave an app permission to set up a VPN connection.\n\nThis app can monitor your device and network activity, including emails, apps, and websites.</string>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index f3da47b..03cd959 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -31,6 +31,7 @@
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.ImageView;
@@ -115,6 +116,10 @@
}
private void handleClick() {
+ showDeviceMonitoringDialog();
+ }
+
+ public void showDeviceMonitoringDialog() {
mHost.collapsePanels();
// TODO: Delay dialog creation until after panels are collapsed.
createDialog();
@@ -175,6 +180,7 @@
private void createDialog() {
final String deviceOwnerPackage = mSecurityController.getDeviceOwnerName();
final String profileOwnerPackage = mSecurityController.getProfileOwnerName();
+ final boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled();
final String primaryVpn = mSecurityController.getPrimaryVpnName();
final String profileVpn = mSecurityController.getProfileVpnName();
final CharSequence deviceOwnerOrganization =
@@ -186,24 +192,47 @@
if (!isBranded) {
mDialog.setTitle(getTitle(deviceOwnerPackage));
}
- mDialog.setMessage(getMessage(deviceOwnerPackage, profileOwnerPackage, primaryVpn,
- profileVpn, deviceOwnerOrganization, hasProfileOwner, isBranded));
+ CharSequence msg = getMessage(deviceOwnerPackage, profileOwnerPackage, primaryVpn,
+ profileVpn, deviceOwnerOrganization, hasProfileOwner, isBranded);
+ if (deviceOwnerPackage == null) {
+ mDialog.setMessage(msg);
+ } else {
+ View dialogView = LayoutInflater.from(mContext)
+ .inflate(R.layout.quick_settings_footer_dialog, null, false);
+ mDialog.setView(dialogView);
+ TextView deviceOwnerWarning =
+ (TextView) dialogView.findViewById(R.id.device_owner_warning);
+ deviceOwnerWarning.setText(msg);
+ // Make the link "learn more" clickable.
+ deviceOwnerWarning.setMovementMethod(new LinkMovementMethod());
+ if (primaryVpn == null) {
+ dialogView.findViewById(R.id.vpn_icon).setVisibility(View.GONE);
+ dialogView.findViewById(R.id.vpn_subtitle).setVisibility(View.GONE);
+ dialogView.findViewById(R.id.vpn_warning).setVisibility(View.GONE);
+ } else {
+ final SpannableStringBuilder message = new SpannableStringBuilder();
+ message.append(mContext.getString(R.string.monitoring_description_do_body_vpn,
+ primaryVpn));
+ message.append(mContext.getString(
+ R.string.monitoring_description_vpn_settings_separator));
+ message.append(mContext.getString(R.string.monitoring_description_vpn_settings),
+ new VpnSpan(), 0);
- mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(isBranded), this);
- if (mSecurityController.isVpnEnabled() && !mSecurityController.isVpnRestricted()) {
- mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getSettingsButton(), this);
+ TextView vpnWarning = (TextView) dialogView.findViewById(R.id.vpn_warning);
+ vpnWarning.setText(message);
+ // Make the link "Open VPN Settings" clickable.
+ vpnWarning.setMovementMethod(new LinkMovementMethod());
+ }
+ if (!isNetworkLoggingEnabled) {
+ dialogView.findViewById(R.id.network_logging_icon).setVisibility(View.GONE);
+ dialogView.findViewById(R.id.network_logging_subtitle).setVisibility(View.GONE);
+ dialogView.findViewById(R.id.network_logging_warning).setVisibility(View.GONE);
+ }
}
- // Make the link "learn more" clickable.
- // TODO: Reaching into SystemUIDialog's internal View hierarchy is ugly and error-prone.
- // This is a temporary solution. b/33126622 will introduce a custom View hierarchy for this
- // dialog, allowing us to set the movement method on the appropriate View without any
- // knowledge of SystemUIDialog's internals.
- mDialog.create();
- ((TextView) mDialog.getWindow().findViewById(com.android.internal.R.id.message))
- .setMovementMethod(new LinkMovementMethod());
-
+ mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(isBranded), this);
mDialog.show();
+ mDialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
private String getSettingsButton() {
@@ -229,11 +258,6 @@
}
message.append("\n\n");
message.append(mContext.getString(R.string.monitoring_description_do_body));
- if (primaryVpn != null) {
- message.append("\n\n");
- message.append(mContext.getString(R.string.monitoring_description_do_body_vpn,
- primaryVpn));
- }
message.append(mContext.getString(
R.string.monitoring_description_do_learn_more_separator));
message.append(mContext.getString(R.string.monitoring_description_do_learn_more),
@@ -340,4 +364,14 @@
return object instanceof EnterprisePrivacySpan;
}
}
+
+ protected class VpnSpan extends ClickableSpan {
+ @Override
+ public void onClick(View widget) {
+ final Intent intent = new Intent(Settings.ACTION_VPN_SETTINGS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mDialog.dismiss();
+ mContext.startActivity(intent);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index d8855c8..6b24a1e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -519,6 +519,10 @@
return mFooter;
}
+ public void showDeviceMonitoringDialog() {
+ mFooter.showDeviceMonitoringDialog();
+ }
+
private class H extends Handler {
private static final int SHOW_DETAIL = 1;
private static final int SET_TILE_VISIBILITY = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 9bbead4..0be53b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -60,8 +60,6 @@
private final QSDetailClipper mClipper;
- private PhoneStatusBar mPhoneStatusBar;
-
private boolean isShown;
private QSTileHost mHost;
private RecyclerView mRecyclerView;
@@ -119,7 +117,6 @@
public void setHost(QSTileHost host) {
mHost = host;
- mPhoneStatusBar = host.getPhoneStatusBar();
mTileAdapter.setHost(host);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index c9c4f2b..1371381 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -576,7 +576,8 @@
R.dimen.recents_task_view_header_height,
R.dimen.recents_task_view_header_height_tablet_land,
R.dimen.recents_task_view_header_height,
- R.dimen.recents_task_view_header_height_tablet_land);
+ R.dimen.recents_task_view_header_height_tablet_land,
+ R.dimen.recents_grid_task_view_header_height);
LayoutInflater inflater = LayoutInflater.from(mContext);
mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
@@ -720,7 +721,7 @@
if (task.isFreeformTask()) {
mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
stackScroller.getStackScroll(), mTmpTransform, null,
- windowOverrideRect);
+ windowOverrideRect, false /* useGridLayout */);
Bitmap thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform,
mThumbTransitionBitmapCache);
Rect toTaskRect = new Rect();
@@ -770,7 +771,8 @@
stackView.updateLayoutAlgorithm(true /* boundScroll */);
stackView.updateToInitialState();
stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
- stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect);
+ stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect,
+ Recents.getConfiguration().isGridEnabled);
return mTmpTransform;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 3c7012a..d8fdd7a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -337,7 +337,8 @@
if (RecentsDebugFlags.Static.EnableStackActionButton) {
// Measure the stack action button within the constraints of the space above the stack
- Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect;
+ Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect(
+ mTaskStackView.useGridLayout());
measureChild(mStackActionButton,
MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
@@ -772,7 +773,8 @@
* @return the bounds of the stack action button.
*/
private Rect getStackActionButtonBoundsFromStackLayout() {
- Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect);
+ Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect(
+ mTaskStackView.useGridLayout()));
int left = isLayoutRtl()
? actionButtonRect.left - mStackActionButton.getPaddingLeft()
: actionButtonRect.right + mStackActionButton.getPaddingRight()
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 3c97310..0529856 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -240,14 +240,14 @@
// This is the current system insets
@ViewDebug.ExportedProperty(category="recents")
public Rect mSystemInsets = new Rect();
- // This is the bounds of the stack action above the stack rect
- @ViewDebug.ExportedProperty(category="recents")
- public Rect mStackActionButtonRect = new Rect();
// The visible ranges when the stack is focused and unfocused
private Range mUnfocusedRange;
private Range mFocusedRange;
+ // This is the bounds of the stack action above the stack rect
+ @ViewDebug.ExportedProperty(category="recents")
+ private Rect mStackActionButtonRect = new Rect();
// The base top margin for the stack from the system insets
@ViewDebug.ExportedProperty(category="recents")
private int mBaseTopMargin;
@@ -370,6 +370,7 @@
R.dimen.recents_layout_initial_top_offset_tablet,
R.dimen.recents_layout_initial_top_offset_tablet,
R.dimen.recents_layout_initial_top_offset_tablet,
+ R.dimen.recents_layout_initial_top_offset_tablet,
R.dimen.recents_layout_initial_top_offset_tablet);
mBaseInitialBottomOffset = getDimensionForDevice(context,
R.dimen.recents_layout_initial_bottom_offset_phone_port,
@@ -377,6 +378,7 @@
R.dimen.recents_layout_initial_bottom_offset_tablet,
R.dimen.recents_layout_initial_bottom_offset_tablet,
R.dimen.recents_layout_initial_bottom_offset_tablet,
+ R.dimen.recents_layout_initial_bottom_offset_tablet,
R.dimen.recents_layout_initial_bottom_offset_tablet);
mFreeformLayoutAlgorithm.reloadOnConfigurationChange(context);
mTaskGridLayoutAlgorithm.reloadOnConfigurationChange(context);
@@ -384,11 +386,13 @@
mBaseTopMargin = getDimensionForDevice(context,
R.dimen.recents_layout_top_margin_phone,
R.dimen.recents_layout_top_margin_tablet,
- R.dimen.recents_layout_top_margin_tablet_xlarge);
+ R.dimen.recents_layout_top_margin_tablet_xlarge,
+ R.dimen.recents_layout_top_margin_tablet);
mBaseSideMargin = getDimensionForDevice(context,
R.dimen.recents_layout_side_margin_phone,
R.dimen.recents_layout_side_margin_tablet,
- R.dimen.recents_layout_side_margin_tablet_xlarge);
+ R.dimen.recents_layout_side_margin_tablet_xlarge,
+ R.dimen.recents_layout_side_margin_tablet);
mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin);
mFreeformStackGap =
res.getDimensionPixelSize(R.dimen.recents_freeform_layout_bottom_margin);
@@ -408,6 +412,7 @@
public boolean setSystemInsets(Rect systemInsets) {
boolean changed = !mSystemInsets.equals(systemInsets);
mSystemInsets.set(systemInsets);
+ mTaskGridLayoutAlgorithm.setSystemInsets(systemInsets);
return changed;
}
@@ -727,6 +732,11 @@
}
}
+ public Rect getStackActionButtonRect(boolean useGridLayout) {
+ return useGridLayout
+ ? mTaskGridLayoutAlgorithm.getStackActionButtonRect() : mStackActionButtonRect;
+ }
+
/**
* Returns the TaskViewTransform that would put the task just off the back of the stack.
*/
@@ -876,10 +886,10 @@
*/
public TaskViewTransform getStackTransformScreenCoordinates(Task task, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform frontTransform,
- Rect windowOverrideRect) {
+ Rect windowOverrideRect, boolean useGridLayout) {
TaskViewTransform transform = getStackTransform(task, stackScroll, mFocusState,
transformOut, frontTransform, true /* forceUpdate */,
- false /* ignoreTaskOverrides */, false /* useGridLayout */);
+ false /* ignoreTaskOverrides */, useGridLayout);
return transformToScreenCoordinates(transform, windowOverrideRect);
}
@@ -1100,9 +1110,9 @@
* Retrieves resources that are constant regardless of the current configuration of the device.
*/
public static int getDimensionForDevice(Context ctx, int phoneResId,
- int tabletResId, int xlargeTabletResId) {
+ int tabletResId, int xlargeTabletResId, int gridLayoutResId) {
return getDimensionForDevice(ctx, phoneResId, phoneResId, tabletResId, tabletResId,
- xlargeTabletResId, xlargeTabletResId);
+ xlargeTabletResId, xlargeTabletResId, gridLayoutResId);
}
/**
@@ -1110,12 +1120,14 @@
*/
public static int getDimensionForDevice(Context ctx, int phonePortResId, int phoneLandResId,
int tabletPortResId, int tabletLandResId, int xlargeTabletPortResId,
- int xlargeTabletLandResId) {
+ int xlargeTabletLandResId, int gridLayoutResId) {
RecentsConfiguration config = Recents.getConfiguration();
Resources res = ctx.getResources();
boolean isLandscape = Utilities.getAppConfiguration(ctx).orientation ==
Configuration.ORIENTATION_LANDSCAPE;
- if (config.isXLargeScreen) {
+ if (config.isGridEnabled) {
+ return res.getDimensionPixelSize(gridLayoutResId);
+ } else if (config.isXLargeScreen) {
return res.getDimensionPixelSize(isLandscape
? xlargeTabletLandResId
: xlargeTabletPortResId);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 57fde67..4625ca7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -1345,14 +1345,7 @@
setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
false /* requestViewFocus */);
}
-
- // Update the stack action button visibility
- if (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
- mStack.getTaskCount() > 0) {
- EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
- } else {
- EventBus.getDefault().send(new HideStackActionButtonEvent());
- }
+ updateStackActionButtonVisibility();
}
public boolean isTouchPointInView(float x, float y, TaskView tv) {
@@ -1647,7 +1640,8 @@
relayoutTaskViewsOnNextFrame(animation);
}
- if (mEnterAnimationComplete) {
+ // In grid layout, the stack action button always remains visible.
+ if (mEnterAnimationComplete && !useGridLayout()) {
if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
mStack.getTaskCount() > 0) {
@@ -2063,6 +2057,9 @@
}
}
+ // Update the Clear All button in case we're switching in or out of grid layout.
+ updateStackActionButtonVisibility();
+
// Trigger a new layout and update to the initial state if necessary
if (event.fromMultiWindow) {
mInitialState = INITIAL_STATE_UPDATE_LAYOUT_ONLY;
@@ -2156,6 +2153,17 @@
Settings.System.LOCK_TO_APP_ENABLED) != 0;
}
+ private void updateStackActionButtonVisibility() {
+ // Always show the button in grid layout.
+ if (useGridLayout() ||
+ (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
+ mStack.getTaskCount() > 0)) {
+ EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
+ } else {
+ EventBus.getDefault().send(new HideStackActionButtonEvent());
+ }
+ }
+
public void dump(String prefix, PrintWriter writer) {
String innerPrefix = prefix + " ";
String id = Integer.toHexString(System.identityHashCode(this));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 71f559b..33fa3b0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -168,7 +168,7 @@
/** Touch preprocessing for handling below */
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Pass through to swipe helper if we are swiping
- mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
+ mInterceptedBySwipeHelper = isSwipingEnabled() && mSwipeHelper.onInterceptTouchEvent(ev);
if (mInterceptedBySwipeHelper) {
return true;
}
@@ -680,4 +680,11 @@
public float getScaledDismissSize() {
return 1.5f * Math.max(mSv.getWidth(), mSv.getHeight());
}
+
+ /**
+ * Returns whether swiping is enabled.
+ */
+ private boolean isSwipingEnabled() {
+ return !mSv.useGridLayout();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 9b30515..2f4ad6a4d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -148,7 +148,7 @@
private ArrayList<Animator> mTmpAnimators = new ArrayList<>();
@ViewDebug.ExportedProperty(deepExport=true, prefix="thumbnail_")
- TaskViewThumbnail mThumbnailView;
+ protected TaskViewThumbnail mThumbnailView;
@ViewDebug.ExportedProperty(deepExport=true, prefix="header_")
TaskViewHeader mHeaderView;
private View mActionButtonView;
@@ -239,7 +239,7 @@
/**
* Update the task view when the configuration changes.
*/
- void onConfigurationChanged() {
+ protected void onConfigurationChanged() {
mHeaderView.onConfigurationChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 6be1691..c0cc83f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -290,14 +290,16 @@
R.dimen.recents_task_view_header_height,
R.dimen.recents_task_view_header_height_tablet_land,
R.dimen.recents_task_view_header_height,
- R.dimen.recents_task_view_header_height_tablet_land);
+ R.dimen.recents_task_view_header_height_tablet_land,
+ R.dimen.recents_grid_task_view_header_height);
int headerButtonPadding = TaskStackLayoutAlgorithm.getDimensionForDevice(getContext(),
R.dimen.recents_task_view_header_button_padding,
R.dimen.recents_task_view_header_button_padding,
R.dimen.recents_task_view_header_button_padding,
R.dimen.recents_task_view_header_button_padding_tablet_land,
R.dimen.recents_task_view_header_button_padding,
- R.dimen.recents_task_view_header_button_padding_tablet_land);
+ R.dimen.recents_task_view_header_button_padding_tablet_land,
+ R.dimen.recents_grid_task_view_header_button_padding);
if (headerBarHeight != mHeaderBarHeight || headerButtonPadding != mHeaderButtonPadding) {
mHeaderBarHeight = headerBarHeight;
mHeaderButtonPadding = headerButtonPadding;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index 780cbca..d109807c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -60,6 +60,8 @@
@ViewDebug.ExportedProperty(category="recents")
private float mThumbnailScale;
private float mFullscreenThumbnailScale;
+ private boolean mSizeToFit = false;
+ private boolean mOverlayHeaderOnThumbnailActionBar = true;
private ActivityManager.TaskThumbnailInfo mThumbnailInfo;
private int mCornerRadius;
@@ -136,13 +138,14 @@
int thumbnailHeight = Math.min(viewHeight,
(int) (mThumbnailRect.height() * mThumbnailScale));
- if (mTask.isLocked) {
+ if (mTask != null && mTask.isLocked) {
canvas.drawRoundRect(0, 0, viewWidth, viewHeight, mCornerRadius, mCornerRadius,
mLockedPaint);
} else if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
- int topOffset = mTaskBar != null
- ? mTaskBar.getHeight() - mCornerRadius
- : 0;
+ int topOffset = 0;
+ if (mTaskBar != null && mOverlayHeaderOnThumbnailActionBar) {
+ topOffset = mTaskBar.getHeight() - mCornerRadius;
+ }
// Draw the background, there will be some small overdraw with the thumbnail
if (thumbnailWidth < viewWidth) {
@@ -238,7 +241,7 @@
// If we haven't measured or the thumbnail is invalid, skip the thumbnail drawing
// and only draw the background color
mThumbnailScale = 0f;
- } else if (isStackTask) {
+ } else if (isStackTask && !mSizeToFit) {
float invThumbnailScale = 1f / mFullscreenThumbnailScale;
if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) {
if (mThumbnailInfo.screenOrientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -270,6 +273,19 @@
}
}
+ /** Sets whether the thumbnail should be resized to fit the task view in all orientations. */
+ public void setSizeToFit(boolean flag) {
+ mSizeToFit = flag;
+ }
+
+ /**
+ * Sets whether the header should overlap (and hide) the action bar in the thumbnail, or
+ * be stacked just above it.
+ */
+ public void setOverlayHeaderOnThumbnailActionBar(boolean flag) {
+ mOverlayHeaderOnThumbnailActionBar = flag;
+ }
+
/** Updates the clip rect based on the given task bar. */
void updateClipToTaskBar(View taskBar) {
mTaskBar = taskBar;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java
index da14e2b..6300400 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java
@@ -23,6 +23,10 @@
import com.android.systemui.recents.views.TaskView;
public class GridTaskView extends TaskView {
+
+ /** The height, in pixels, of the header view. */
+ private int mHeaderHeight;
+
public GridTaskView(Context context) {
this(context, null);
}
@@ -37,6 +41,18 @@
public GridTaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ mHeaderHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.recents_grid_task_view_header_height);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ // Show the full thumbnail and don't overlap with the header.
+ mThumbnailView.setSizeToFit(true);
+ mThumbnailView.setOverlayHeaderOnThumbnailActionBar(false);
+ mThumbnailView.updateThumbnailScale();
+ mThumbnailView.setTranslationY(mHeaderHeight);
}
@Override
@@ -44,4 +60,12 @@
return new AnimateableGridViewBounds(this, mContext.getResources().getDimensionPixelSize(
R.dimen.recents_task_view_shadow_rounded_corners_radius));
}
+
+ @Override
+ protected void onConfigurationChanged() {
+ super.onConfigurationChanged();
+ mHeaderHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.recents_grid_task_view_header_height);
+ mThumbnailView.setTranslationY(mHeaderHeight);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
index 8d1bec8..65a8ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
@@ -18,7 +18,10 @@
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.view.WindowManager;
+
import com.android.systemui.R;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
@@ -26,15 +29,29 @@
public class TaskGridLayoutAlgorithm {
+ private final String TAG = "TaskGridLayoutAlgorithm";
+ private final int MAX_LAYOUT_TASK_COUNT = 8;
+
+ /** The horizontal padding around the whole recents view. */
private int mPaddingLeftRight;
+ /** The vertical padding around the whole recents view. */
private int mPaddingTopBottom;
+ /** The padding between task views. */
private int mPaddingTaskView;
private Rect mDisplayRect;
private Rect mWindowRect;
+ private Point mScreenSize = new Point();
private Rect mTaskGridRect;
+ /** The height, in pixels, of each task view's title bar. */
+ private int mTitleBarHeight;
+
+ /** The aspect ratio of each task thumbnail, without the title bar. */
+ private float mAppAspectRatio;
+ private Rect mSystemInsets = new Rect();
+
public TaskGridLayoutAlgorithm(Context context) {
reloadOnConfigurationChange(context);
}
@@ -46,38 +63,71 @@
mPaddingTaskView = res.getDimensionPixelSize(R.dimen.recents_grid_padding_task_view);
mTaskGridRect = new Rect();
+ mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
+
+ WindowManager windowManager = (WindowManager) context
+ .getSystemService(Context.WINDOW_SERVICE);
+ windowManager.getDefaultDisplay().getRealSize(mScreenSize);
+
+ updateAppAspectRatio();
}
- public TaskViewTransform getTransform(int taskIndex, int taskAmount,
+ public TaskViewTransform getTransform(int taskIndex, int taskCount,
TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
- int taskPerLine = taskAmount < 2 ? 1 : (
- taskAmount < 5 ? 2 : 3);
+ int layoutTaskCount = Math.min(MAX_LAYOUT_TASK_COUNT, taskCount);
- int taskWidth = (mDisplayRect.width() - mPaddingLeftRight * 2
- - mPaddingTaskView * (taskPerLine - 1)) / taskPerLine;
- int taskHeight = taskWidth * mDisplayRect.height() / mDisplayRect.width();
+ // We also need to invert the index in order to display the most recent tasks first.
+ int taskLayoutIndex = taskCount - taskIndex - 1;
+
+ int tasksPerLine = layoutTaskCount < 2 ? 1 : (
+ layoutTaskCount < 5 ? 2 : (
+ layoutTaskCount < 7 ? 3 : 4));
+ int lines = layoutTaskCount < 3 ? 1 : 2;
+
+ int taskWidth, taskHeight;
+ int maxTaskWidth = (mDisplayRect.width() - 2 * mPaddingLeftRight
+ - (tasksPerLine - 1) * mPaddingTaskView) / tasksPerLine;
+ int maxTaskHeight = (mDisplayRect.height() - 2 * mPaddingTopBottom
+ - (lines - 1) * mPaddingTaskView) / lines;
+
+ if (maxTaskHeight >= maxTaskWidth / mAppAspectRatio + mTitleBarHeight) {
+ // Width bound.
+ taskWidth = maxTaskWidth;
+ taskHeight = (int) (maxTaskWidth / mAppAspectRatio + mTitleBarHeight);
+ } else {
+ // Height bound.
+ taskHeight = maxTaskHeight;
+ taskWidth = (int) ((taskHeight - mTitleBarHeight) * mAppAspectRatio);
+ }
+ int emptySpaceX = mDisplayRect.width() - 2 * mPaddingLeftRight
+ - (tasksPerLine * taskWidth) - (tasksPerLine - 1) * mPaddingTaskView;
+ int emptySpaceY = mDisplayRect.height() - 2 * mPaddingTopBottom
+ - (lines * taskHeight) - (lines - 1) * mPaddingTaskView;
+
mTaskGridRect.set(0, 0, taskWidth, taskHeight);
- int xIndex = taskIndex % taskPerLine;
- int yIndex = taskIndex / taskPerLine;
- int x = mPaddingLeftRight + (taskWidth + mPaddingTaskView) * xIndex;
- int y = mPaddingTopBottom + (taskHeight + mPaddingTaskView) * yIndex;
+ int xIndex = taskLayoutIndex % tasksPerLine;
+ int yIndex = taskLayoutIndex / tasksPerLine;
+ int x = emptySpaceX / 2 + mPaddingLeftRight + (taskWidth + mPaddingTaskView) * xIndex;
+ int y = emptySpaceY / 2 + mPaddingTopBottom + (taskHeight + mPaddingTaskView) * yIndex;
float z = stackLayout.mMaxTranslationZ;
float dimAlpha = 0f;
float viewOutlineAlpha = 0f;
+ boolean isTaskViewVisible = (taskLayoutIndex < MAX_LAYOUT_TASK_COUNT);
// Fill out the transform
transformOut.scale = 1f;
- transformOut.alpha = 1f;
+ transformOut.alpha = isTaskViewVisible ? 1f : 0f;
transformOut.translationZ = z;
transformOut.dimAlpha = dimAlpha;
transformOut.viewOutlineAlpha = viewOutlineAlpha;
transformOut.rect.set(mTaskGridRect);
transformOut.rect.offset(x, y);
Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
- transformOut.visible = true;
+ // We only show the 8 most recent tasks.
+ transformOut.visible = isTaskViewVisible;
return transformOut;
}
@@ -85,4 +135,23 @@
mDisplayRect = displayRect;
mWindowRect = windowRect;
}
-}
\ No newline at end of file
+
+ public void setSystemInsets(Rect systemInsets) {
+ mSystemInsets = systemInsets;
+ updateAppAspectRatio();
+ }
+
+ private void updateAppAspectRatio() {
+ int usableWidth = mScreenSize.x - mSystemInsets.left - mSystemInsets.right;
+ int usableHeight = mScreenSize.y - mSystemInsets.top - mSystemInsets.bottom;
+ mAppAspectRatio = (float) usableWidth / (float) usableHeight;
+ }
+
+ public Rect getStackActionButtonRect() {
+ Rect buttonRect = new Rect(mDisplayRect);
+ buttonRect.right -= mPaddingLeftRight;
+ buttonRect.left += mPaddingLeftRight;
+ buttonRect.bottom = buttonRect.top + mPaddingTopBottom;
+ return buttonRect;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index a0bae20..3cd2a7a 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -67,6 +67,10 @@
return mView;
}
+ public boolean isMinimized() {
+ return mMinimized;
+ }
+
private void addDivider(Configuration configuration) {
mView = (DividerView)
LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 414ebec..5601425 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1351,8 +1351,9 @@
* anything on successful docking
* @param metricsUndockAction the action to log when undocking, or -1 to not log anything when
* undocking
+ * @return true if toggle split screen was successful
*/
- protected abstract void toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction);
+ protected abstract boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction);
/** Proxy for RecentsComponent */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutAppItemLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutAppItemLayout.java
new file mode 100644
index 0000000..507b665
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutAppItemLayout.java
@@ -0,0 +1,60 @@
+/*
+ * 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.statusbar;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+/**
+ * Layout used for displaying keyboard shortcut items inside an alert dialog.
+ * The layout sets the maxWidth of shortcuts keyword textview to 70% of available space.
+ */
+public class KeyboardShortcutAppItemLayout extends RelativeLayout {
+
+ private static final double MAX_WIDTH_PERCENT_FOR_KEYWORDS = 0.70;
+
+ public KeyboardShortcutAppItemLayout(Context context) {
+ super(context);
+ }
+
+ public KeyboardShortcutAppItemLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
+ ImageView shortcutIcon = (ImageView) findViewById(R.id.keyboard_shortcuts_icon);
+ TextView shortcutKeyword = (TextView) findViewById(R.id.keyboard_shortcuts_keyword);
+ int totalMeasuredWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int totalPadding = getPaddingLeft() + getPaddingRight();
+ int availableWidth = totalMeasuredWidth - totalPadding;
+ if (shortcutIcon.getVisibility() == View.VISIBLE) {
+ availableWidth = availableWidth - shortcutIcon.getMeasuredWidth();
+ }
+ shortcutKeyword.setMaxWidth((int)
+ Math.round(availableWidth * MAX_WIDTH_PERCENT_FOR_KEYWORDS));
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index e054bbe..0a138b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -62,6 +62,7 @@
private boolean mHasItemsInStableShelf;
private NotificationIconContainer mCollapsedIcons;
private int mScrollFastThreshold;
+ private int mStatusBarState;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -523,7 +524,10 @@
}
private void setHasItemsInStableShelf(boolean hasItemsInStableShelf) {
- mHasItemsInStableShelf = hasItemsInStableShelf;
+ if (mHasItemsInStableShelf != hasItemsInStableShelf) {
+ mHasItemsInStableShelf = hasItemsInStableShelf;
+ updateInteractiveness();
+ }
}
/**
@@ -538,6 +542,21 @@
mCollapsedIcons = collapsedIcons;
}
+ public void setStatusBarState(int statusBarState) {
+ if (mStatusBarState != statusBarState) {
+ mStatusBarState = statusBarState;
+ updateInteractiveness();
+ }
+ }
+
+ private void updateInteractiveness() {
+ boolean interactive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf;
+ setClickable(interactive);
+ setFocusable(interactive);
+ setImportantForAccessibility(interactive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ : View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
+
private class ShelfState extends ExpandableViewState {
private float openedAmount;
private boolean hasItemsInStableShelf;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 5da652d..eb4c631 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -36,6 +36,7 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityOptions;
+import android.app.admin.DevicePolicyManager;
import android.app.IActivityManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -664,12 +665,9 @@
array.clear();
}
- private final View.OnClickListener mShelfClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mState == StatusBarState.KEYGUARD) {
- goToLockedShade(null);
- }
+ private final View.OnClickListener mGoToLockedShadeListener = v -> {
+ if (mState == StatusBarState.KEYGUARD) {
+ goToLockedShade(null);
}
};
private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
@@ -1013,6 +1011,7 @@
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG);
context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
IntentFilter demoFilter = new IntentFilter();
@@ -1062,8 +1061,9 @@
(NotificationShelf) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_notification_shelf, mStackScroller, false);
mNotificationShelf.setOnActivatedListener(this);
- mNotificationShelf.setOnClickListener(mShelfClickListener);
mStackScroller.setShelf(mNotificationShelf);
+ mNotificationShelf.setOnClickListener(mGoToLockedShadeListener);
+ mNotificationShelf.setStatusBarState(mState);
}
@Override
@@ -1365,35 +1365,33 @@
return false;
}
- ActivityManager.RunningTaskInfo runningTask =
- Recents.getSystemServices().getRunningTask();
- boolean isRunningTaskInHomeOrRecentsStack = runningTask != null &&
- ActivityManager.StackId.isHomeOrRecentsStack(runningTask.stackId);
- if (isRunningTaskInHomeOrRecentsStack) {
- return false;
- }
-
- toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
+ return toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
- return true;
}
};
@Override
- protected void toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
+ protected boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
if (mRecents == null) {
- return;
+ return false;
}
int dockSide = WindowManagerProxy.getInstance().getDockSide();
if (dockSide == WindowManager.DOCKED_INVALID) {
- mRecents.dockTopTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
+ return mRecents.dockTopTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction);
} else {
- EventBus.getDefault().send(new UndockingTaskEvent());
- if (metricsUndockAction != -1) {
- MetricsLogger.action(mContext, metricsUndockAction);
+ Divider divider = getComponent(Divider.class);
+ if (divider != null && divider.isMinimized()) {
+ // Undocking from the minimized state is not supported
+ return false;
+ } else {
+ EventBus.getDefault().send(new UndockingTaskEvent());
+ if (metricsUndockAction != -1) {
+ MetricsLogger.action(mContext, metricsUndockAction);
+ }
}
}
+ return true;
}
private final View.OnLongClickListener mLongPressHomeListener
@@ -3644,6 +3642,9 @@
else if (Intent.ACTION_SCREEN_ON.equals(action)) {
notifyNavigationBarScreenOn(true);
}
+ else if (DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG.equals(action)) {
+ mQSPanel.showDeviceMonitoringDialog();
+ }
}
};
@@ -4604,6 +4605,7 @@
mStackScroller.setStatusBarState(state);
updateReportRejectedTouchVisibility();
updateDozing();
+ mNotificationShelf.setStatusBarState(state);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 567ab3b..227ebdf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -165,10 +165,6 @@
mHeader = view;
}
- public PhoneStatusBar getPhoneStatusBar() {
- return mStatusBar;
- }
-
public void destroy() {
mHandlerThread.quitSafely();
mTiles.values().forEach(tile -> tile.destroy());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 7d4927b..1dce8b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -3179,6 +3179,7 @@
}
updateNotificationAnimationStates();
updateChronometers();
+ requestChildrenUpdate();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index f5c60a0..89d4b00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -116,7 +116,8 @@
}
@Override
- protected void toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
+ protected boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
+ return false;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java b/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java
index f420921..5cfe677 100644
--- a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java
@@ -81,11 +81,23 @@
* @return Builder object post-modification.
*/
public LayoutInflaterBuilder replace(@NonNull Class from, @NonNull Class to) {
+ return replace(from.getName(), to);
+ }
+
+ /**
+ * Instructs the Builder to configure the LayoutInflater such that all instances
+ * of one {@link View} will be replaced with instances of another during inflation.
+ *
+ * @param tag Instances of this tag will be replaced during inflation.
+ * @param to Instances of this class will be inflated as replacements.
+ * @return Builder object post-modification.
+ */
+ public LayoutInflaterBuilder replace(@NonNull String tag, @NonNull Class to) {
assertIfAlreadyBuilt();
if (mReplaceMap == null) {
mReplaceMap = new ArrayMap<String, String>();
}
- mReplaceMap.put(from.getName(), to.getName());
+ mReplaceMap.put(tag, to.getName());
return this;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
index 4c25c62e..8acd6ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
@@ -14,37 +14,31 @@
package com.android.systemui.qs;
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
import android.content.Context;
-import android.content.res.Resources;
+import android.os.Handler;
import android.os.Looper;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.text.SpannableStringBuilder;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.util.LayoutInflaterBuilder;
+import com.android.systemui.utils.TestableImageView;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import static junit.framework.Assert.assertEquals;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
public class QSFooterTest extends SysuiTestCase {
@@ -53,28 +47,26 @@
private final String DEVICE_OWNER_PACKAGE = "TestDPC";
private final String VPN_PACKAGE = "TestVPN";
- private ViewGroup mRootView = mock(ViewGroup.class);
- private TextView mFooterText = mock(TextView.class);
- private ImageView mFooterIcon = mock(ImageView.class);
- private ImageView mFooterIcon2 = mock(ImageView.class);
+ private ViewGroup mRootView;
+ private TextView mFooterText;
+ private TestableImageView mFooterIcon;
+ private TestableImageView mFooterIcon2;
private QSFooter mFooter;
- private Resources mResources;
private SecurityController mSecurityController = mock(SecurityController.class);
@Before
public void setUp() {
- when(mRootView.findViewById(R.id.footer_text)).thenReturn(mFooterText);
- when(mRootView.findViewById(R.id.footer_icon)).thenReturn(mFooterIcon);
- when(mRootView.findViewById(R.id.footer_icon2)).thenReturn(mFooterIcon2);
- final LayoutInflater layoutInflater = mock(LayoutInflater.class);
- when(layoutInflater.inflate(eq(R.layout.quick_settings_footer), anyObject(), anyBoolean()))
- .thenReturn(mRootView);
- final Context context = mock(Context.class);
- when(context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).thenReturn(layoutInflater);
- mResources = mContext.getResources();
- when(context.getResources()).thenReturn(mResources);
- mFooter = new QSFooter(null, context);
- reset(mRootView);
+ mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE,
+ new LayoutInflaterBuilder(mContext)
+ .replace("ImageView", TestableImageView.class)
+ .build());
+ Handler h = new Handler(Looper.getMainLooper());
+ h.post(() -> mFooter = new QSFooter(null, mContext));
+ waitForIdleSync(h);
+ mRootView = (ViewGroup) mFooter.getView();
+ mFooterText = (TextView) mRootView.findViewById(R.id.footer_text);
+ mFooterIcon = (TestableImageView) mRootView.findViewById(R.id.footer_icon);
+ mFooterIcon2 = (TestableImageView) mRootView.findViewById(R.id.footer_icon2);
mFooter.setHostEnvironment(null, mSecurityController, Looper.getMainLooper());
}
@@ -86,8 +78,7 @@
mFooter.refreshState();
waitForIdleSync(mFooter.mHandler);
- verify(mRootView).setVisibility(View.GONE);
- verifyNoMoreInteractions(mRootView);
+ assertEquals(View.GONE, mRootView.getVisibility());
}
@Test
@@ -97,10 +88,8 @@
mFooter.refreshState();
waitForIdleSync(mFooter.mHandler);
- verify(mFooterText).setText(mResources.getString(R.string.do_disclosure_generic));
- verifyNoMoreInteractions(mFooterText);
- verify(mRootView).setVisibility(View.VISIBLE);
- verifyNoMoreInteractions(mRootView);
+ assertEquals(mContext.getString(R.string.do_disclosure_generic), mFooterText.getText());
+ assertEquals(View.VISIBLE, mRootView.getVisibility());
}
@Test
@@ -111,11 +100,9 @@
mFooter.refreshState();
waitForIdleSync(mFooter.mHandler);
- verify(mFooterText).setText(mResources.getString(R.string.do_disclosure_with_name,
- MANAGING_ORGANIZATION));
- verifyNoMoreInteractions(mFooterText);
- verify(mRootView).setVisibility(View.VISIBLE);
- verifyNoMoreInteractions(mRootView);
+ assertEquals(mContext.getString(R.string.do_disclosure_with_name, MANAGING_ORGANIZATION),
+ mFooterText.getText());
+ assertEquals(View.VISIBLE, mRootView.getVisibility());
}
@Test
@@ -126,9 +113,9 @@
mFooter.refreshState();
waitForIdleSync(mFooter.mHandler);
- verify(mFooterIcon).setVisibility(View.VISIBLE);
- verify(mFooterIcon).setImageResource(R.drawable.ic_qs_network_logging);
- verify(mFooterIcon2).setVisibility(View.INVISIBLE);
+ assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+ assertEquals(R.drawable.ic_qs_network_logging, mFooterIcon.getLastImageResource());
+ assertEquals(View.INVISIBLE, mFooterIcon2.getVisibility());
}
@Test
@@ -140,9 +127,10 @@
mFooter.refreshState();
waitForIdleSync(mFooter.mHandler);
- verify(mFooterIcon).setVisibility(View.VISIBLE);
- verify(mFooterIcon, never()).setImageResource(anyInt());
- verify(mFooterIcon2).setVisibility(View.INVISIBLE);
+ assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+ // -1 == never set.
+ assertEquals(-1, mFooterIcon.getLastImageResource());
+ assertEquals(View.INVISIBLE, mFooterIcon2.getVisibility());
}
@Test
@@ -154,10 +142,11 @@
mFooter.refreshState();
waitForIdleSync(mFooter.mHandler);
- verify(mFooterIcon).setVisibility(View.VISIBLE);
- verify(mFooterIcon, never()).setImageResource(anyInt());
- verify(mFooterIcon2).setVisibility(View.VISIBLE);
- verify(mFooterIcon2, never()).setImageResource(anyInt());
+ assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+ assertEquals(View.VISIBLE, mFooterIcon2.getVisibility());
+ // -1 == never set.
+ assertEquals(-1, mFooterIcon.getLastImageResource());
+ assertEquals(-1, mFooterIcon2.getLastImageResource());
}
@Test
@@ -211,20 +200,15 @@
private CharSequence getExpectedMessage(boolean hasDeviceOwnerOrganization, boolean hasVPN) {
final SpannableStringBuilder message = new SpannableStringBuilder();
message.append(hasDeviceOwnerOrganization ?
- mResources.getString(R.string.monitoring_description_do_header_with_name,
+ mContext.getString(R.string.monitoring_description_do_header_with_name,
MANAGING_ORGANIZATION, DEVICE_OWNER_PACKAGE) :
- mResources.getString(R.string.monitoring_description_do_header_generic,
+ mContext.getString(R.string.monitoring_description_do_header_generic,
DEVICE_OWNER_PACKAGE));
message.append("\n\n");
- message.append(mResources.getString(R.string.monitoring_description_do_body));
- if (hasVPN) {
- message.append("\n\n");
- message.append(mResources.getString(R.string.monitoring_description_do_body_vpn,
- VPN_PACKAGE));
- }
- message.append(mResources.getString(
+ message.append(mContext.getString(R.string.monitoring_description_do_body));
+ message.append(mContext.getString(
R.string.monitoring_description_do_learn_more_separator));
- message.append(mResources.getString(R.string.monitoring_description_do_learn_more),
+ message.append(mContext.getString(R.string.monitoring_description_do_learn_more),
mFooter.new EnterprisePrivacySpan(), 0);
return message;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 350a95f..c0d5bbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -63,7 +63,7 @@
when(userSwitcher.getKeyguardMonitor()).thenReturn(keyguardMonitor);
when(userSwitcher.getUsers()).thenReturn(new ArrayList<>());
QSTileHost host = new QSTileHost(mContext,
- mock(PhoneStatusBar.class),
+ null,
getLeakChecker(BluetoothController.class),
getLeakChecker(LocationController.class),
getLeakChecker(RotationLockController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index 782a489..66ec7dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -15,8 +15,10 @@
*/
package com.android.systemui.qs.external;
+import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
@@ -26,9 +28,7 @@
import static org.mockito.Mockito.when;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.content.pm.ServiceInfo;
import android.net.Uri;
@@ -50,15 +50,12 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TileLifecycleManagerTest extends SysuiTestCase {
private static final int TEST_FAIL_TIMEOUT = 5000;
- private final Context mMockContext = Mockito.mock(Context.class);
private final PackageManagerAdapter mMockPackageManagerAdapter =
Mockito.mock(PackageManagerAdapter.class);
private final IQSTileService.Stub mMockTileService = Mockito.mock(IQSTileService.Stub.class);
@@ -77,19 +74,7 @@
// Stub.asInterface will just return itself.
when(mMockTileService.queryLocalInterface(anyString())).thenReturn(mMockTileService);
- // Default behavior for bind is success and connects as mMockTileService.
- when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any()))
- .thenAnswer(
- new Answer<Boolean>() {
- @Override
- public Boolean answer(InvocationOnMock invocation) {
- ServiceConnection connection =
- (ServiceConnection) invocation.getArguments()[1];
- connection.onServiceConnected(
- mTileServiceComponentName, mMockTileService);
- return true;
- }
- });
+ mContext.addMockService(mTileServiceComponentName, mMockTileService);
mTileServiceIntent = new Intent().setComponent(mTileServiceComponentName);
@@ -97,7 +82,7 @@
mThread = new HandlerThread("TestThread");
mThread.start();
mHandler = new Handler(mThread.getLooper());
- mStateManager = new TileLifecycleManager(mHandler, mMockContext,
+ mStateManager = new TileLifecycleManager(mHandler, mContext,
Mockito.mock(IQSService.class), new Tile(),
mTileServiceIntent,
mUser,
@@ -126,11 +111,7 @@
}
private void verifyBind(int times) {
- verify(mMockContext, times(times)).bindServiceAsUser(
- mTileServiceIntent,
- mStateManager,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
- mUser);
+ assertEquals(times > 0, mContext.isBound(mTileServiceComponentName));
}
@Test
@@ -143,7 +124,7 @@
public void testUnbind() {
mStateManager.setBindService(true);
mStateManager.setBindService(false);
- verify(mMockContext).unbindService(mStateManager);
+ assertFalse(mContext.isBound(mTileServiceComponentName));
}
@Test
@@ -203,7 +184,7 @@
verifyBind(1);
mStateManager.setBindService(false);
- verify(mMockContext).unbindService(mStateManager);
+ assertFalse(mContext.isBound(mTileServiceComponentName));
verify(mMockTileService, never()).onStartListening();
}
@@ -217,7 +198,7 @@
verifyBind(1);
mStateManager.setBindService(false);
- verify(mMockContext).unbindService(mStateManager);
+ assertFalse(mContext.isBound(mTileServiceComponentName));
verify(mMockTileService, never()).onClick(null);
}
@@ -233,7 +214,7 @@
// Package is re-enabled.
setPackageEnabled(true);
mStateManager.onReceive(
- mMockContext,
+ mContext,
new Intent(
Intent.ACTION_PACKAGE_CHANGED,
Uri.fromParts(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 639c8da..7335af3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -16,15 +16,18 @@
package com.android.systemui.statusbar;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
-import android.content.ContentResolver;
import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Looper;
import android.support.test.runner.AndroidJUnit4;
-import android.telephony.SubscriptionManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.View;
import android.view.ViewGroup;
@@ -37,22 +40,15 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KeyguardIndicationControllerTest extends SysuiTestCase {
private final String ORGANIZATION_NAME = "organization";
- private final String DISCLOSURE_WITH_ORGANIZATION_NAME = "managed by organization";
- private Context mContext = mock(Context.class);
+ private String mDisclosureWithOrganization;
+
private DevicePolicyManager mDevicePolicyManager = mock(DevicePolicyManager.class);
private ViewGroup mIndicationArea = mock(ViewGroup.class);
private KeyguardIndicationTextView mDisclosure = mock(KeyguardIndicationTextView.class);
@@ -61,19 +57,11 @@
@Before
public void setUp() throws Exception {
- final Resources resources = mock(Resources.class);
- when(mContext.getResources()).thenReturn(resources);
- when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
- when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
- when(mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)).thenReturn(
- mock(SubscriptionManager.class));
- when(mContext.getSystemService(Context.TRUST_SERVICE)).thenReturn(
- mock(TrustManager.class));
- when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
- mDevicePolicyManager);
-
- when(resources.getString(R.string.do_disclosure_with_name, ORGANIZATION_NAME))
- .thenReturn(DISCLOSURE_WITH_ORGANIZATION_NAME);
+ mContext.addMockSystemService(Context.DEVICE_POLICY_SERVICE, mDevicePolicyManager);
+ mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class));
+ mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class));
+ mDisclosureWithOrganization = mContext.getString(R.string.do_disclosure_with_name,
+ ORGANIZATION_NAME);
when(mIndicationArea.findViewById(R.id.keyguard_indication_enterprise_disclosure))
.thenReturn(mDisclosure);
@@ -113,7 +101,7 @@
createController();
verify(mDisclosure).setVisibility(View.VISIBLE);
- verify(mDisclosure).switchIndication(DISCLOSURE_WITH_ORGANIZATION_NAME);
+ verify(mDisclosure).switchIndication(mDisclosureWithOrganization);
verifyNoMoreInteractions(mDisclosure);
}
@@ -140,7 +128,7 @@
monitor.onKeyguardVisibilityChanged(true);
verify(mDisclosure).setVisibility(View.VISIBLE);
- verify(mDisclosure).switchIndication(DISCLOSURE_WITH_ORGANIZATION_NAME);
+ verify(mDisclosure).switchIndication(mDisclosureWithOrganization);
verifyNoMoreInteractions(mDisclosure);
reset(mDisclosure);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index 9a697ee..87c4c66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -16,26 +16,24 @@
package com.android.systemui.statusbar.policy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
import android.app.admin.DevicePolicyManager;
import android.content.Context;
-import android.content.pm.UserInfo;
import android.net.ConnectivityManager;
-import android.os.UserManager;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.systemui.SysuiTestCase;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -45,16 +43,9 @@
@Before
public void setUp() throws Exception {
- final Context context = mock(Context.class);
- when(context.getSystemService(Context.DEVICE_POLICY_SERVICE))
- .thenReturn(mDevicePolicyManager);
- when(context.getSystemService(Context.CONNECTIVITY_SERVICE))
- .thenReturn(mock(ConnectivityManager.class));
- final UserManager userManager = mock(UserManager.class);
- when(userManager.getUserInfo(anyInt())).thenReturn(mock(UserInfo.class));
- when(context.getSystemService(Context.USER_SERVICE))
- .thenReturn(userManager);
- mSecurityController = new SecurityControllerImpl(context);
+ mContext.addMockSystemService(Context.DEVICE_POLICY_SERVICE, mDevicePolicyManager);
+ mContext.addMockSystemService(Context.CONNECTIVITY_SERVICE, mock(ConnectivityManager.class));
+ mSecurityController = new SecurityControllerImpl(mContext);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
index bf73416..a952806 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
@@ -16,15 +16,19 @@
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks;
+import android.content.ComponentName;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.content.res.Resources;
import android.os.Handler;
+import android.os.IBinder;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.ArrayMap;
import com.android.systemui.utils.leaks.Tracker;
import com.android.systemui.SysuiTestCase;
@@ -34,6 +38,10 @@
private final FakeContentResolver mFakeContentResolver;
private final FakeSettingsProvider mSettingsProvider;
+ private ArrayMap<String, Object> mMockSystemServices;
+ private ArrayMap<ComponentName, IBinder> mMockServices;
+ private ArrayMap<ServiceConnection, ComponentName> mActiveServices;
+
private Tracker mReceiver;
private Tracker mService;
private Tracker mComponent;
@@ -51,6 +59,33 @@
mComponent = test.getTracker("component");
}
+ @Override
+ public Resources getResources() {
+ return super.getResources();
+ }
+
+ public void addMockSystemService(String name, Object service) {
+ mMockSystemServices = lazyInit(mMockSystemServices);
+ mMockSystemServices.put(name, service);
+ }
+
+ public void addMockService(ComponentName component, IBinder service) {
+ mMockServices = lazyInit(mMockServices);
+ mMockServices.put(component, service);
+ }
+
+ private <T, V> ArrayMap<T, V> lazyInit(ArrayMap<T, V> services) {
+ return services != null ? services : new ArrayMap<T, V>();
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (mMockSystemServices != null && mMockSystemServices.containsKey(name)) {
+ return mMockSystemServices.get(name);
+ }
+ return super.getSystemService(name);
+ }
+
public FakeSettingsProvider getSettingsProvider() {
return mSettingsProvider;
}
@@ -96,6 +131,7 @@
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
+ if (checkMocks(service.getComponent(), conn)) return true;
return super.bindService(service, conn, flags);
}
@@ -103,6 +139,7 @@
public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
Handler handler, UserHandle user) {
if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
+ if (checkMocks(service.getComponent(), conn)) return true;
return super.bindServiceAsUser(service, conn, flags, handler, user);
}
@@ -110,15 +147,35 @@
public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
UserHandle user) {
if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
+ if (checkMocks(service.getComponent(), conn)) return true;
return super.bindServiceAsUser(service, conn, flags, user);
}
+ private boolean checkMocks(ComponentName component, ServiceConnection conn) {
+ if (mMockServices != null && component != null && mMockServices.containsKey(component)) {
+ mActiveServices = lazyInit(mActiveServices);
+ mActiveServices.put(conn, component);
+ conn.onServiceConnected(component, mMockServices.get(component));
+ return true;
+ }
+ return false;
+ }
+
@Override
public void unbindService(ServiceConnection conn) {
if (mService != null) mService.getLeakInfo(conn).clearAllocations();
+ if (mActiveServices != null && mActiveServices.containsKey(conn)) {
+ conn.onServiceDisconnected(mActiveServices.get(conn));
+ mActiveServices.remove(conn);
+ return;
+ }
super.unbindService(conn);
}
+ public boolean isBound(ComponentName component) {
+ return mActiveServices != null && mActiveServices.containsValue(component);
+ }
+
@Override
public void registerComponentCallbacks(ComponentCallbacks callback) {
if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java
new file mode 100644
index 0000000..b131460
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java
@@ -0,0 +1,40 @@
+/*
+ * 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.utils;
+
+import android.annotation.DrawableRes;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+public class TestableImageView extends ImageView {
+
+ private int mLastResId = -1;
+
+ public TestableImageView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setImageResource(@DrawableRes int resId) {
+ mLastResId = resId;
+ super.setImageResource(resId);
+ }
+
+ public int getLastImageResource() {
+ return mLastResId;
+ }
+}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index d3110a4..1af400e 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -339,13 +339,15 @@
/**
* Save the Bluetooth on/off state
- *
*/
private void persistBluetoothSetting(int value) {
if (DBG) Slog.d(TAG, "Persisting Bluetooth Setting: " + value);
+ // waive WRITE_SECURE_SETTINGS permission check
+ long callingIdentity = Binder.clearCallingIdentity();
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.BLUETOOTH_ON,
value);
+ Binder.restoreCallingIdentity(callingIdentity);
}
/**
@@ -610,20 +612,26 @@
}
/**
- * Action taken when GattService is turned off
+ * Action taken when GattService is turned on
*/
private void onBluetoothGattServiceUp() {
if (DBG) Slog.d(TAG,"BluetoothGatt Service is Up");
try {
mBluetoothLock.readLock().lock();
- if (isBleAppPresent() == false && mBluetooth != null
- && mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) {
+ if (mBluetooth == null) {
+ if (DBG) Slog.w(TAG, "onBluetoothServiceUp: mBluetooth is null!");
+ return;
+ }
+ int st = mBluetooth.getState();
+ if (st != BluetoothAdapter.STATE_BLE_ON) {
+ if (DBG) Slog.v(TAG, "onBluetoothServiceUp: state isn't BLE_ON: " +
+ BluetoothAdapter.nameForState(st));
+ return;
+ }
+ if (isBluetoothPersistedStateOnBluetooth() || !isBleAppPresent()) {
+ // This triggers transition to STATE_ON
mBluetooth.onLeServiceUp();
-
- // waive WRITE_SECURE_SETTINGS permission check
- long callingIdentity = Binder.clearCallingIdentity();
persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
- Binder.restoreCallingIdentity(callingIdentity);
}
} catch (RemoteException e) {
Slog.e(TAG,"Unable to call onServiceUp", e);
@@ -763,10 +771,7 @@
synchronized(mReceiver) {
if (persist) {
- // waive WRITE_SECURE_SETTINGS permission check
- long callingIdentity = Binder.clearCallingIdentity();
persistBluetoothSetting(BLUETOOTH_OFF);
- Binder.restoreCallingIdentity(callingIdentity);
}
mEnableExternal = false;
sendDisableMsg();
@@ -1290,8 +1295,9 @@
if (mBluetooth != null) {
int state = mBluetooth.getState();
if (state == BluetoothAdapter.STATE_BLE_ON) {
- Slog.w(TAG, "BT is in BLE_ON State");
+ Slog.w(TAG, "BT Enable in BLE_ON State, going to ON");
mBluetooth.onLeServiceUp();
+ persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
break;
}
}
diff --git a/services/core/java/com/android/server/DiskStatsService.java b/services/core/java/com/android/server/DiskStatsService.java
index 8ca675a..dd95f67 100644
--- a/services/core/java/com/android/server/DiskStatsService.java
+++ b/services/core/java/com/android/server/DiskStatsService.java
@@ -22,6 +22,15 @@
import android.os.StatFs;
import android.os.SystemClock;
import android.os.storage.StorageManager;
+import android.util.Log;
+
+import com.android.server.storage.DiskStatsFileLogger;
+import com.android.server.storage.DiskStatsLoggingService;
+
+import libcore.io.IoUtils;
+
+import org.json.JSONException;
+import org.json.JSONObject;
import java.io.File;
import java.io.FileDescriptor;
@@ -35,11 +44,13 @@
*/
public class DiskStatsService extends Binder {
private static final String TAG = "DiskStatsService";
+ private static final String DISKSTATS_DUMP_FILE = "/data/system/diskstats_cache.json";
private final Context mContext;
public DiskStatsService(Context context) {
mContext = context;
+ DiskStatsLoggingService.schedule(context);
}
@Override
@@ -84,6 +95,10 @@
pw.println("File-based Encryption: true");
}
+ if (isCheckin(args)) {
+ reportCachedValues(pw);
+ }
+
// TODO: Read /proc/yaffs and report interesting values;
// add configurable (through args) performance test parameters.
}
@@ -114,4 +129,45 @@
return;
}
}
+
+ private boolean isCheckin(String[] args) {
+ for (String opt : args) {
+ if ("--checkin".equals(opt)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void reportCachedValues(PrintWriter pw) {
+ try {
+ String jsonString = IoUtils.readFileAsString(DISKSTATS_DUMP_FILE);
+ JSONObject json = new JSONObject(jsonString);
+ pw.print("App Size: ");
+ pw.println(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY));
+ pw.print("App Cache Size: ");
+ pw.println(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
+ pw.print("Photos Size: ");
+ pw.println(json.getLong(DiskStatsFileLogger.PHOTOS_KEY));
+ pw.print("Videos Size: ");
+ pw.println(json.getLong(DiskStatsFileLogger.VIDEOS_KEY));
+ pw.print("Audio Size: ");
+ pw.println(json.getLong(DiskStatsFileLogger.AUDIO_KEY));
+ pw.print("Downloads Size: ");
+ pw.println(json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY));
+ pw.print("System Size: ");
+ pw.println(json.getLong(DiskStatsFileLogger.SYSTEM_KEY));
+ pw.print("Other Size: ");
+ pw.println(json.getLong(DiskStatsFileLogger.MISC_KEY));
+ pw.print("Package Names: ");
+ pw.println(json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY));
+ pw.print("App Sizes: ");
+ pw.println(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY));
+ pw.print("Cache Sizes: ");
+ pw.println(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY));
+ } catch (IOException | JSONException e) {
+ Log.w(TAG, "exception reading diskstats cache file", e);
+ }
+ }
+
}
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 1398530..4d6ffe6 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -16,12 +16,14 @@
package com.android.server;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.PasswordMetrics;
import android.app.backup.BackupManager;
import android.app.trust.IStrongAuthTracker;
import android.app.trust.TrustManager;
@@ -931,6 +933,7 @@
synchronized (mSeparateChallengeLock) {
setLockPatternInternal(pattern, savedCredential, userId);
setSeparateProfileChallengeEnabled(userId, true, null);
+ notifyPasswordChanged(userId);
}
}
@@ -945,6 +948,7 @@
setKeystorePassword(null, userId);
fixateNewestUserKeyAuth(userId);
onUserLockChanged(userId);
+ notifyActivePasswordMetricsAvailable(null, userId);
return;
}
@@ -994,6 +998,7 @@
synchronized (mSeparateChallengeLock) {
setLockPasswordInternal(password, savedCredential, userId);
setSeparateProfileChallengeEnabled(userId, true, null);
+ notifyPasswordChanged(userId);
}
}
@@ -1007,6 +1012,7 @@
setKeystorePassword(null, userId);
fixateNewestUserKeyAuth(userId);
onUserLockChanged(userId);
+ notifyActivePasswordMetricsAvailable(null, userId);
return;
}
@@ -1420,6 +1426,7 @@
// migrate credential to GateKeeper
credentialUtil.setCredential(credential, null, userId);
if (!hasChallenge) {
+ notifyActivePasswordMetricsAvailable(credential, userId);
return VerifyCredentialResponse.OK;
}
// Fall through to get the auth token. Technically this should never happen,
@@ -1459,6 +1466,7 @@
if (progressCallback != null) {
progressCallback.onCredentialVerified();
}
+ notifyActivePasswordMetricsAvailable(credential, userId);
unlockKeystore(credential, userId);
Slog.i(TAG, "Unlocking user " + userId +
@@ -1482,6 +1490,36 @@
return response;
}
+ private void notifyActivePasswordMetricsAvailable(String password, @UserIdInt int userId) {
+ final PasswordMetrics metrics;
+ if (password == null) {
+ metrics = new PasswordMetrics();
+ } else {
+ metrics = PasswordMetrics.computeForPassword(password);
+ metrics.quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(userId);
+ }
+
+ // Asynchronous to avoid dead lock
+ mHandler.post(() -> {
+ DevicePolicyManager dpm = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ dpm.setActivePasswordState(metrics, userId);
+ });
+ }
+
+ /**
+ * Call after {@link #notifyActivePasswordMetricsAvailable} so metrics are updated before
+ * reporting the password changed.
+ */
+ private void notifyPasswordChanged(@UserIdInt int userId) {
+ // Same handler as notifyActivePasswordMetricsAvailable to ensure correct ordering
+ mHandler.post(() -> {
+ DevicePolicyManager dpm = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ dpm.reportPasswordChanged(userId);
+ });
+ }
+
@Override
public boolean checkVoldPassword(int userId) throws RemoteException {
if (!mFirstCallToVold) {
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index f712f12..c64aa8e 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -40,7 +40,7 @@
import android.net.RecommendationResult;
import android.net.ScoredNetwork;
import android.net.Uri;
-import android.net.wifi.WifiConfiguration;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
@@ -319,47 +319,54 @@
" is not the active scorer.");
}
- // Separate networks by type.
- Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
- for (ScoredNetwork network : networks) {
- List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
- if (networkList == null) {
- networkList = new ArrayList<>();
- networksByType.put(network.networkKey.type, networkList);
- }
- networkList.add(network);
- }
-
- // Pass the scores of each type down to the appropriate network scorer.
- for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
- final RemoteCallbackList<INetworkScoreCache> callbackList;
- final boolean isEmpty;
- synchronized (mScoreCaches) {
- callbackList = mScoreCaches.get(entry.getKey());
- isEmpty = callbackList == null || callbackList.getRegisteredCallbackCount() == 0;
- }
- if (isEmpty) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Separate networks by type.
+ Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
+ for (ScoredNetwork network : networks) {
+ List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
+ if (networkList == null) {
+ networkList = new ArrayList<>();
+ networksByType.put(network.networkKey.type, networkList);
}
- continue;
+ networkList.add(network);
}
- sendCallback(new Consumer<INetworkScoreCache>() {
- @Override
- public void accept(INetworkScoreCache networkScoreCache) {
- try {
- networkScoreCache.updateScores(entry.getValue());
- } catch (RemoteException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
+ // Pass the scores of each type down to the appropriate network scorer.
+ for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
+ final RemoteCallbackList<INetworkScoreCache> callbackList;
+ final boolean isEmpty;
+ synchronized (mScoreCaches) {
+ callbackList = mScoreCaches.get(entry.getKey());
+ isEmpty = callbackList == null
+ || callbackList.getRegisteredCallbackCount() == 0;
+ }
+ if (isEmpty) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "No scorer registered for type " + entry.getKey()
+ + ", discarding");
+ }
+ continue;
+ }
+
+ sendCallback(new Consumer<INetworkScoreCache>() {
+ @Override
+ public void accept(INetworkScoreCache networkScoreCache) {
+ try {
+ networkScoreCache.updateScores(entry.getValue());
+ } catch (RemoteException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
+ }
}
}
- }
- }, Collections.singleton(callbackList));
- }
+ }, Collections.singleton(callbackList));
+ }
- return true;
+ return true;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
@@ -369,8 +376,13 @@
if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) ||
mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
PackageManager.PERMISSION_GRANTED) {
- clearInternal();
- return true;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ clearInternal();
+ return true;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
} else {
throw new SecurityException(
"Caller is neither the active scorer nor the scorer manager.");
@@ -428,35 +440,46 @@
INetworkScoreCache scoreCache,
int filterType) {
mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
- synchronized (mScoreCaches) {
- RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
- if (callbackList == null) {
- callbackList = new RemoteCallbackList<>();
- mScoreCaches.put(networkType, callbackList);
- }
- if (!callbackList.register(scoreCache, filterType)) {
- if (callbackList.getRegisteredCallbackCount() == 0) {
- mScoreCaches.remove(networkType);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mScoreCaches) {
+ RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
+ if (callbackList == null) {
+ callbackList = new RemoteCallbackList<>();
+ mScoreCaches.put(networkType, callbackList);
}
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
+ if (!callbackList.register(scoreCache, filterType)) {
+ if (callbackList.getRegisteredCallbackCount() == 0) {
+ mScoreCaches.remove(networkType);
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
+ }
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
@Override
public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
- synchronized (mScoreCaches) {
- RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
- if (callbackList == null || !callbackList.unregister(scoreCache)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Unable to unregister NetworkScoreCache for type " + networkType);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mScoreCaches) {
+ RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
+ if (callbackList == null || !callbackList.unregister(scoreCache)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Unable to unregister NetworkScoreCache for type "
+ + networkType);
+ }
+ } else if (callbackList.getRegisteredCallbackCount() == 0) {
+ mScoreCaches.remove(networkType);
}
- } else if (callbackList.getRegisteredCallbackCount() == 0) {
- mScoreCaches.remove(networkType);
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
@@ -464,43 +487,53 @@
public RecommendationResult requestRecommendation(RecommendationRequest request) {
mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
throwIfCalledOnMainThread();
- final INetworkRecommendationProvider provider = getRecommendationProvider();
- if (provider != null) {
- try {
- return mRequestRecommendationCaller.getRecommendationResult(provider, request);
- } catch (RemoteException | TimeoutException e) {
- Log.w(TAG, "Failed to request a recommendation.", e);
- // TODO(jjoslin): 12/15/16 - Keep track of failures.
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final INetworkRecommendationProvider provider = getRecommendationProvider();
+ if (provider != null) {
+ try {
+ return mRequestRecommendationCaller.getRecommendationResult(provider, request);
+ } catch (RemoteException | TimeoutException e) {
+ Log.w(TAG, "Failed to request a recommendation.", e);
+ // TODO(jjoslin): 12/15/16 - Keep track of failures.
+ }
}
- }
- if (DBG) {
- Log.d(TAG, "Returning the default network recommendation.");
- }
+ if (DBG) {
+ Log.d(TAG, "Returning the default network recommendation.");
+ }
- WifiConfiguration selectedConfig = null;
- if (request != null) {
- selectedConfig = request.getCurrentSelectedConfig();
+ if (request != null && request.getCurrentSelectedConfig() != null) {
+ return RecommendationResult.createConnectRecommendation(
+ request.getCurrentSelectedConfig());
+ }
+ return RecommendationResult.createDoNotConnectRecommendation();
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- return new RecommendationResult(selectedConfig);
}
@Override
public boolean requestScores(NetworkKey[] networks) {
mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
- final INetworkRecommendationProvider provider = getRecommendationProvider();
- if (provider != null) {
- try {
- provider.requestScores(networks);
- // TODO(jjoslin): 12/15/16 - Consider pushing null scores into the cache to prevent
- // repeated requests for the same scores.
- return true;
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to request scores.", e);
- // TODO(jjoslin): 12/15/16 - Keep track of failures.
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final INetworkRecommendationProvider provider = getRecommendationProvider();
+ if (provider != null) {
+ try {
+ provider.requestScores(networks);
+ // TODO(jjoslin): 12/15/16 - Consider pushing null scores into the cache to
+ // prevent repeated requests for the same scores.
+ return true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to request scores.", e);
+ // TODO(jjoslin): 12/15/16 - Keep track of failures.
+ }
}
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- return false;
}
@Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index eae4905..6b0d508 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18640,7 +18640,14 @@
}
if (doNext) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ String.format("ProcessBroadcast from %s (%s) %s", r.callerPackage,
+ r.callerApp == null ? "caller unknown" : r.callerApp.toShortString(),
+ r.intent == null ? "" : r.intent.toString()));
+ }
r.queue.processNextBroadcast(false);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
trimApplications();
} finally {
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 5e98859..9ffe2b7 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -433,6 +433,8 @@
}));
intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL,
mLastPortalProbeResult.detectUrl);
+ intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT,
+ getCaptivePortalUserAgent(mContext));
intent.setFlags(
Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 63b5250..c0550c6 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -142,8 +142,9 @@
if (DBG) {
Slog.d(TAG, String.format(
- "showNotification tag=%s event=%s transport=%s extraInfo=%d highPrioriy=%s",
- tag, nameOf(eventId), getTransportName(transportType), extraInfo, highPriority));
+ "showNotification tag=%s event=%s transport=%s extraInfo=%s highPrioriy=%s",
+ tag, nameOf(eventId), getTransportName(transportType), extraInfo,
+ highPriority));
}
Resources r = Resources.getSystem();
@@ -227,13 +228,14 @@
}
final int eventId = mNotificationTypeMap.get(id);
if (DBG) {
- Slog.d(TAG, String.format("clearing notification tag=%s event=", tag, nameOf(eventId)));
+ Slog.d(TAG, String.format("clearing notification tag=%s event=%s", tag,
+ nameOf(eventId)));
}
try {
mNotificationManager.cancelAsUser(tag, eventId, UserHandle.ALL);
} catch (NullPointerException npe) {
Slog.d(TAG, String.format(
- "failed to clear notification tag=%s event=", tag, nameOf(eventId)), npe);
+ "failed to clear notification tag=%s event=%s", tag, nameOf(eventId)), npe);
}
mNotificationTypeMap.delete(id);
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index a03c4aa..dbd719b 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -25,6 +25,7 @@
import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -136,15 +137,9 @@
private boolean isPreChannelsNotification() {
try {
if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(getChannel().getId())) {
- final boolean isSystemNotification =
- NotificationManagerService.isUidSystem(sbn.getUid())
- || ("android".equals(sbn.getPackageName()));
- if (isSystemNotification) {
- return false;
- }
- final ApplicationInfo applicationInfo =
+ final ApplicationInfo applicationInfo =
mContext.getPackageManager().getApplicationInfoAsUser(sbn.getPackageName(),
- 0, sbn.getUserId());
+ 0, UserHandle.getUserId(sbn.getUid()));
if (applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
return true;
}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 95718de..98d4c69 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -784,11 +784,6 @@
}
}
- private static boolean isUidSystem(int uid) {
- final int appid = UserHandle.getAppId(uid);
- return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
- }
-
private static class Record {
static int UNKNOWN_UID = UserHandle.USER_NULL;
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 3193974..203f841 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -105,11 +105,11 @@
}
}
- public void createAppData(String uuid, String packageName, int userId, int flags, int appId,
+ public long createAppData(String uuid, String packageName, int userId, int flags, int appId,
String seInfo, int targetSdkVersion) throws InstallerException {
- if (!checkBeforeRemote()) return;
+ if (!checkBeforeRemote()) return -1;
try {
- mInstalld.createAppData(uuid, packageName, userId, flags, appId, seInfo,
+ return mInstalld.createAppData(uuid, packageName, userId, flags, appId, seInfo,
targetSdkVersion);
} catch (Exception e) {
throw InstallerException.from(e);
@@ -182,16 +182,6 @@
}
}
- public long getAppDataInode(String uuid, String packageName, int userId, int flags)
- throws InstallerException {
- if (!checkBeforeRemote()) return -1;
- try {
- return mInstalld.getAppDataInode(uuid, packageName, userId, flags);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
int dexoptNeeded, @Nullable String outputPath, int dexFlags,
String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries)
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 34c1470..52528c0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7639,6 +7639,11 @@
}
}
+ @Override
+ public void notifyDexLoad(String loadingPackageName, List<String> dexPaths, String loaderIsa) {
+ // TODO(calin): b/32871170
+ }
+
// TODO: this is not used nor needed. Delete it.
@Override
public boolean performDexOptIfNeeded(String packageName) {
@@ -15463,6 +15468,14 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
+ // Ephemeral apps must have target SDK >= O.
+ // TODO: Update conditional and error message when O gets locked down
+ if (ephemeral && pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
+ res.setError(PackageManager.INSTALL_FAILED_EPHEMERAL_INVALID,
+ "Ephemeral apps must have target SDK version of at least O");
+ return;
+ }
+
// If we are installing a clustered package add results for the children
if (pkg.childPackages != null) {
synchronized (mPackages) {
@@ -20454,8 +20467,9 @@
Preconditions.checkNotNull(app.seinfo);
+ long ceDataInode = -1;
try {
- mInstaller.createAppData(volumeUuid, packageName, userId, flags,
+ ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,
appId, app.seinfo, app.targetSdkVersion);
} catch (InstallerException e) {
if (app.isSystemApp()) {
@@ -20463,7 +20477,7 @@
+ ", but trying to recover: " + e);
destroyAppDataLeafLIF(pkg, userId, flags);
try {
- mInstaller.createAppData(volumeUuid, packageName, userId, flags,
+ ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,
appId, app.seinfo, app.targetSdkVersion);
logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
} catch (InstallerException e2) {
@@ -20474,21 +20488,13 @@
}
}
- if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
- try {
- // CE storage is unlocked right now, so read out the inode and
- // remember for use later when it's locked
- // TODO: mark this structure as dirty so we persist it!
- final long ceDataInode = mInstaller.getAppDataInode(volumeUuid, packageName, userId,
- StorageManager.FLAG_STORAGE_CE);
- synchronized (mPackages) {
- final PackageSetting ps = mSettings.mPackages.get(packageName);
- if (ps != null) {
- ps.setCeDataInode(ceDataInode, userId);
- }
+ if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
+ // TODO: mark this structure as dirty so we persist it!
+ synchronized (mPackages) {
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps != null) {
+ ps.setCeDataInode(ceDataInode, userId);
}
- } catch (InstallerException e) {
- Slog.e(TAG, "Failed to find inode for " + packageName + ": " + e);
}
}
@@ -21610,6 +21616,10 @@
PackageManagerService.this.requestEphemeralResolutionPhaseTwo(
responseObj, origIntent, resolvedType, launchIntent, callingPackage, userId);
}
+
+ public String getSetupWizardPackageName() {
+ return mSetupWizardPackage;
+ }
}
@Override
diff --git a/services/core/java/com/android/server/storage/AppCollector.java b/services/core/java/com/android/server/storage/AppCollector.java
new file mode 100644
index 0000000..cf05e9f
--- /dev/null
+++ b/services/core/java/com/android/server/storage/AppCollector.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.storage;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageStats;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.os.storage.VolumeInfo;
+import android.util.Log;
+import com.android.internal.os.BackgroundThread;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * AppCollector asynchronously collects package sizes.
+ */
+public class AppCollector {
+ private static String TAG = "AppCollector";
+
+ private CompletableFuture<List<PackageStats>> mStats;
+ private final BackgroundHandler mBackgroundHandler;
+
+ /**
+ * Constrcuts a new AppCollector which runs on the provided volume.
+ * @param context Android context used to get
+ * @param volume Volume to check for apps.
+ */
+ public AppCollector(Context context, VolumeInfo volume) {
+ mBackgroundHandler = new BackgroundHandler(BackgroundThread.get().getLooper(),
+ volume,
+ context.getPackageManager(),
+ (UserManager) context.getSystemService(Context.USER_SERVICE));
+ }
+
+ /**
+ * Returns a list of package stats for the context and volume. Note that in a multi-user
+ * environment, this may return stats for the same package multiple times. These "duplicate"
+ * entries will have the package stats for the package for a given user, not the package in
+ * aggregate.
+ * @param timeoutMillis Milliseconds before timing out and returning early with null.
+ */
+ public List<PackageStats> getPackageStats(long timeoutMillis) {
+ synchronized(this) {
+ if (mStats == null) {
+ mStats = new CompletableFuture<>();
+ mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_START_LOADING_SIZES);
+ }
+ }
+
+ List<PackageStats> value = null;
+ try {
+ value = mStats.get(timeoutMillis, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "An exception occurred while getting app storage", e);
+ } catch (TimeoutException e) {
+ Log.e(TAG, "AppCollector timed out");
+ }
+ return value;
+ }
+
+ private class StatsObserver extends IPackageStatsObserver.Stub {
+ private AtomicInteger mCount;
+ private final ArrayList<PackageStats> mPackageStats;
+
+ public StatsObserver(int count) {
+ mCount = new AtomicInteger(count);
+ mPackageStats = new ArrayList<>(count);
+ }
+
+ @Override
+ public void onGetStatsCompleted(PackageStats packageStats, boolean succeeded)
+ throws RemoteException {
+ if (succeeded) {
+ mPackageStats.add(packageStats);
+ }
+
+ if (mCount.decrementAndGet() == 0) {
+ mStats.complete(mPackageStats);
+ }
+ }
+ }
+
+ private class BackgroundHandler extends Handler {
+ static final int MSG_START_LOADING_SIZES = 0;
+ private final VolumeInfo mVolume;
+ private final PackageManager mPm;
+ private final UserManager mUm;
+
+ BackgroundHandler(Looper looper, VolumeInfo volume, PackageManager pm, UserManager um) {
+ super(looper);
+ mVolume = volume;
+ mPm = pm;
+ mUm = um;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START_LOADING_SIZES: {
+ final List<ApplicationInfo> apps = mPm.getInstalledApplications(
+ PackageManager.GET_UNINSTALLED_PACKAGES
+ | PackageManager.GET_DISABLED_COMPONENTS);
+
+ final List<ApplicationInfo> volumeApps = new ArrayList<>();
+ for (ApplicationInfo app : apps) {
+ if (Objects.equals(app.volumeUuid, mVolume.getFsUuid())) {
+ volumeApps.add(app);
+ }
+ }
+
+ List<UserInfo> users = mUm.getUsers();
+ final int count = users.size() * volumeApps.size();
+ if (count == 0) {
+ mStats.complete(new ArrayList<>());
+ }
+
+ // Kick off the async package size query for all apps.
+ final StatsObserver observer = new StatsObserver(count);
+ for (UserInfo user : users) {
+ for (ApplicationInfo app : volumeApps) {
+ mPm.getPackageSizeInfoAsUser(app.packageName, user.id,
+ observer);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/storage/DiskStatsFileLogger.java b/services/core/java/com/android/server/storage/DiskStatsFileLogger.java
new file mode 100644
index 0000000..22299df
--- /dev/null
+++ b/services/core/java/com/android/server/storage/DiskStatsFileLogger.java
@@ -0,0 +1,166 @@
+/*
+ * 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/LICENSE2.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.storage;
+
+import android.content.pm.PackageStats;
+import android.os.Environment;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.server.storage.FileCollector.MeasurementResult;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * DiskStatsFileLogger logs collected storage information to a file in a JSON format.
+ *
+ * The following information is cached in the file:
+ * 1. Size of images on disk.
+ * 2. Size of videos on disk.
+ * 3. Size of audio on disk.
+ * 4. Size of the downloads folder.
+ * 5. System size.
+ * 6. Aggregate and individual app and app cache sizes.
+ * 7. How much storage couldn't be categorized in one of the above categories.
+ */
+public class DiskStatsFileLogger {
+ private static final String TAG = "DiskStatsLogger";
+
+ public static final String PHOTOS_KEY = "photosSize";
+ public static final String VIDEOS_KEY = "videosSize";
+ public static final String AUDIO_KEY = "audioSize";
+ public static final String DOWNLOADS_KEY = "downloadsSize";
+ public static final String SYSTEM_KEY = "systemSize";
+ public static final String MISC_KEY = "otherSize";
+ public static final String APP_SIZE_AGG_KEY = "appSize";
+ public static final String APP_CACHE_AGG_KEY = "cacheSize";
+ public static final String PACKAGE_NAMES_KEY = "packageNames";
+ public static final String APP_SIZES_KEY = "appSizes";
+ public static final String APP_CACHES_KEY = "cacheSizes";
+ public static final String LAST_QUERY_TIMESTAMP_KEY = "queryTime";
+
+ private MeasurementResult mResult;
+ private long mDownloadsSize;
+ private long mSystemSize;
+ private List<PackageStats> mPackageStats;
+
+ /**
+ * Constructs a DiskStatsFileLogger with calculated measurement results.
+ */
+ public DiskStatsFileLogger(MeasurementResult result, MeasurementResult downloadsResult,
+ List<PackageStats> stats, long systemSize) {
+ mResult = result;
+ mDownloadsSize = downloadsResult.totalAccountedSize();
+ mSystemSize = systemSize;
+ mPackageStats = stats;
+ }
+
+ /**
+ * Dumps the storage collection output to a file.
+ * @param file File to write the output into.
+ * @throws FileNotFoundException
+ */
+ public void dumpToFile(File file) throws FileNotFoundException {
+ PrintWriter pw = new PrintWriter(file);
+ JSONObject representation = getJsonRepresentation();
+ if (representation != null) {
+ pw.println(representation);
+ }
+ pw.close();
+ }
+
+ private JSONObject getJsonRepresentation() {
+ JSONObject json = new JSONObject();
+ try {
+ json.put(LAST_QUERY_TIMESTAMP_KEY, System.currentTimeMillis());
+ json.put(PHOTOS_KEY, mResult.imagesSize);
+ json.put(VIDEOS_KEY, mResult.videosSize);
+ json.put(AUDIO_KEY, mResult.audioSize);
+ json.put(DOWNLOADS_KEY, mDownloadsSize);
+ json.put(SYSTEM_KEY, mSystemSize);
+ json.put(MISC_KEY, mResult.miscSize);
+ addAppsToJson(json);
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ return null;
+ }
+
+ return json;
+ }
+
+ private void addAppsToJson(JSONObject json) throws JSONException {
+ JSONArray names = new JSONArray();
+ JSONArray appSizeList = new JSONArray();
+ JSONArray cacheSizeList = new JSONArray();
+
+ long appSizeSum = 0L;
+ long cacheSizeSum = 0L;
+ boolean isExternal = Environment.isExternalStorageEmulated();
+ for (Map.Entry<String, PackageStats> entry : mergePackagesAcrossUsers().entrySet()) {
+ PackageStats stat = entry.getValue();
+ long appSize = stat.codeSize + stat.dataSize;
+ long cacheSize = stat.cacheSize;
+ if (isExternal) {
+ appSize += stat.externalCodeSize + stat.externalDataSize;
+ cacheSize += stat.externalCacheSize;
+ }
+ appSizeSum += appSize;
+ cacheSizeSum += cacheSize;
+
+ names.put(stat.packageName);
+ appSizeList.put(appSize);
+ cacheSizeList.put(cacheSize);
+ }
+ json.put(PACKAGE_NAMES_KEY, names);
+ json.put(APP_SIZES_KEY, appSizeList);
+ json.put(APP_CACHES_KEY, cacheSizeList);
+ json.put(APP_SIZE_AGG_KEY, appSizeSum);
+ json.put(APP_CACHE_AGG_KEY, cacheSizeSum);
+ }
+
+ /**
+ * A given package may exist for multiple users with distinct sizes. This function merges
+ * the duplicated packages together and sums up their sizes to get the actual totals for the
+ * package.
+ * @return A mapping of package name to merged package stats.
+ */
+ private ArrayMap<String, PackageStats> mergePackagesAcrossUsers() {
+ ArrayMap<String, PackageStats> packageMap = new ArrayMap<>();
+ for (PackageStats stat : mPackageStats) {
+ PackageStats existingStats = packageMap.get(stat.packageName);
+ if (existingStats != null) {
+ existingStats.cacheSize += stat.cacheSize;
+ existingStats.codeSize += stat.codeSize;
+ existingStats.dataSize += stat.dataSize;
+ existingStats.externalCacheSize += stat.externalCacheSize;
+ existingStats.externalCodeSize += stat.externalCodeSize;
+ existingStats.externalDataSize += stat.externalDataSize;
+ } else {
+ packageMap.put(stat.packageName, new PackageStats(stat));
+ }
+ }
+ return packageMap;
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/storage/DiskStatsLoggingService.java b/services/core/java/com/android/server/storage/DiskStatsLoggingService.java
new file mode 100644
index 0000000..4a86175
--- /dev/null
+++ b/services/core/java/com/android/server/storage/DiskStatsLoggingService.java
@@ -0,0 +1,170 @@
+/*
+ * 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/LICENSE2.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.storage;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageStats;
+import android.os.AsyncTask;
+import android.os.BatteryManager;
+import android.os.Environment;
+import android.os.Environment.UserEnvironment;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.storage.FileCollector.MeasurementResult;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * DiskStatsLoggingService is a JobService which collects storage categorization information and
+ * app size information on a roughly daily cadence.
+ */
+public class DiskStatsLoggingService extends JobService {
+ private static final String TAG = "DiskStatsLogService";
+ public static final String DUMPSYS_CACHE_PATH = "/data/system/diskstats_cache.json";
+ private static final int JOB_DISKSTATS_LOGGING = 0x4449534b; // DISK
+ private static ComponentName sDiskStatsLoggingService = new ComponentName(
+ "android",
+ DiskStatsLoggingService.class.getName());
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ // We need to check the preconditions again because they may not be enforced for
+ // subsequent runs.
+ if (!isCharging(this)) {
+ jobFinished(params, true);
+ return false;
+ }
+
+ final int userId = UserHandle.myUserId();
+ UserEnvironment environment = new UserEnvironment(userId);
+ AppCollector collector = new AppCollector(this,
+ getPackageManager().getPrimaryStorageCurrentVolume());
+ LogRunnable task = new LogRunnable();
+ task.setRootDirectory(environment.getExternalStorageDirectory());
+ task.setDownloadsDirectory(
+ environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));
+ task.setSystemSize(FileCollector.getSystemSize(this));
+ task.setLogOutputFile(new File(DUMPSYS_CACHE_PATH));
+ task.setAppCollector(collector);
+ task.setJobService(this, params);
+ AsyncTask.execute(task);
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ // TODO: Try to stop being handled.
+ return false;
+ }
+
+ /**
+ * Schedules a DiskStats collection task. This task only runs on device idle while charging
+ * once every 24 hours.
+ * @param context Context to use to get a job scheduler.
+ */
+ public static void schedule(Context context) {
+ JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+
+ js.schedule(new JobInfo.Builder(JOB_DISKSTATS_LOGGING, sDiskStatsLoggingService)
+ .setRequiresDeviceIdle(true)
+ .setRequiresCharging(true)
+ .setPeriodic(TimeUnit.DAYS.toMillis(1))
+ .build());
+ }
+
+ private static boolean isCharging(Context context) {
+ BatteryManager batteryManager = context.getSystemService(BatteryManager.class);
+ if (batteryManager != null) {
+ return batteryManager.isCharging();
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ static class LogRunnable implements Runnable {
+ private static final long TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5);
+
+ private JobService mJobService;
+ private JobParameters mParams;
+ private AppCollector mCollector;
+ private File mOutputFile;
+ private File mRootDirectory;
+ private File mDownloadsDirectory;
+ private long mSystemSize;
+
+ public void setRootDirectory(File file) {
+ mRootDirectory = file;
+ }
+
+ public void setDownloadsDirectory(File file) {
+ mDownloadsDirectory = file;
+ }
+
+ public void setAppCollector(AppCollector collector) {
+ mCollector = collector;
+ }
+
+ public void setLogOutputFile(File file) {
+ mOutputFile = file;
+ }
+
+ public void setSystemSize(long size) {
+ mSystemSize = size;
+ }
+
+ public void setJobService(JobService jobService, JobParameters params) {
+ mJobService = jobService;
+ mParams = params;
+ }
+
+ public void run() {
+ FileCollector.MeasurementResult mainCategories =
+ FileCollector.getMeasurementResult(mRootDirectory);
+ FileCollector.MeasurementResult downloads =
+ FileCollector.getMeasurementResult(mDownloadsDirectory);
+
+ logToFile(mainCategories, downloads, mCollector.getPackageStats(TIMEOUT_MILLIS),
+ mSystemSize);
+
+ if (mJobService != null) {
+ mJobService.jobFinished(mParams, false);
+ }
+ }
+
+ private void logToFile(MeasurementResult mainCategories, MeasurementResult downloads,
+ List<PackageStats> stats, long systemSize) {
+ DiskStatsFileLogger logger = new DiskStatsFileLogger(mainCategories, downloads, stats,
+ systemSize);
+ try {
+ mOutputFile.createNewFile();
+ logger.dumpToFile(mOutputFile);
+ } catch (IOException e) {
+ Log.e(TAG, "Exception while writing opportunistic disk file cache.", e);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/storage/FileCollector.java b/services/core/java/com/android/server/storage/FileCollector.java
new file mode 100644
index 0000000..90f9f139
--- /dev/null
+++ b/services/core/java/com/android/server/storage/FileCollector.java
@@ -0,0 +1,247 @@
+/*
+ * 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/LICENSE2.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.storage;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.util.ArrayMap;
+
+import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+
+/**
+ * FileCollector walks over a directory and categorizes storage usage by their type.
+ */
+public class FileCollector {
+ private static final int UNRECOGNIZED = -1;
+ private static final int IMAGES = 0;
+ private static final int VIDEO = 1;
+ private static final int AUDIO = 2;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ UNRECOGNIZED,
+ IMAGES,
+ VIDEO,
+ AUDIO })
+ private @interface FileTypes {}
+
+
+ private static final Map<String, Integer> EXTENSION_MAP = new ArrayMap<String, Integer>();
+ static {
+ // Audio
+ EXTENSION_MAP.put("aac", AUDIO);
+ EXTENSION_MAP.put("amr", AUDIO);
+ EXTENSION_MAP.put("awb", AUDIO);
+ EXTENSION_MAP.put("snd", AUDIO);
+ EXTENSION_MAP.put("flac", AUDIO);
+ EXTENSION_MAP.put("mp3", AUDIO);
+ EXTENSION_MAP.put("mpga", AUDIO);
+ EXTENSION_MAP.put("mpega", AUDIO);
+ EXTENSION_MAP.put("mp2", AUDIO);
+ EXTENSION_MAP.put("m4a", AUDIO);
+ EXTENSION_MAP.put("aif", AUDIO);
+ EXTENSION_MAP.put("aiff", AUDIO);
+ EXTENSION_MAP.put("aifc", AUDIO);
+ EXTENSION_MAP.put("gsm", AUDIO);
+ EXTENSION_MAP.put("mka", AUDIO);
+ EXTENSION_MAP.put("m3u", AUDIO);
+ EXTENSION_MAP.put("wma", AUDIO);
+ EXTENSION_MAP.put("wax", AUDIO);
+ EXTENSION_MAP.put("ra", AUDIO);
+ EXTENSION_MAP.put("rm", AUDIO);
+ EXTENSION_MAP.put("ram", AUDIO);
+ EXTENSION_MAP.put("pls", AUDIO);
+ EXTENSION_MAP.put("sd2", AUDIO);
+ EXTENSION_MAP.put("wav", AUDIO);
+ EXTENSION_MAP.put("ogg", AUDIO);
+ EXTENSION_MAP.put("oga", AUDIO);
+ // Video
+ EXTENSION_MAP.put("3gpp", VIDEO);
+ EXTENSION_MAP.put("3gp", VIDEO);
+ EXTENSION_MAP.put("3gpp2", VIDEO);
+ EXTENSION_MAP.put("3g2", VIDEO);
+ EXTENSION_MAP.put("avi", VIDEO);
+ EXTENSION_MAP.put("dl", VIDEO);
+ EXTENSION_MAP.put("dif", VIDEO);
+ EXTENSION_MAP.put("dv", VIDEO);
+ EXTENSION_MAP.put("fli", VIDEO);
+ EXTENSION_MAP.put("m4v", VIDEO);
+ EXTENSION_MAP.put("ts", VIDEO);
+ EXTENSION_MAP.put("mpeg", VIDEO);
+ EXTENSION_MAP.put("mpg", VIDEO);
+ EXTENSION_MAP.put("mpe", VIDEO);
+ EXTENSION_MAP.put("mp4", VIDEO);
+ EXTENSION_MAP.put("vob", VIDEO);
+ EXTENSION_MAP.put("qt", VIDEO);
+ EXTENSION_MAP.put("mov", VIDEO);
+ EXTENSION_MAP.put("mxu", VIDEO);
+ EXTENSION_MAP.put("webm", VIDEO);
+ EXTENSION_MAP.put("lsf", VIDEO);
+ EXTENSION_MAP.put("lsx", VIDEO);
+ EXTENSION_MAP.put("mkv", VIDEO);
+ EXTENSION_MAP.put("mng", VIDEO);
+ EXTENSION_MAP.put("asf", VIDEO);
+ EXTENSION_MAP.put("asx", VIDEO);
+ EXTENSION_MAP.put("wm", VIDEO);
+ EXTENSION_MAP.put("wmv", VIDEO);
+ EXTENSION_MAP.put("wmx", VIDEO);
+ EXTENSION_MAP.put("wvx", VIDEO);
+ EXTENSION_MAP.put("movie", VIDEO);
+ EXTENSION_MAP.put("wrf", VIDEO);
+ // Images
+ EXTENSION_MAP.put("bmp", IMAGES);
+ EXTENSION_MAP.put("gif", IMAGES);
+ EXTENSION_MAP.put("jpg", IMAGES);
+ EXTENSION_MAP.put("jpeg", IMAGES);
+ EXTENSION_MAP.put("jpe", IMAGES);
+ EXTENSION_MAP.put("pcx", IMAGES);
+ EXTENSION_MAP.put("png", IMAGES);
+ EXTENSION_MAP.put("svg", IMAGES);
+ EXTENSION_MAP.put("svgz", IMAGES);
+ EXTENSION_MAP.put("tiff", IMAGES);
+ EXTENSION_MAP.put("tif", IMAGES);
+ EXTENSION_MAP.put("wbmp", IMAGES);
+ EXTENSION_MAP.put("webp", IMAGES);
+ EXTENSION_MAP.put("dng", IMAGES);
+ EXTENSION_MAP.put("cr2", IMAGES);
+ EXTENSION_MAP.put("ras", IMAGES);
+ EXTENSION_MAP.put("art", IMAGES);
+ EXTENSION_MAP.put("jng", IMAGES);
+ EXTENSION_MAP.put("nef", IMAGES);
+ EXTENSION_MAP.put("nrw", IMAGES);
+ EXTENSION_MAP.put("orf", IMAGES);
+ EXTENSION_MAP.put("rw2", IMAGES);
+ EXTENSION_MAP.put("pef", IMAGES);
+ EXTENSION_MAP.put("psd", IMAGES);
+ EXTENSION_MAP.put("pnm", IMAGES);
+ EXTENSION_MAP.put("pbm", IMAGES);
+ EXTENSION_MAP.put("pgm", IMAGES);
+ EXTENSION_MAP.put("ppm", IMAGES);
+ EXTENSION_MAP.put("srw", IMAGES);
+ EXTENSION_MAP.put("arw", IMAGES);
+ EXTENSION_MAP.put("rgb", IMAGES);
+ EXTENSION_MAP.put("xbm", IMAGES);
+ EXTENSION_MAP.put("xpm", IMAGES);
+ EXTENSION_MAP.put("xwd", IMAGES);
+ }
+
+ /**
+ * Returns the file categorization measurement result.
+ * @param path Directory to collect and categorize storage in.
+ */
+ public static MeasurementResult getMeasurementResult(File path) {
+ return collectFiles(StorageManager.maybeTranslateEmulatedPathToInternal(path),
+ new MeasurementResult());
+ }
+
+ /**
+ * Returns the size of a system for a given context. This is done by finding the difference
+ * between the shared data and the total primary storage size.
+ * @param context Context to use to get storage information.
+ */
+ public static long getSystemSize(Context context) {
+ PackageManager pm = context.getPackageManager();
+ VolumeInfo primaryVolume = pm.getPrimaryStorageCurrentVolume();
+
+ StorageManager sm = context.getSystemService(StorageManager.class);
+ VolumeInfo shared = sm.findEmulatedForPrivate(primaryVolume);
+ if (shared == null) {
+ return 0;
+ }
+
+ final long sharedDataSize = shared.getPath().getTotalSpace();
+ long systemSize = sm.getPrimaryStorageSize() - sharedDataSize;
+
+ // This case is not exceptional -- we just fallback to the shared data volume in this case.
+ if (systemSize <= 0) {
+ return 0;
+ }
+
+ return systemSize;
+ }
+
+ private static MeasurementResult collectFiles(File file, MeasurementResult result) {
+ File[] files = file.listFiles();
+
+ if (files == null) {
+ return result;
+ }
+
+ for (File f : files) {
+ if (f.isDirectory()) {
+ try {
+ collectFiles(f, result);
+ } catch (StackOverflowError e) {
+ return result;
+ }
+ } else {
+ handleFile(result, f);
+ }
+ }
+
+ return result;
+ }
+
+ private static void handleFile(MeasurementResult result, File f) {
+ long fileSize = f.length();
+ int fileType = EXTENSION_MAP.getOrDefault(getExtensionForFile(f), UNRECOGNIZED);
+ switch (fileType) {
+ case AUDIO:
+ result.audioSize += fileSize;
+ break;
+ case VIDEO:
+ result.videosSize += fileSize;
+ break;
+ case IMAGES:
+ result.imagesSize += fileSize;
+ break;
+ default:
+ result.miscSize += fileSize;
+ }
+ }
+
+ private static String getExtensionForFile(File file) {
+ String fileName = file.getName();
+ int index = fileName.lastIndexOf('.');
+ if (index == -1) {
+ return "";
+ }
+ return fileName.substring(index + 1).toLowerCase();
+ }
+
+ /**
+ * MeasurementResult contains a storage categorization result.
+ */
+ public static class MeasurementResult {
+ public long imagesSize;
+ public long videosSize;
+ public long miscSize;
+ public long audioSize;
+
+ /**
+ * Sums up the storage taken by all of the categorizable sizes in the measurement.
+ */
+ public long totalAccountedSize() {
+ return imagesSize + videosSize + miscSize + audioSize;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 3645c24..8f64353 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1556,6 +1556,19 @@
throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
}
final long ident = Binder.clearCallingIdentity();
+
+ // Live wallpapers can't be specified for keyguard. If we're using a static
+ // system+lock image currently, migrate the system wallpaper to be a lock-only
+ // image as part of making a different live component active as the system
+ // wallpaper.
+ if (mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
+ if (mLockWallpaperMap.get(userId) == null) {
+ // We're using the static imagery and there is no lock-specific image in place,
+ // therefore it's a shared system+lock image that we need to migrate.
+ migrateSystemToLockWallpaperLocked(userId);
+ }
+ }
+
try {
wallpaper.imageWallpaperPending = false;
if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {
diff --git a/services/core/jni/com_android_server_ConsumerIrService.cpp b/services/core/jni/com_android_server_ConsumerIrService.cpp
index 5906e6b..1f7bf4a0 100644
--- a/services/core/jni/com_android_server_ConsumerIrService.cpp
+++ b/services/core/jni/com_android_server_ConsumerIrService.cpp
@@ -49,7 +49,7 @@
hidl_vec<int32_t> patternVec;
patternVec.setToExternal(const_cast<int32_t*>(cPattern.get()), cPattern.size());
- bool success = mHal->transmit(carrierFrequency, patternVec, cPattern.size());
+ bool success = mHal->transmit(carrierFrequency, patternVec);
return success ? 0 : -1;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3281bd6..050f25d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -17,7 +17,9 @@
package com.android.server.devicepolicy;
import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
+import static android.app.admin.DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG;
import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY;
+import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED;
import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE;
import static android.app.admin.DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyManager.CODE_HAS_DEVICE_OWNER;
@@ -244,6 +246,7 @@
private static final int MONITORING_CERT_NOTIFICATION_ID = R.plurals.ssl_ca_cert_warning;
private static final int PROFILE_WIPED_NOTIFICATION_ID = 1001;
+ private static final int NETWORK_LOGGING_NOTIFICATION_ID = 1002;
private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
private static final String ATTR_SETUP_COMPLETE = "setup-complete";
@@ -252,7 +255,6 @@
private static final String ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED =
"device-provisioning-config-applied";
private static final String ATTR_DEVICE_PAIRED = "device-paired";
-
private static final String ATTR_DELEGATED_CERT_INSTALLER = "delegated-cert-installer";
private static final String ATTR_APPLICATION_RESTRICTIONS_MANAGER
= "application-restrictions-manager";
@@ -635,6 +637,8 @@
private static final String TAG_PARENT_ADMIN = "parent-admin";
private static final String TAG_ORGANIZATION_COLOR = "organization-color";
private static final String TAG_ORGANIZATION_NAME = "organization-name";
+ private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
+ private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
final DeviceAdminInfo info;
@@ -685,6 +689,11 @@
boolean forceEphemeralUsers = false; // Can only be set by a device owner.
boolean isNetworkLoggingEnabled = false; // Can only be set by a device owner.
+ // one notification after enabling + 3 more after reboots
+ static final int DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN = 4;
+ int numNetworkLoggingNotifications = 0;
+ long lastNetworkLoggingNotificationTimeMs = 0; // Time in milliseconds since epoch
+
ActiveAdmin parentAdmin;
final boolean isParent;
@@ -903,6 +912,10 @@
if (isNetworkLoggingEnabled) {
out.startTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
out.attribute(null, ATTR_VALUE, Boolean.toString(isNetworkLoggingEnabled));
+ out.attribute(null, ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS,
+ Integer.toString(numNetworkLoggingNotifications));
+ out.attribute(null, ATTR_LAST_NETWORK_LOGGING_NOTIFICATION,
+ Long.toString(lastNetworkLoggingNotificationTimeMs));
out.endTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
}
if (disabledKeyguardFeatures != DEF_KEYGUARD_FEATURES_DISABLED) {
@@ -1094,6 +1107,10 @@
} else if (TAG_IS_NETWORK_LOGGING_ENABLED.equals(tag)) {
isNetworkLoggingEnabled = Boolean.parseBoolean(
parser.getAttributeValue(null, ATTR_VALUE));
+ lastNetworkLoggingNotificationTimeMs = Long.parseLong(
+ parser.getAttributeValue(null, ATTR_LAST_NETWORK_LOGGING_NOTIFICATION));
+ numNetworkLoggingNotifications = Integer.parseInt(
+ parser.getAttributeValue(null, ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS));
} else if (TAG_DISABLE_KEYGUARD_FEATURES.equals(tag)) {
disabledKeyguardFeatures = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
@@ -1689,9 +1706,9 @@
mSecurityLogMonitor = new SecurityLogMonitor(this);
- mHasFeature = mContext.getPackageManager()
+ mHasFeature = mInjector.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
- mIsWatch = mContext.getPackageManager()
+ mIsWatch = mInjector.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_WATCH);
if (!mHasFeature) {
// Skip the rest of the initialization
@@ -2341,20 +2358,6 @@
out.endTag(null, "failed-password-attempts");
}
- final PasswordMetrics metrics = policy.mActivePasswordMetrics;
- if (!metrics.isDefault()) {
- out.startTag(null, "active-password");
- out.attribute(null, "quality", Integer.toString(metrics.quality));
- out.attribute(null, "length", Integer.toString(metrics.length));
- out.attribute(null, "uppercase", Integer.toString(metrics.upperCase));
- out.attribute(null, "lowercase", Integer.toString(metrics.lowerCase));
- out.attribute(null, "letters", Integer.toString(metrics.letters));
- out.attribute(null, "numeric", Integer.toString(metrics.numeric));
- out.attribute(null, "symbols", Integer.toString(metrics.symbols));
- out.attribute(null, "nonletter", Integer.toString(metrics.nonLetter));
- out.endTag(null, "active-password");
- }
-
for (int i = 0; i < policy.mAcceptedCaCertificates.size(); i++) {
out.startTag(null, TAG_ACCEPTED_CA_CERTIFICATES);
out.attribute(null, ATTR_NAME, policy.mAcceptedCaCertificates.valueAt(i));
@@ -2455,6 +2458,7 @@
JournaledFile journal = makeJournaledFile(userHandle);
FileInputStream stream = null;
File file = journal.chooseForRead();
+ boolean needsRewrite = false;
try {
stream = new FileInputStream(file);
XmlPullParser parser = Xml.newPullParser();
@@ -2541,16 +2545,6 @@
} else if ("password-owner".equals(tag)) {
policy.mPasswordOwner = Integer.parseInt(
parser.getAttributeValue(null, "value"));
- } else if ("active-password".equals(tag)) {
- final PasswordMetrics m = policy.mActivePasswordMetrics;
- m.quality = Integer.parseInt(parser.getAttributeValue(null, "quality"));
- m.length = Integer.parseInt(parser.getAttributeValue(null, "length"));
- m.upperCase = Integer.parseInt(parser.getAttributeValue(null, "uppercase"));
- m.lowerCase = Integer.parseInt(parser.getAttributeValue(null, "lowercase"));
- m.letters = Integer.parseInt(parser.getAttributeValue(null, "letters"));
- m.numeric = Integer.parseInt(parser.getAttributeValue(null, "numeric"));
- m.symbols = Integer.parseInt(parser.getAttributeValue(null, "symbols"));
- m.nonLetter = Integer.parseInt(parser.getAttributeValue(null, "nonletter"));
} else if (TAG_ACCEPTED_CA_CERTIFICATES.equals(tag)) {
policy.mAcceptedCaCertificates.add(parser.getAttributeValue(null, ATTR_NAME));
} else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) {
@@ -2576,6 +2570,8 @@
policy.mAdminBroadcastPending = Boolean.toString(true).equals(pending);
} else if (TAG_INITIALIZATION_BUNDLE.equals(tag)) {
policy.mInitBundle = PersistableBundle.restoreFromXml(parser);
+ } else if ("active-password".equals(tag)) {
+ needsRewrite = true;
} else {
Slog.w(LOG_TAG, "Unknown tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -2595,27 +2591,14 @@
// Ignore
}
+ // Might need to upgrade the file by rewriting it
+ if (needsRewrite) {
+ saveSettingsLocked(userHandle);
+ }
+
// Generate a list of admins from the admin map
policy.mAdminList.addAll(policy.mAdminMap.values());
- // Validate that what we stored for the password quality matches
- // sufficiently what is currently set. Note that this is only
- // a sanity check in case the two get out of sync; this should
- // never normally happen.
- final long identity = mInjector.binderClearCallingIdentity();
- try {
- int actualPasswordQuality = mLockPatternUtils.getActivePasswordQuality(userHandle);
- if (actualPasswordQuality < policy.mActivePasswordMetrics.quality) {
- Slog.w(LOG_TAG, "Active password quality 0x"
- + Integer.toHexString(policy.mActivePasswordMetrics.quality)
- + " does not match actual quality 0x"
- + Integer.toHexString(actualPasswordQuality));
- policy.mActivePasswordMetrics = new PasswordMetrics();
- }
- } finally {
- mInjector.binderRestoreCallingIdentity(identity);
- }
-
validatePasswordOwnerLocked(policy);
updateMaximumTimeToLockLocked(userHandle);
updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
@@ -3849,6 +3832,8 @@
private boolean isActivePasswordSufficientForUserLocked(
DevicePolicyData policy, int userHandle, boolean parent) {
+ enforceUserUnlocked(userHandle, parent);
+
final int requiredPasswordQuality = getPasswordQuality(null, userHandle, parent);
if (policy.mActivePasswordMetrics.quality < requiredPasswordQuality) {
return false;
@@ -4460,7 +4445,7 @@
}
try {
- int uid = mContext.getPackageManager().getPackageUidAsUser(
+ int uid = mInjector.getPackageManager().getPackageUidAsUser(
policy.mDelegatedCertInstallerPackage, userHandle);
return uid == callingUid;
} catch (NameNotFoundException e) {
@@ -4923,33 +4908,52 @@
return;
}
enforceFullCrossUsersPermission(userHandle);
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+
+ // If the managed profile doesn't have a separate password, set the metrics to default
+ if (isManagedProfile(userHandle) && !isSeparateProfileChallengeEnabled(userHandle)) {
+ metrics = new PasswordMetrics();
+ }
+
+ validateQualityConstant(metrics.quality);
+ DevicePolicyData policy = getUserData(userHandle);
+ synchronized (this) {
+ policy.mActivePasswordMetrics = metrics;
+ }
+ }
+
+ @Override
+ public void reportPasswordChanged(@UserIdInt int userId) {
+ if (!mHasFeature) {
+ return;
+ }
+ enforceFullCrossUsersPermission(userId);
// Managed Profile password can only be changed when it has a separate challenge.
- if (!isSeparateProfileChallengeEnabled(userHandle)) {
- enforceNotManagedProfile(userHandle, "set the active password");
+ if (!isSeparateProfileChallengeEnabled(userId)) {
+ enforceNotManagedProfile(userId, "set the active password");
}
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
- validateQualityConstant(metrics.quality);
- DevicePolicyData policy = getUserData(userHandle);
+ DevicePolicyData policy = getUserData(userId);
long ident = mInjector.binderClearCallingIdentity();
try {
synchronized (this) {
- policy.mActivePasswordMetrics = metrics;
policy.mFailedPasswordAttempts = 0;
- saveSettingsLocked(userHandle);
- updatePasswordExpirationsLocked(userHandle);
- setExpirationAlarmCheckLocked(mContext, userHandle, /* parent */ false);
+ saveSettingsLocked(userId);
+ updatePasswordExpirationsLocked(userId);
+ setExpirationAlarmCheckLocked(mContext, userId, /* parent */ false);
// Send a broadcast to each profile using this password as its primary unlock.
sendAdminCommandForLockscreenPoliciesLocked(
DeviceAdminReceiver.ACTION_PASSWORD_CHANGED,
- DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, userHandle);
+ DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, userId);
}
- removeCaApprovalsIfNeeded(userHandle);
+ removeCaApprovalsIfNeeded(userId);
} finally {
mInjector.binderRestoreCallingIdentity(ident);
}
@@ -6021,6 +6025,14 @@
}
}
+ private boolean isDeviceOwnerPackage(String packageName, int userId) {
+ synchronized (this) {
+ return mOwners.hasDeviceOwner()
+ && mOwners.getDeviceOwnerUserId() == userId
+ && mOwners.getDeviceOwnerPackageName().equals(packageName);
+ }
+ }
+
public boolean isProfileOwner(ComponentName who, int userId) {
final ComponentName profileOwner = getProfileOwner(userId);
return who != null && who.equals(profileOwner);
@@ -6103,7 +6115,7 @@
Preconditions.checkNotNull(packageName, "packageName is null");
final int callingUid = mInjector.binderGetCallingUid();
try {
- int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
+ int uid = mInjector.getPackageManager().getPackageUidAsUser(packageName,
UserHandle.getUserId(callingUid));
if (uid != callingUid) {
throw new SecurityException("Invalid packageName");
@@ -6589,6 +6601,14 @@
"User must be running and unlocked");
}
+ private void enforceUserUnlocked(@UserIdInt int userId, boolean parent) {
+ if (parent) {
+ enforceUserUnlocked(getProfileParentId(userId));
+ } else {
+ enforceUserUnlocked(userId);
+ }
+ }
+
private void enforceManageUsers() {
final int callingUid = mInjector.binderGetCallingUid();
if (!(isCallerWithSystemUid() || callingUid == Process.ROOT_UID)) {
@@ -6847,7 +6867,7 @@
}
try {
- int uid = mContext.getPackageManager().getPackageUidAsUser(
+ int uid = mInjector.getPackageManager().getPackageUidAsUser(
policy.mApplicationRestrictionsManagingPackage, userHandle);
return uid == callingUid;
} catch (NameNotFoundException e) {
@@ -8635,7 +8655,7 @@
}
final String deviceOwnerPackageName = mOwners.getDeviceOwnerComponent()
.getPackageName();
- final String[] pkgs = mContext.getPackageManager().getPackagesForUid(callerUid);
+ final String[] pkgs = mInjector.getPackageManager().getPackagesForUid(callerUid);
for (String pkg : pkgs) {
if (deviceOwnerPackageName.equals(pkg)) {
@@ -8672,7 +8692,7 @@
ActivityInfo[] receivers = null;
try {
- receivers = mContext.getPackageManager().getPackageInfo(
+ receivers = mInjector.getPackageManager().getPackageInfo(
deviceOwnerPackage, PackageManager.GET_RECEIVERS).receivers;
} catch (NameNotFoundException e) {
Log.e(LOG_TAG, "Cannot find device owner package", e);
@@ -8728,7 +8748,7 @@
< android.os.Build.VERSION_CODES.M) {
return false;
}
- final PackageManager packageManager = mContext.getPackageManager();
+ final PackageManager packageManager = mInjector.getPackageManager();
switch (grantState) {
case DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED: {
mInjector.getPackageManagerInternal().grantRuntimePermission(packageName,
@@ -8763,7 +8783,7 @@
@Override
public int getPermissionGrantState(ComponentName admin, String packageName,
String permission) throws RemoteException {
- PackageManager packageManager = mContext.getPackageManager();
+ PackageManager packageManager = mInjector.getPackageManager();
UserHandle user = mInjector.binderGetCallingUserHandle();
synchronized (this) {
@@ -8800,17 +8820,33 @@
}
@Override
- public boolean isProvisioningAllowed(String action) {
- return checkProvisioningPreConditionSkipPermission(action) == CODE_OK;
+ public boolean isProvisioningAllowed(String action, String packageName) {
+ Preconditions.checkNotNull(packageName);
+
+ final int callingUid = mInjector.binderGetCallingUid();
+ final long ident = mInjector.binderClearCallingIdentity();
+ try {
+ final int uidForPackage = mInjector.getPackageManager().getPackageUidAsUser(
+ packageName, UserHandle.getUserId(callingUid));
+ Preconditions.checkArgument(callingUid == uidForPackage,
+ "Caller uid doesn't match the one for the provided package.");
+ } catch (NameNotFoundException e) {
+ throw new IllegalArgumentException("Invalid package provided " + packageName, e);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+
+ return checkProvisioningPreConditionSkipPermission(action, packageName) == CODE_OK;
}
@Override
- public int checkProvisioningPreCondition(String action) {
+ public int checkProvisioningPreCondition(String action, String packageName) {
+ Preconditions.checkNotNull(packageName);
enforceCanManageProfileAndDeviceOwners();
- return checkProvisioningPreConditionSkipPermission(action);
+ return checkProvisioningPreConditionSkipPermission(action, packageName);
}
- private int checkProvisioningPreConditionSkipPermission(String action) {
+ private int checkProvisioningPreConditionSkipPermission(String action, String packageName) {
if (!mHasFeature) {
return CODE_DEVICE_ADMIN_NOT_SUPPORTED;
}
@@ -8819,7 +8855,7 @@
if (action != null) {
switch (action) {
case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE:
- return checkManagedProfileProvisioningPreCondition(callingUserId);
+ return checkManagedProfileProvisioningPreCondition(packageName, callingUserId);
case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
return checkDeviceOwnerProvisioningPreCondition(callingUserId);
case DevicePolicyManager.ACTION_PROVISION_MANAGED_USER:
@@ -8888,7 +8924,7 @@
}
}
- private int checkManagedProfileProvisioningPreCondition(int callingUserId) {
+ private int checkManagedProfileProvisioningPreCondition(String packageName, int callingUserId) {
if (!hasFeatureManagedUsers()) {
return CODE_MANAGED_USERS_NOT_SUPPORTED;
}
@@ -8901,24 +8937,25 @@
// Managed user cannot have a managed profile.
return CODE_USER_HAS_PROFILE_OWNER;
}
+
final long ident = mInjector.binderClearCallingIdentity();
try {
- /* STOPSHIP(b/31952368) Reinstate a check similar to this once ManagedProvisioning
- uses checkProvisioningPreCondition (see ag/1607846) and passes the packageName
- there. In isProvisioningAllowed we should check isCallerDeviceOwner, but for
- managed provisioning we need to check the package that is going to be set as PO
- if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE)) {
- if (!isCallerDeviceOwner(callingUid)
- || isAdminAffectedByRestriction(mOwners.getDeviceOwnerComponent(),
- UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) {
+ final UserHandle callingUserHandle = UserHandle.of(callingUserId);
+ if (mUserManager.hasUserRestriction(
+ UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserHandle)) {
+ // The DO can initiate provisioning if the restriction was set by the DO.
+ if (!isDeviceOwnerPackage(packageName, callingUserId)
+ || isAdminAffectedByRestriction(mOwners.getDeviceOwnerComponent(),
+ UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) {
// Caller is not DO or the restriction was set by the system.
- return false;
- }
- } */
+ return CODE_ADD_MANAGED_PROFILE_DISALLOWED;
+ }
+ }
+
// TODO: Allow it if the caller is the DO? DO could just call removeUser() before
// provisioning, so not strictly required...
boolean canRemoveProfile = !mUserManager.hasUserRestriction(
- UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, UserHandle.of(callingUserId));
+ UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, callingUserHandle);
if (!mUserManager.canAddMoreManagedProfiles(callingUserId, canRemoveProfile)) {
return CODE_CANNOT_ADD_MANAGED_PROFILE;
}
@@ -9867,7 +9904,12 @@
// already in the requested state
return;
}
- getDeviceOwnerAdminLocked().isNetworkLoggingEnabled = enabled;
+ ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ deviceOwner.isNetworkLoggingEnabled = enabled;
+ if (!enabled) {
+ deviceOwner.numNetworkLoggingNotifications = 0;
+ deviceOwner.lastNetworkLoggingNotificationTimeMs = 0;
+ }
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
setNetworkLoggingActiveInternal(enabled);
@@ -9883,6 +9925,7 @@
Slog.wtf(LOG_TAG, "Network logging could not be started due to the logging"
+ " service not being available yet.");
}
+ sendNetworkLoggingNotificationLocked();
} else {
if (mNetworkLogger != null && !mNetworkLogger.stopNetworkLogging()) {
mNetworkLogger = null;
@@ -9948,6 +9991,41 @@
return mNetworkLogger.retrieveLogs(batchToken);
}
+ private void sendNetworkLoggingNotificationLocked() {
+ final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ if (deviceOwner == null || !deviceOwner.isNetworkLoggingEnabled) {
+ return;
+ }
+ if (deviceOwner.numNetworkLoggingNotifications >=
+ ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) {
+ return;
+ }
+ final long now = System.currentTimeMillis();
+ if (now - deviceOwner.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) {
+ return;
+ }
+ deviceOwner.numNetworkLoggingNotifications++;
+ if (deviceOwner.numNetworkLoggingNotifications
+ >= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) {
+ deviceOwner.lastNetworkLoggingNotificationTimeMs = 0;
+ } else {
+ deviceOwner.lastNetworkLoggingNotificationTimeMs = now;
+ }
+ final Intent intent = new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG);
+ intent.setPackage("com.android.systemui");
+ final PendingIntent pendingIntent = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0,
+ UserHandle.CURRENT);
+ Notification notification = new Notification.Builder(mContext)
+ .setSmallIcon(R.drawable.ic_qs_network_logging)
+ .setContentTitle(mContext.getString(R.string.network_logging_notification_title))
+ .setContentText(mContext.getString(R.string.network_logging_notification_text))
+ .setShowWhen(true)
+ .setContentIntent(pendingIntent)
+ .build();
+ mInjector.getNotificationManager().notify(NETWORK_LOGGING_NOTIFICATION_ID, notification);
+ saveSettingsLocked(mOwners.getDeviceOwnerUserId());
+ }
+
/**
* Return the package name of owner in a given user.
*/
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 3f5b96e..86983eb 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -23,7 +23,8 @@
android-support-test \
mockito-target-minus-junit4 \
platform-test-annotations \
- ShortcutManagerTestUtils
+ ShortcutManagerTestUtils \
+ truth-prebuilt
LOCAL_JAVA_LIBRARIES := android.test.runner
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index c653b8e..69d27f2 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -122,6 +122,8 @@
when(mContext.getResources()).thenReturn(mResources);
mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager);
WifiConfiguration configuration = new WifiConfiguration();
+ configuration.SSID = "NetworkScoreServiceTest_SSID";
+ configuration.BSSID = "NetworkScoreServiceTest_BSSID";
mRecommendationRequest = new RecommendationRequest.Builder()
.setCurrentRecommendedWifiConfig(configuration).build();
}
@@ -232,9 +234,10 @@
injectProvider();
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
final WifiConfiguration wifiConfiguration = new WifiConfiguration();
- wifiConfiguration.SSID = "testRequestRecommendation_resultReturned";
- final RecommendationResult providerResult =
- new RecommendationResult(wifiConfiguration);
+ wifiConfiguration.SSID = "testRequestRecommendation_resultReturned_SSID";
+ wifiConfiguration.BSSID = "testRequestRecommendation_resultReturned_BSSID";
+ final RecommendationResult providerResult = RecommendationResult
+ .createConnectRecommendation(wifiConfiguration);
final Bundle bundle = new Bundle();
bundle.putParcelable(EXTRA_RECOMMENDATION_RESULT, providerResult);
doAnswer(invocation -> {
@@ -250,6 +253,8 @@
assertNotNull(result);
assertEquals(providerResult.getWifiConfiguration().SSID,
result.getWifiConfiguration().SSID);
+ assertEquals(providerResult.getWifiConfiguration().BSSID,
+ result.getWifiConfiguration().BSSID);
}
@Test
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 48c9853..c35d114 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -77,7 +77,7 @@
/**
* Tests for DevicePolicyManager( and DevicePolicyManagerService).
- *
+ * You can run them via:
m FrameworksServicesTests &&
adb install \
-r ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
@@ -85,6 +85,9 @@
-w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
(mmma frameworks/base/services/tests/servicestests/ for non-ninja build)
+ *
+ * , or:
+ * runtest -c com.android.server.devicepolicy.DevicePolicyManagerTest frameworks-services
*/
@SmallTest
public class DevicePolicyManagerTest extends DpmTestBase {
@@ -2010,7 +2013,7 @@
// UnfinishedVerificationException.
}
- public void setup_DeviceAdminFeatureOff() throws Exception {
+ private void setup_DeviceAdminFeatureOff() throws Exception {
when(mContext.packageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN))
.thenReturn(false);
when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
@@ -2026,6 +2029,8 @@
public void testIsProvisioningAllowed_DeviceAdminFeatureOff() throws Exception {
setup_DeviceAdminFeatureOff();
+ mContext.packageName = admin1.getPackageName();
+ setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
@@ -2047,7 +2052,7 @@
DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
}
- public void setup_ManagedProfileFeatureOff() throws Exception {
+ private void setup_ManagedProfileFeatureOff() throws Exception {
when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(false);
initializeDpms();
@@ -2061,6 +2066,8 @@
public void testIsProvisioningAllowed_ManagedProfileFeatureOff() throws Exception {
setup_ManagedProfileFeatureOff();
+ mContext.packageName = admin1.getPackageName();
+ setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
@@ -2102,7 +2109,7 @@
DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED);
}
- public void setup_nonSplitUser_firstBoot_primaryUser() throws Exception {
+ private void setup_nonSplitUser_firstBoot_primaryUser() throws Exception {
when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(false);
@@ -2115,6 +2122,8 @@
public void testIsProvisioningAllowed_nonSplitUser_firstBoot_primaryUser() throws Exception {
setup_nonSplitUser_firstBoot_primaryUser();
+ mContext.packageName = admin1.getPackageName();
+ setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
@@ -2138,7 +2147,7 @@
DevicePolicyManager.CODE_NOT_SYSTEM_USER_SPLIT);
}
- public void setup_nonSplitUser_afterDeviceSetup_primaryUser() throws Exception {
+ private void setup_nonSplitUser_afterDeviceSetup_primaryUser() throws Exception {
when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(false);
@@ -2152,6 +2161,8 @@
public void testIsProvisioningAllowed_nonSplitUser_afterDeviceSetup_primaryUser()
throws Exception {
setup_nonSplitUser_afterDeviceSetup_primaryUser();
+ mContext.packageName = admin1.getPackageName();
+ setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
false/* because of completed device setup */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
@@ -2176,7 +2187,88 @@
DevicePolicyManager.CODE_NOT_SYSTEM_USER_SPLIT);
}
- public void setup_splitUser_firstBoot_systemUser() throws Exception {
+ public void testIsProvisioningAllowed_nonSplitUser_withDo_primaryUser() throws Exception {
+ setDeviceOwner();
+ setup_nonSplitUser_afterDeviceSetup_primaryUser();
+ setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
+ mContext.packageName = admin1.getPackageName();
+
+ // COMP mode is allowed.
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+
+ when(mContext.userManager.hasUserRestriction(
+ eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
+ eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
+ .thenReturn(true);
+
+ // The DO should be allowed to initiate provisioning if it set the restriction itself.
+ when(mContext.userManager.getUserRestrictionSource(
+ eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
+ eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+
+ // The DO should not be allowed to initiate provisioning if the restriction is set by
+ // another entity.
+ when(mContext.userManager.getUserRestrictionSource(
+ eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
+ eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
+ }
+
+ public void
+ testCheckProvisioningPreCondition_nonSplitUser_withDo_primaryUser() throws Exception {
+ setDeviceOwner();
+ setup_nonSplitUser_afterDeviceSetup_primaryUser();
+ mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
+ DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
+
+ // COMP mode is allowed.
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DevicePolicyManager.CODE_OK);
+
+ // And other DPCs can also provisioning a managed profile (DO + BYOD case).
+ assertCheckProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ "some.other.dpc.package.name",
+ DevicePolicyManager.CODE_OK);
+
+ when(mContext.userManager.hasUserRestriction(
+ eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
+ eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
+ .thenReturn(true);
+
+ // The DO should be allowed to initiate provisioning if it set the restriction itself, but
+ // other packages should be forbidden.
+ when(mContext.userManager.getUserRestrictionSource(
+ eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
+ eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ "some.other.dpc.package.name",
+ DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+
+ // The DO should not be allowed to initiate provisioning if the restriction is set by
+ // another entity.
+ when(mContext.userManager.getUserRestrictionSource(
+ eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
+ eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+ assertCheckProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ "some.other.dpc.package.name",
+ DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+ }
+
+ private void setup_splitUser_firstBoot_systemUser() throws Exception {
when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true);
@@ -2189,6 +2281,8 @@
public void testIsProvisioningAllowed_splitUser_firstBoot_systemUser() throws Exception {
setup_splitUser_firstBoot_systemUser();
+ mContext.packageName = admin1.getPackageName();
+ setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
false /* because canAddMoreManagedProfiles returns false */);
@@ -2213,7 +2307,7 @@
DevicePolicyManager.CODE_SYSTEM_USER);
}
- public void setup_splitUser_afterDeviceSetup_systemUser() throws Exception {
+ private void setup_splitUser_afterDeviceSetup_systemUser() throws Exception {
when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true);
@@ -2226,6 +2320,8 @@
public void testIsProvisioningAllowed_splitUser_afterDeviceSetup_systemUser() throws Exception {
setup_splitUser_afterDeviceSetup_systemUser();
+ mContext.packageName = admin1.getPackageName();
+ setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
true/* it's undefined behavior. Can be changed into false in the future */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
@@ -2251,7 +2347,7 @@
DevicePolicyManager.CODE_SYSTEM_USER);
}
- public void setup_splitUser_firstBoot_primaryUser() throws Exception {
+ private void setup_splitUser_firstBoot_primaryUser() throws Exception {
when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true);
@@ -2264,6 +2360,8 @@
public void testIsProvisioningAllowed_splitUser_firstBoot_primaryUser() throws Exception {
setup_splitUser_firstBoot_primaryUser();
+ mContext.packageName = admin1.getPackageName();
+ setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
@@ -2286,7 +2384,7 @@
DevicePolicyManager.CODE_OK);
}
- public void setup_splitUser_afterDeviceSetup_primaryUser() throws Exception {
+ private void setup_splitUser_afterDeviceSetup_primaryUser() throws Exception {
when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true);
@@ -2300,6 +2398,8 @@
public void testIsProvisioningAllowed_splitUser_afterDeviceSetup_primaryUser()
throws Exception {
setup_splitUser_afterDeviceSetup_primaryUser();
+ mContext.packageName = admin1.getPackageName();
+ setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
true/* it's undefined behavior. Can be changed into false in the future */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
@@ -2324,7 +2424,7 @@
DevicePolicyManager.CODE_USER_SETUP_COMPLETED);
}
- public void setup_provisionManagedProfileWithDeviceOwner_systemUser() throws Exception {
+ private void setup_provisionManagedProfileWithDeviceOwner_systemUser() throws Exception {
setDeviceOwner();
when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
@@ -2340,6 +2440,8 @@
public void testIsProvisioningAllowed_provisionManagedProfileWithDeviceOwner_systemUser()
throws Exception {
setup_provisionManagedProfileWithDeviceOwner_systemUser();
+ mContext.packageName = admin1.getPackageName();
+ setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
false /* can't provision managed profile on system user */);
}
@@ -2368,6 +2470,8 @@
public void testIsProvisioningAllowed_provisionManagedProfileWithDeviceOwner_primaryUser()
throws Exception {
setup_provisionManagedProfileWithDeviceOwner_primaryUser();
+ setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
+ mContext.packageName = admin1.getPackageName();
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
}
@@ -2375,6 +2479,8 @@
throws Exception {
setup_provisionManagedProfileWithDeviceOwner_primaryUser();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+
+ // COMP mode is allowed.
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
}
@@ -2386,8 +2492,8 @@
.thenReturn(true);
when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true);
when(mContext.userManager.hasUserRestriction(
- UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
- UserHandle.of(DpmMockContext.CALLER_USER_HANDLE)))
+ eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE),
+ eq(UserHandle.of(DpmMockContext.CALLER_USER_HANDLE))))
.thenReturn(true);
when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
false /* we can't remove a managed profile */)).thenReturn(false);
@@ -2401,6 +2507,8 @@
public void testIsProvisioningAllowed_provisionManagedProfileCantRemoveUser_primaryUser()
throws Exception {
setup_provisionManagedProfileCantRemoveUser_primaryUser();
+ mContext.packageName = admin1.getPackageName();
+ setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
}
@@ -2415,7 +2523,8 @@
public void testCheckProvisioningPreCondition_permission() {
// GIVEN the permission MANAGE_PROFILE_AND_DEVICE_OWNERS is not granted
try {
- dpm.checkProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE);
+ dpm.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, "some.package");
fail("Didn't throw SecurityException");
} catch (SecurityException expected) {
}
@@ -2820,8 +2929,14 @@
}
private void assertCheckProvisioningPreCondition(String action, int provisioningCondition) {
- assertEquals("checkProvisioningPreCondition(" + action + ") returning unexpected result",
- provisioningCondition, dpm.checkProvisioningPreCondition(action));
+ assertCheckProvisioningPreCondition(action, admin1.getPackageName(), provisioningCondition);
+ }
+
+ private void assertCheckProvisioningPreCondition(
+ String action, String packageName, int provisioningCondition) {
+ assertEquals("checkProvisioningPreCondition("
+ + action + ", " + packageName + ") returning unexpected result",
+ provisioningCondition, dpm.checkProvisioningPreCondition(action, packageName));
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index db27f72..8a11976 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -82,6 +82,10 @@
eq(packageName),
eq(0),
eq(userId));
+
+ doReturn(ai.uid).when(mMockContext.packageManager).getPackageUidAsUser(
+ eq(packageName),
+ eq(userId));
}
protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid)
diff --git a/services/tests/servicestests/src/com/android/server/storage/AppCollectorTest.java b/services/tests/servicestests/src/com/android/server/storage/AppCollectorTest.java
new file mode 100644
index 0000000..da22e77
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/storage/AppCollectorTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.storage;
+
+import android.content.pm.UserInfo;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageStats;
+import android.os.UserManager;
+import android.os.storage.VolumeInfo;
+import android.test.AndroidTestCase;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+@RunWith(JUnit4.class)
+public class AppCollectorTest extends AndroidTestCase {
+ private static final long TIMEOUT = TimeUnit.MINUTES.toMillis(1);
+ @Mock private Context mContext;
+ @Mock private PackageManager mPm;
+ @Mock private UserManager mUm;
+ private List<ApplicationInfo> mApps;
+ private List<UserInfo> mUsers;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ mApps = new ArrayList<>();
+ when(mContext.getPackageManager()).thenReturn(mPm);
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUm);
+
+ // Set up the app list.
+ when(mPm.getInstalledApplications(anyInt())).thenReturn(mApps);
+
+ // Set up the user list with a single user (0).
+ mUsers = new ArrayList<>();
+ mUsers.add(new UserInfo(0, "", 0));
+ when(mUm.getUsers()).thenReturn(mUsers);
+ }
+
+ @Test
+ public void testNoApps() throws Exception {
+ VolumeInfo volume = new VolumeInfo("testuuid", 0, null, null);
+ volume.fsUuid = "testuuid";
+ AppCollector collector = new AppCollector(mContext, volume);
+
+ assertThat(collector.getPackageStats(TIMEOUT)).isEmpty();
+ }
+
+ @Test
+ public void testAppOnExternalVolume() throws Exception {
+ addApplication("com.test.app", "differentuuid");
+ VolumeInfo volume = new VolumeInfo("testuuid", 0, null, null);
+ volume.fsUuid = "testuuid";
+ AppCollector collector = new AppCollector(mContext, volume);
+
+ assertThat(collector.getPackageStats(TIMEOUT)).isEmpty();
+ }
+
+ @Test
+ public void testOneValidApp() throws Exception {
+ addApplication("com.test.app", "testuuid");
+ VolumeInfo volume = new VolumeInfo("testuuid", 0, null, null);
+ volume.fsUuid = "testuuid";
+ AppCollector collector = new AppCollector(mContext, volume);
+ PackageStats stats = new PackageStats("com.test.app");
+
+ // Set up this to handle the asynchronous call to the PackageManager. This returns the
+ // package info for the specified package.
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ try {
+ ((IPackageStatsObserver.Stub) invocation.getArguments()[2])
+ .onGetStatsCompleted(stats, true);
+ } catch (Exception e) {
+ // We fail instead of just letting the exception fly because throwing
+ // out of the callback like this on the background thread causes the test
+ // runner to crash, rather than reporting the failure.
+ fail();
+ }
+ return null;
+ }
+ }).when(mPm).getPackageSizeInfoAsUser(eq("com.test.app"), eq(0), any());
+
+
+ // Because getPackageStats is a blocking call, we block execution of the test until the
+ // call finishes. In order to finish the call, we need the above answer to execute.
+ List<PackageStats> myStats = new ArrayList<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ myStats.addAll(collector.getPackageStats(TIMEOUT));
+ latch.countDown();
+ }
+ }).start();
+ latch.await();
+
+ assertThat(myStats).containsExactly(stats);
+ }
+
+ @Test
+ public void testMultipleUsersOneApp() throws Exception {
+ addApplication("com.test.app", "testuuid");
+ ApplicationInfo otherUsersApp = new ApplicationInfo();
+ otherUsersApp.packageName = "com.test.app";
+ otherUsersApp.volumeUuid = "testuuid";
+ otherUsersApp.uid = 1;
+ mUsers.add(new UserInfo(1, "", 0));
+
+ VolumeInfo volume = new VolumeInfo("testuuid", 0, null, null);
+ volume.fsUuid = "testuuid";
+ AppCollector collector = new AppCollector(mContext, volume);
+ PackageStats stats = new PackageStats("com.test.app");
+ PackageStats otherStats = new PackageStats("com.test.app");
+ otherStats.userHandle = 1;
+
+ // Set up this to handle the asynchronous call to the PackageManager. This returns the
+ // package info for our packages.
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ try {
+ ((IPackageStatsObserver.Stub) invocation.getArguments()[2])
+ .onGetStatsCompleted(stats, true);
+
+ // Now callback for the other uid.
+ ((IPackageStatsObserver.Stub) invocation.getArguments()[2])
+ .onGetStatsCompleted(otherStats, true);
+ } catch (Exception e) {
+ // We fail instead of just letting the exception fly because throwing
+ // out of the callback like this on the background thread causes the test
+ // runner to crash, rather than reporting the failure.
+ fail();
+ }
+ return null;
+ }
+ }).when(mPm).getPackageSizeInfoAsUser(eq("com.test.app"), eq(0), any());
+
+
+ // Because getPackageStats is a blocking call, we block execution of the test until the
+ // call finishes. In order to finish the call, we need the above answer to execute.
+ List<PackageStats> myStats = new ArrayList<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ myStats.addAll(collector.getPackageStats(TIMEOUT));
+ latch.countDown();
+ }
+ }).start();
+ latch.await();
+
+ // This should
+ assertThat(myStats).containsAllOf(stats, otherStats);
+ }
+
+ private void addApplication(String packageName, String uuid) {
+ ApplicationInfo info = new ApplicationInfo();
+ info.packageName = packageName;
+ info.volumeUuid = uuid;
+ mApps.add(info);
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java b/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java
new file mode 100644
index 0000000..2aca702
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.storage;
+
+import android.content.pm.PackageStats;
+import android.test.AndroidTestCase;
+import android.util.ArraySet;
+import libcore.io.IoUtils;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.util.ArrayList;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(JUnit4.class)
+public class DiskStatsFileLoggerTest extends AndroidTestCase {
+ @Rule public TemporaryFolder temporaryFolder;
+ public FileCollector.MeasurementResult mMainResult;
+ public FileCollector.MeasurementResult mDownloadsResult;
+ private ArrayList<PackageStats> mPackages;
+ private File mOutputFile;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ temporaryFolder = new TemporaryFolder();
+ temporaryFolder.create();
+ mOutputFile = temporaryFolder.newFile();
+ mMainResult = new FileCollector.MeasurementResult();
+ mDownloadsResult = new FileCollector.MeasurementResult();
+ mPackages = new ArrayList<>();
+ }
+
+ @Test
+ public void testEmptyStorage() throws Exception {
+ DiskStatsFileLogger logger = new DiskStatsFileLogger(
+ mMainResult, mDownloadsResult,mPackages, 0L);
+
+ logger.dumpToFile(mOutputFile);
+
+ JSONObject output = getOutputFileAsJson();
+ assertThat(output.getLong(DiskStatsFileLogger.PHOTOS_KEY)).isEqualTo(0L);
+ assertThat(output.getLong(DiskStatsFileLogger.VIDEOS_KEY)).isEqualTo(0L);
+ assertThat(output.getLong(DiskStatsFileLogger.AUDIO_KEY)).isEqualTo(0L);
+ assertThat(output.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)).isEqualTo(0L);
+ assertThat(output.getLong(DiskStatsFileLogger.SYSTEM_KEY)).isEqualTo(0L);
+ assertThat(output.getLong(DiskStatsFileLogger.MISC_KEY)).isEqualTo(0L);
+ assertThat(output.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(0L);
+ assertThat(output.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(0L);
+ assertThat(
+ output.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY).length()).isEqualTo(0L);
+ assertThat(output.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY).length()).isEqualTo(0L);
+ assertThat(output.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY).length()).isEqualTo(0L);
+ }
+
+ @Test
+ public void testMeasurementResultsReported() throws Exception {
+ mMainResult.audioSize = 1;
+ mMainResult.imagesSize = 10;
+ mMainResult.miscSize = 100;
+ mDownloadsResult.miscSize = 1000;
+ DiskStatsFileLogger logger = new DiskStatsFileLogger(
+ mMainResult, mDownloadsResult,mPackages, 3L);
+
+ logger.dumpToFile(mOutputFile);
+
+ JSONObject output = getOutputFileAsJson();
+ assertThat(output.getLong(DiskStatsFileLogger.AUDIO_KEY)).isEqualTo(1L);
+ assertThat(output.getLong(DiskStatsFileLogger.PHOTOS_KEY)).isEqualTo(10L);
+ assertThat(output.getLong(DiskStatsFileLogger.MISC_KEY)).isEqualTo(100L);
+ assertThat(output.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)).isEqualTo(1000L);
+ assertThat(output.getLong(DiskStatsFileLogger.SYSTEM_KEY)).isEqualTo(3L);
+ }
+
+ @Test
+ public void testAppsReported() throws Exception {
+ PackageStats firstPackage = new PackageStats("com.test.app");
+ firstPackage.codeSize = 100;
+ firstPackage.dataSize = 1000;
+ firstPackage.cacheSize = 20;
+ mPackages.add(firstPackage);
+
+ PackageStats secondPackage = new PackageStats("com.test.app2");
+ secondPackage.codeSize = 10;
+ secondPackage.dataSize = 1;
+ secondPackage.cacheSize = 2;
+ mPackages.add(secondPackage);
+
+ DiskStatsFileLogger logger = new DiskStatsFileLogger(
+ mMainResult, mDownloadsResult, mPackages, 0L);
+ logger.dumpToFile(mOutputFile);
+
+ JSONObject output = getOutputFileAsJson();
+ assertThat(output.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(1111);
+ assertThat(output.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(22);
+
+ JSONArray packageNames = output.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY);
+ assertThat(packageNames.length()).isEqualTo(2);
+ JSONArray appSizes = output.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
+ assertThat(appSizes.length()).isEqualTo(2);
+ JSONArray cacheSizes = output.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY);
+ assertThat(cacheSizes.length()).isEqualTo(2);
+
+ // We need to do this crazy Set over this because the DiskStatsFileLogger provides no
+ // guarantee of the ordering of the apps in its output. By using a set, we avoid any order
+ // problems.
+ ArraySet<AppSizeGrouping> apps = new ArraySet<>();
+ for (int i = 0; i < packageNames.length(); i++) {
+ AppSizeGrouping app = new AppSizeGrouping(packageNames.getString(i),
+ appSizes.getLong(i), cacheSizes.getLong(i));
+ apps.add(app);
+ }
+ assertThat(apps).containsAllOf(new AppSizeGrouping("com.test.app", 1100, 20),
+ new AppSizeGrouping("com.test.app2", 11, 2));
+ }
+
+ @Test
+ public void testEmulatedExternalStorageCounted() throws Exception {
+ PackageStats app = new PackageStats("com.test.app");
+ app.dataSize = 1000;
+ app.externalDataSize = 1000;
+ app.cacheSize = 20;
+ mPackages.add(app);
+
+ DiskStatsFileLogger logger = new DiskStatsFileLogger(
+ mMainResult, mDownloadsResult, mPackages, 0L);
+ logger.dumpToFile(mOutputFile);
+
+ JSONObject output = getOutputFileAsJson();
+ JSONArray appSizes = output.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
+ assertThat(appSizes.length()).isEqualTo(1);
+ assertThat(appSizes.getLong(0)).isEqualTo(2000);
+ }
+
+ @Test
+ public void testDuplicatePackageNameIsMergedAcrossMultipleUsers() throws Exception {
+ PackageStats app = new PackageStats("com.test.app");
+ app.dataSize = 1000;
+ app.externalDataSize = 1000;
+ app.cacheSize = 20;
+ app.userHandle = 0;
+ mPackages.add(app);
+
+ PackageStats secondApp = new PackageStats("com.test.app");
+ secondApp.dataSize = 100;
+ secondApp.externalDataSize = 100;
+ secondApp.cacheSize = 2;
+ secondApp.userHandle = 1;
+ mPackages.add(secondApp);
+
+ DiskStatsFileLogger logger = new DiskStatsFileLogger(
+ mMainResult, mDownloadsResult, mPackages, 0L);
+ logger.dumpToFile(mOutputFile);
+
+ JSONObject output = getOutputFileAsJson();
+ assertThat(output.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(2200);
+ assertThat(output.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(22);
+ JSONArray packageNames = output.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY);
+ assertThat(packageNames.length()).isEqualTo(1);
+ assertThat(packageNames.getString(0)).isEqualTo("com.test.app");
+
+ JSONArray appSizes = output.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
+ assertThat(appSizes.length()).isEqualTo(1);
+ assertThat(appSizes.getLong(0)).isEqualTo(2200);
+
+ JSONArray cacheSizes = output.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY);
+ assertThat(cacheSizes.length()).isEqualTo(1);
+ assertThat(cacheSizes.getLong(0)).isEqualTo(22);
+ }
+
+ private JSONObject getOutputFileAsJson() throws Exception {
+ return new JSONObject(IoUtils.readFileAsString(mOutputFile.getAbsolutePath()));
+ }
+
+ /**
+ * This class exists for putting zipped app size information arrays into a set for comparison
+ * purposes.
+ */
+ private class AppSizeGrouping {
+ public String packageName;
+ public long appSize;
+ public long cacheSize;
+
+ public AppSizeGrouping(String packageName, long appSize, long cacheSize) {
+ this.packageName = packageName;
+ this.appSize = appSize;
+ this.cacheSize = cacheSize;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 37 * result + (int)(appSize ^ (appSize >>> 32));
+ result = 37 * result + (int)(cacheSize ^ (cacheSize >>> 32));
+ result = 37 * result + packageName.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof AppSizeGrouping)) {
+ return false;
+ }
+ if (this == o) {
+ return true;
+ }
+ AppSizeGrouping grouping = (AppSizeGrouping) o;
+ return packageName.equals(grouping.packageName) && appSize == grouping.appSize &&
+ cacheSize == grouping.cacheSize;
+ }
+
+ @Override
+ public String toString() {
+ return packageName + " " + appSize + " " + cacheSize;
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java b/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java
new file mode 100644
index 0000000..357ce74
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.PackageStats;
+import android.test.AndroidTestCase;
+
+import com.android.server.storage.DiskStatsLoggingService.LogRunnable;
+
+import libcore.io.IoUtils;
+
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.util.ArrayList;
+
+@RunWith(JUnit4.class)
+public class DiskStatsLoggingServiceTest extends AndroidTestCase {
+ @Rule public TemporaryFolder mTemporaryFolder;
+ @Rule public TemporaryFolder mDownloads;
+ @Rule public TemporaryFolder mRootDirectory;
+ @Mock private AppCollector mCollector;
+ private File mInputFile;
+
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ mTemporaryFolder = new TemporaryFolder();
+ mTemporaryFolder.create();
+ mInputFile = mTemporaryFolder.newFile();
+ mDownloads = new TemporaryFolder();
+ mDownloads.create();
+ mRootDirectory = new TemporaryFolder();
+ mRootDirectory.create();
+ }
+
+ @Test
+ public void testEmptyLog() throws Exception {
+ LogRunnable task = new LogRunnable();
+ task.setAppCollector(mCollector);
+ task.setDownloadsDirectory(mDownloads.getRoot());
+ task.setRootDirectory(mRootDirectory.getRoot());
+ task.setLogOutputFile(mInputFile);
+ task.setSystemSize(0L);
+ task.run();
+
+ JSONObject json = getJsonOutput();
+ assertThat(json.getLong(DiskStatsFileLogger.PHOTOS_KEY)).isEqualTo(0L);
+ assertThat(json.getLong(DiskStatsFileLogger.VIDEOS_KEY)).isEqualTo(0L);
+ assertThat(json.getLong(DiskStatsFileLogger.AUDIO_KEY)).isEqualTo(0L);
+ assertThat(json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)).isEqualTo(0L);
+ assertThat(json.getLong(DiskStatsFileLogger.SYSTEM_KEY)).isEqualTo(0L);
+ assertThat(json.getLong(DiskStatsFileLogger.MISC_KEY)).isEqualTo(0L);
+ assertThat(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(0L);
+ assertThat(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(0L);
+ assertThat(
+ json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY).length()).isEqualTo(0L);
+ assertThat(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY).length()).isEqualTo(0L);
+ assertThat(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY).length()).isEqualTo(0L);
+ }
+
+ @Test
+ public void testPopulatedLogTask() throws Exception {
+ // Write data to directories.
+ writeDataToFile(mDownloads.newFile(), "lol");
+ writeDataToFile(mRootDirectory.newFile("test.jpg"), "1234");
+ writeDataToFile(mRootDirectory.newFile("test.mp4"), "12345");
+ writeDataToFile(mRootDirectory.newFile("test.mp3"), "123456");
+ writeDataToFile(mRootDirectory.newFile("test.whatever"), "1234567");
+
+ // Write apps.
+ ArrayList<PackageStats> apps = new ArrayList<>();
+ PackageStats testApp = new PackageStats("com.test.app");
+ testApp.dataSize = 5L;
+ testApp.cacheSize = 55L;
+ testApp.codeSize = 10L;
+ apps.add(testApp);
+ when(mCollector.getPackageStats(anyInt())).thenReturn(apps);
+
+ LogRunnable task = new LogRunnable();
+ task.setAppCollector(mCollector);
+ task.setDownloadsDirectory(mDownloads.getRoot());
+ task.setRootDirectory(mRootDirectory.getRoot());
+ task.setLogOutputFile(mInputFile);
+ task.setSystemSize(10L);
+ task.run();
+
+ JSONObject json = getJsonOutput();
+ assertThat(json.getLong(DiskStatsFileLogger.PHOTOS_KEY)).isEqualTo(4L);
+ assertThat(json.getLong(DiskStatsFileLogger.VIDEOS_KEY)).isEqualTo(5L);
+ assertThat(json.getLong(DiskStatsFileLogger.AUDIO_KEY)).isEqualTo(6L);
+ assertThat(json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)).isEqualTo(3L);
+ assertThat(json.getLong(DiskStatsFileLogger.SYSTEM_KEY)).isEqualTo(10L);
+ assertThat(json.getLong(DiskStatsFileLogger.MISC_KEY)).isEqualTo(7L);
+ assertThat(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(15L);
+ assertThat(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(55L);
+ assertThat(
+ json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY).length()).isEqualTo(1L);
+ assertThat(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY).length()).isEqualTo(1L);
+ assertThat(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY).length()).isEqualTo(1L);
+ }
+
+ private void writeDataToFile(File f, String data) throws Exception{
+ PrintStream out = new PrintStream(f);
+ out.print(data);
+ out.close();
+ }
+
+ private JSONObject getJsonOutput() throws Exception {
+ return new JSONObject(IoUtils.readFileAsString(mInputFile.getAbsolutePath()));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/storage/FileCollectorTest.java b/services/tests/servicestests/src/com/android/server/storage/FileCollectorTest.java
new file mode 100644
index 0000000..f1b3442
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/storage/FileCollectorTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.storage;
+
+import android.test.AndroidTestCase;
+import com.android.server.storage.FileCollector.MeasurementResult;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.PrintStream;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(JUnit4.class)
+public class FileCollectorTest extends AndroidTestCase {
+ @Rule
+ public TemporaryFolder temporaryFolder;
+
+ @Before
+ public void setUp() throws Exception {
+ temporaryFolder = new TemporaryFolder();
+ temporaryFolder.create();
+ }
+
+ @Test
+ public void testEmpty() throws Exception {
+ MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot());
+ assertThat(result.totalAccountedSize()).isEqualTo(0L);
+ }
+
+ @Test
+ public void testImageFile() throws Exception {
+ writeDataToFile(temporaryFolder.newFile("test.jpg"), "1234");
+
+ MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot());
+
+ assertThat(result.imagesSize).isEqualTo(4);
+ }
+
+ @Test
+ public void testVideoFile() throws Exception {
+ writeDataToFile(temporaryFolder.newFile("test.mp4"), "1234");
+
+ MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot());
+
+ assertThat(result.videosSize).isEqualTo(4);
+ }
+
+ @Test
+ public void testAudioFile() throws Exception {
+ writeDataToFile(temporaryFolder.newFile("test.mp3"), "1234");
+
+ MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot());
+
+ assertThat(result.audioSize).isEqualTo(4);
+ }
+
+ @Test
+ public void testMiscFile() throws Exception {
+ writeDataToFile(temporaryFolder.newFile("test"), "1234");
+
+ MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot());
+
+ assertThat(result.miscSize).isEqualTo(4);
+ }
+
+ @Test
+ public void testNestedFile() throws Exception {
+ File directory = temporaryFolder.newFolder();
+ writeDataToFile(new File(directory, "test"), "1234");
+
+ MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot());
+
+ assertThat(result.miscSize).isEqualTo(4);
+ }
+
+ @Test
+ public void testMultipleFiles() throws Exception {
+ writeDataToFile(temporaryFolder.newFile("test"), "1234");
+ writeDataToFile(temporaryFolder.newFile("test2"), "12345");
+
+ MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot());
+
+ assertThat(result.miscSize).isEqualTo(9);
+ }
+
+ @Test
+ public void testTotalSize() throws Exception {
+ writeDataToFile(temporaryFolder.newFile("test.jpg"), "1");
+ writeDataToFile(temporaryFolder.newFile("test.mp3"), "1");
+ writeDataToFile(temporaryFolder.newFile("test.mp4"), "1");
+ writeDataToFile(temporaryFolder.newFile("test"), "1");
+
+ MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot());
+
+ assertThat(result.totalAccountedSize()).isEqualTo(4);
+ }
+
+ @Test
+ public void testFileEndsWithPeriod() throws Exception {
+ writeDataToFile(temporaryFolder.newFile("test."), "1");
+
+ MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot());
+
+ assertThat(result.miscSize).isEqualTo(1);
+ assertThat(result.totalAccountedSize()).isEqualTo(1);
+ }
+
+ public void testIgnoreFileExtensionCase() throws Exception {
+ writeDataToFile(temporaryFolder.newFile("test.JpG"), "1234");
+
+ MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot());
+
+ assertThat(result.imagesSize).isEqualTo(4);
+ }
+
+ private void writeDataToFile(File f, String data) throws Exception{
+ PrintStream out = new PrintStream(f);
+ out.print(data);
+ out.close();
+ }
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 8f2a44c..54f15e1 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1165,6 +1165,18 @@
public static final String KEY_EDITABLE_TETHER_APN_BOOL =
"editable_tether_apn_bool";
+ /**
+ * An array containing custom call forwarding number prefixes that will be blocked while the
+ * device is reporting that it is roaming. By default, there are no custom call
+ * forwarding prefixes and none of these numbers will be filtered. If one or more entries are
+ * present, the system will not complete the call and display an error message.
+ *
+ * To display a message to the user when call forwarding fails for 3gpp MMI codes while roaming,
+ * use the {@link #KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL} option instead.
+ */
+ public static final String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY =
+ "call_forwarding_blocks_while_roaming_string_array";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -1370,6 +1382,8 @@
sDefaults.putInt(KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT, -1);
sDefaults.putBoolean(KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true);
sDefaults.putBoolean(KEY_EDITABLE_TETHER_APN_BOOL, false);
+ sDefaults.putStringArray(KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY,
+ null);
}
/**
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 0334254..989f72c 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -233,6 +233,12 @@
*/
public static final int DIALED_ON_WRONG_SLOT = 56;
+ /**
+ * The call being placed was detected as a call forwarding number and was being dialed while
+ * roaming on a carrier that does not allow this.
+ */
+ public static final int DIALED_CALL_FORWARDING_WHILE_ROAMING = 57;
+
//*********************************************************************************************
// When adding a disconnect type:
// 1) Please assign the new type the next id value below.
@@ -241,14 +247,14 @@
// 4) Update toString() with the newly added disconnect type.
// 5) Update android.telecom.DisconnectCauseUtil with any mappings to a telecom.DisconnectCause.
//
- // NextId: 57
+ // NextId: 58
//*********************************************************************************************
/** Smallest valid value for call disconnect codes. */
public static final int MINIMUM_VALID_VALUE = NOT_DISCONNECTED;
/** Largest valid value for call disconnect codes. */
- public static final int MAXIMUM_VALID_VALUE = DIALED_ON_WRONG_SLOT;
+ public static final int MAXIMUM_VALID_VALUE = DIALED_CALL_FORWARDING_WHILE_ROAMING;
/** Private constructor to avoid class instantiation. */
private DisconnectCause() {
@@ -370,6 +376,8 @@
return "DATA_LIMIT_REACHED";
case DIALED_ON_WRONG_SLOT:
return "DIALED_ON_WRONG_SLOT";
+ case DIALED_CALL_FORWARDING_WHILE_ROAMING:
+ return "DIALED_CALL_FORWARDING_WHILE_ROAMING";
default:
return "INVALID: " + cause;
}
diff --git a/tools/layoutlib/.idea/modules.xml b/tools/layoutlib/.idea/modules.xml
index 9bdc381..6ffc1cc 100644
--- a/tools/layoutlib/.idea/modules.xml
+++ b/tools/layoutlib/.idea/modules.xml
@@ -4,6 +4,7 @@
<modules>
<module fileurl="file://$PROJECT_DIR$/bridge/bridge.iml" filepath="$PROJECT_DIR$/bridge/bridge.iml" />
<module fileurl="file://$PROJECT_DIR$/create/create.iml" filepath="$PROJECT_DIR$/create/create.iml" />
+ <module fileurl="file://$PROJECT_DIR$/legacy/legacy.iml" filepath="$PROJECT_DIR$/legacy/legacy.iml" />
<module fileurl="file://$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" filepath="$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" />
</modules>
</component>
diff --git a/tools/layoutlib/legacy/Android.mk b/tools/layoutlib/legacy/Android.mk
new file mode 100644
index 0000000..5855f89
--- /dev/null
+++ b/tools/layoutlib/legacy/Android.mk
@@ -0,0 +1,30 @@
+#
+# Copyright (C) 2008 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.
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+
+LOCAL_JAVA_LIBRARIES := \
+ layoutlib_api-prebuilt
+
+LOCAL_MODULE := layoutlib-legacy
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/tools/layoutlib/legacy/legacy.iml b/tools/layoutlib/legacy/legacy.iml
new file mode 100644
index 0000000..a167a75
--- /dev/null
+++ b/tools/layoutlib/legacy/legacy.iml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="layoutlib_api-prebuilt" level="project" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/tools/layoutlib/legacy/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/legacy/src/com/android/layoutlib/bridge/Bridge.java
new file mode 100644
index 0000000..0cfc181
--- /dev/null
+++ b/tools/layoutlib/legacy/src/com/android/layoutlib/bridge/Bridge.java
@@ -0,0 +1,75 @@
+/*
+ * 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.layoutlib.bridge;import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.Result.Status;
+import com.android.ide.common.rendering.api.SessionParams;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+
+/**
+ * Legacy Bridge used in the SDK version of layoutlib
+ */
+public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
+ private static final String SDK_NOT_SUPPORTED = "The SDK layoutlib version is not supported";
+ private static final Result NOT_SUPPORTED_RESULT =
+ Status.NOT_IMPLEMENTED.createResult(SDK_NOT_SUPPORTED);
+ private static BufferedImage sImage;
+
+ private static class BridgeRenderSession extends RenderSession {
+
+ @Override
+ public synchronized BufferedImage getImage() {
+ if (sImage == null) {
+ sImage = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = sImage.createGraphics();
+ g.clearRect(0, 0, 500, 500);
+ g.drawString(SDK_NOT_SUPPORTED, 20, 20);
+ g.dispose();
+ }
+
+ return sImage;
+ }
+
+ @Override
+ public Result render(long timeout, boolean forceMeasure) {
+ return NOT_SUPPORTED_RESULT;
+ }
+
+ @Override
+ public Result measure(long timeout) {
+ return NOT_SUPPORTED_RESULT;
+ }
+
+ @Override
+ public Result getResult() {
+ return NOT_SUPPORTED_RESULT;
+ }
+ }
+
+
+ @Override
+ public RenderSession createSession(SessionParams params) {
+ return new BridgeRenderSession();
+ }
+
+ @Override
+ public int getApiLevel() {
+ return 0;
+ }
+}
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java
index 21b7a04..84ec8b7 100644
--- a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java
@@ -171,6 +171,9 @@
arg1.getDevice().getSyncService().pullFile(arg0,
target.getAbsoluteFile().toString(), new NullProgressMonitor());
} catch (Exception e) {
+ if (target != null) {
+ target.delete();
+ }
e.printStackTrace();
target = null;
}
@@ -189,6 +192,9 @@
out.write(arg0);
out.close();
} catch (Exception e) {
+ if (target != null) {
+ target.delete();
+ }
e.printStackTrace();
target = null;
}
@@ -215,6 +221,8 @@
return analyzeHprof(hprofLocalFile);
} catch (Exception e) {
throw new RuntimeException(e);
+ } finally {
+ hprofLocalFile.delete();
}
}
}
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index da87135..da9aa06 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -16,6 +16,7 @@
package android.net.wifi;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -259,10 +260,10 @@
public long blackListTimestamp;
/**
- * Status: indicating the scan result is not a result
- * that is part of user's saved configurations
+ * Status indicating the scan result does not correspond to a user's saved configuration
* @hide
*/
+ @SystemApi
public boolean untrusted;
/**
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 43e6246..3b7f721 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -701,6 +701,7 @@
* {@link com.android.server.wifi.WifiStateMachine}, or via a network score in
* {@link com.android.server.wifi.ExternalScoreEvaluator}.
*/
+ @SystemApi
public boolean meteredHint;
/**
diff --git a/wifi/java/android/net/wifi/WifiNetworkScoreCache.java b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
index c328748..9dd118b 100755
--- a/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
+++ b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
@@ -17,13 +17,19 @@
package android.net.wifi;
import android.Manifest.permission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
+import android.os.Handler;
import android.net.INetworkScoreCache;
import android.net.NetworkKey;
import android.net.ScoredNetwork;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;
@@ -43,30 +49,55 @@
// We treat the lowest possible score as though there were no score, effectively allowing the
// scorer to provide an RSSI threshold below which a network should not be used.
public static final int INVALID_NETWORK_SCORE = Byte.MIN_VALUE;
+
+ // See {@link #CacheListener}.
+ @Nullable
+ @GuardedBy("mCacheLock")
+ private CacheListener mListener;
+
private final Context mContext;
+ private final Object mCacheLock = new Object();
// The key is of the form "<ssid>"<bssid>
// TODO: What about SSIDs that can't be encoded as UTF-8?
private final Map<String, ScoredNetwork> mNetworkCache;
+
public WifiNetworkScoreCache(Context context) {
- mContext = context;
+ this(context, null /* listener */);
+ }
+
+ /**
+ * Instantiates a WifiNetworkScoreCache.
+ *
+ * @param context Application context
+ * @param listener CacheListener for cache updates
+ */
+ public WifiNetworkScoreCache(Context context, @Nullable CacheListener listener) {
+ mContext = context.getApplicationContext();
+ mListener = listener;
mNetworkCache = new HashMap<String, ScoredNetwork>();
}
@Override public final void updateScores(List<ScoredNetwork> networks) {
- if (networks == null) {
+ if (networks == null || networks.isEmpty()) {
return;
- }
- Log.e(TAG, "updateScores list size=" + networks.size());
+ }
+ Log.d(TAG, "updateScores list size=" + networks.size());
- synchronized(mNetworkCache) {
- for (ScoredNetwork network : networks) {
- String networkKey = buildNetworkKey(network);
- if (networkKey == null) continue;
- mNetworkCache.put(networkKey, network);
- }
- }
+ synchronized(mNetworkCache) {
+ for (ScoredNetwork network : networks) {
+ String networkKey = buildNetworkKey(network);
+ if (networkKey == null) continue;
+ mNetworkCache.put(networkKey, network);
+ }
+ }
+
+ synchronized (mCacheLock) {
+ if (mListener != null) {
+ mListener.post(networks);
+ }
+ }
}
@Override public final void clearScores() {
@@ -193,4 +224,53 @@
}
}
+ /** Registers a CacheListener instance, replacing the previous listener if it existed. */
+ public void registerListener(CacheListener listener) {
+ synchronized (mCacheLock) {
+ mListener = listener;
+ }
+ }
+
+ /** Removes the registered CacheListener. */
+ public void unregisterListener() {
+ synchronized (mCacheLock) {
+ mListener = null;
+ }
+ }
+
+ /** Listener for updates to the cache inside WifiNetworkScoreCache. */
+ public abstract static class CacheListener {
+
+ private Handler mHandler;
+
+ /**
+ * Constructor for CacheListener.
+ *
+ * @param handler the Handler on which to invoke the {@link #networkCacheUpdated} method.
+ * This cannot be null.
+ */
+ public CacheListener(@NonNull Handler handler) {
+ Preconditions.checkNotNull(handler);
+ mHandler = handler;
+ }
+
+ /** Invokes the {@link #networkCacheUpdated(List<ScoredNetwork>)} method on the handler. */
+ void post(List<ScoredNetwork> updatedNetworks) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ networkCacheUpdated(updatedNetworks);
+ }
+ });
+ }
+
+ /**
+ * Invoked whenever the cache is updated.
+ *
+ * <p>Clearing the cache does not invoke this method.
+ *
+ * @param updatedNetworks the networks that were updated
+ */
+ public abstract void networkCacheUpdated(List<ScoredNetwork> updatedNetworks);
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java
index f8549b9..12d4995 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java
@@ -17,6 +17,8 @@
package android.net.wifi;
import static org.junit.Assert.*;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -24,6 +26,9 @@
import android.net.RssiCurve;
import android.net.ScoredNetwork;
import android.net.WifiKey;
+import android.net.wifi.WifiNetworkScoreCache.CacheListener;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -33,124 +38,167 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+
/** Unit tests for {@link WifiNetworkScoreCache}. */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class WifiNetworkScoreCacheTest {
- @Mock public Context mockContext; // isn't used, can be null
- @Mock private RssiCurve mockRssiCurve;
+ public static final String SSID = "ssid";
+ public static final String FORMATTED_SSID = "\"" + SSID + "\"";
+ public static final String BSSID = "AA:AA:AA:AA:AA:AA";
- public static final String SSID = "ssid";
- public static final String FORMATTED_SSID = "\"" + SSID + "\"";
- public static final String BSSID = "AA:AA:AA:AA:AA:AA";
+ public static final WifiKey VALID_KEY = new WifiKey(FORMATTED_SSID, BSSID);
- public static final WifiKey VALID_KEY = new WifiKey(FORMATTED_SSID, BSSID);
+ public static final ScanResult VALID_SCAN_RESULT = buildScanResult(SSID, BSSID);
- public static final ScanResult VALID_SCAN_RESULT = buildScanResult(SSID, BSSID);
-
- private ScoredNetwork mValidScoredNetwork;
- private WifiNetworkScoreCache mScoreCache =
- new WifiNetworkScoreCache(mockContext);
-
- private static ScanResult buildScanResult(String ssid, String bssid) {
- return new ScanResult(
- WifiSsid.createFromAsciiEncoded(ssid),
- bssid,
- "" /* caps */,
- 0 /* level */,
- 0 /* frequency */,
- 0 /* tsf */,
- 0 /* distCm */,
- 0 /* distSdCm*/);
- }
-
- private static ScoredNetwork buildScoredNetwork(WifiKey key, RssiCurve curve) {
- return new ScoredNetwork(new NetworkKey(key), curve);
- }
-
- // Called from setup
- private void initializeCacheWithValidScoredNetwork() {
- mScoreCache.updateScores(ImmutableList.of(mValidScoredNetwork));
- }
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mValidScoredNetwork = buildScoredNetwork(VALID_KEY, mockRssiCurve);
- mScoreCache = new WifiNetworkScoreCache(mockContext);
- initializeCacheWithValidScoredNetwork();
- }
+ @Mock private Context mockApplicationContext;
+ @Mock private Context mockContext; // isn't used, can be null
+ @Mock private RssiCurve mockRssiCurve;
- @Test
- public void isScoredNetworkShouldReturnTrueAfterUpdateScoresIsCalled() {
- assertTrue(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
- }
+ private CacheListener mCacheListener;
+ private CountDownLatch mLatch;
+ private Handler mHandler;
+ private List<ScoredNetwork> mUpdatedNetworksCaptor;
+ private ScoredNetwork mValidScoredNetwork;
+ private WifiNetworkScoreCache mScoreCache =
+ new WifiNetworkScoreCache(mockContext);
- @Test
- public void isScoredNetworkShouldReturnFalseAfterClearScoresIsCalled() {
- mScoreCache.clearScores();
- assertFalse(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
- }
+ private static ScanResult buildScanResult(String ssid, String bssid) {
+ return new ScanResult(
+ WifiSsid.createFromAsciiEncoded(ssid),
+ bssid,
+ "" /* caps */,
+ 0 /* level */,
+ 0 /* frequency */,
+ 0 /* tsf */,
+ 0 /* distCm */,
+ 0 /* distSdCm*/);
+ }
- @Test
- public void updateScoresShouldAddNewNetwork() {
- WifiKey key2 = new WifiKey("\"ssid2\"", BSSID);
- ScoredNetwork network2 = buildScoredNetwork(key2, mockRssiCurve);
- ScanResult result2 = buildScanResult("ssid2", BSSID);
+ private static ScoredNetwork buildScoredNetwork(WifiKey key, RssiCurve curve) {
+ return new ScoredNetwork(new NetworkKey(key), curve);
+ }
- mScoreCache.updateScores(ImmutableList.of(network2));
+ // Called from setup
+ private void initializeCacheWithValidScoredNetwork() {
+ mScoreCache.updateScores(ImmutableList.of(mValidScoredNetwork));
+ }
- assertTrue(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
- assertTrue(mScoreCache.isScoredNetwork(result2));
- }
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
- @Test
- public void hasScoreCurveShouldReturnTrue() {
- assertTrue(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT));
- }
+ when(mockContext.getApplicationContext()).thenReturn(mockApplicationContext);
- @Test
- public void hasScoreCurveShouldReturnFalseWhenNoCachedNetwork() {
- ScanResult unscored = buildScanResult("fake", BSSID);
- assertFalse(mScoreCache.hasScoreCurve(unscored));
- }
+ mValidScoredNetwork = buildScoredNetwork(VALID_KEY, mockRssiCurve);
+ mScoreCache = new WifiNetworkScoreCache(mockContext);
+ initializeCacheWithValidScoredNetwork();
- @Test
- public void hasScoreCurveShouldReturnFalseWhenScoredNetworkHasNoCurve() {
- ScoredNetwork noCurve = buildScoredNetwork(VALID_KEY, null /* rssiCurve */);
- mScoreCache.updateScores(ImmutableList.of(noCurve));
+ HandlerThread thread = new HandlerThread("WifiNetworkScoreCacheTest Handler Thread");
+ thread.start();
+ mHandler = new Handler(thread.getLooper());
+ mLatch = new CountDownLatch(1);
+ mCacheListener = new CacheListener(mHandler) {
+ @Override
+ public void networkCacheUpdated(List<ScoredNetwork> updatedNetworks) {
+ mUpdatedNetworksCaptor = updatedNetworks;
+ mLatch.countDown();
+ }
+ };
+ }
- assertFalse(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT));
- }
- @Test
- public void getNetworkScoreShouldReturnScore() {
- final byte score = 50;
- final int rssi = -70;
- ScanResult result = new ScanResult(VALID_SCAN_RESULT);
- result.level = rssi;
+ @Test
+ public void isScoredNetworkShouldReturnTrueAfterUpdateScoresIsCalled() {
+ assertTrue(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
+ }
- when(mockRssiCurve.lookupScore(rssi)).thenReturn(score);
+ @Test
+ public void isScoredNetworkShouldReturnFalseAfterClearScoresIsCalled() {
+ mScoreCache.clearScores();
+ assertFalse(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
+ }
- assertEquals(score, mScoreCache.getNetworkScore(result));
- }
+ @Test
+ public void updateScoresShouldAddNewNetwork() {
+ WifiKey key2 = new WifiKey("\"ssid2\"", BSSID);
+ ScoredNetwork network2 = buildScoredNetwork(key2, mockRssiCurve);
+ ScanResult result2 = buildScanResult("ssid2", BSSID);
- @Test
- public void getMeteredHintShouldReturnFalse() {
- assertFalse(mScoreCache.getMeteredHint(VALID_SCAN_RESULT));
- }
+ mScoreCache.updateScores(ImmutableList.of(network2));
- @Test
- public void getMeteredHintShouldReturnTrue() {
- ScoredNetwork network =
- new ScoredNetwork(new NetworkKey(VALID_KEY), mockRssiCurve, true /* metered Hint */);
- mScoreCache.updateScores(ImmutableList.of(network));
+ assertTrue(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
+ assertTrue(mScoreCache.isScoredNetwork(result2));
+ }
- assertTrue(mScoreCache.getMeteredHint(VALID_SCAN_RESULT));
- }
+ @Test
+ public void hasScoreCurveShouldReturnTrue() {
+ assertTrue(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT));
+ }
+
+ @Test
+ public void hasScoreCurveShouldReturnFalseWhenNoCachedNetwork() {
+ ScanResult unscored = buildScanResult("fake", BSSID);
+ assertFalse(mScoreCache.hasScoreCurve(unscored));
+ }
+
+ @Test
+ public void hasScoreCurveShouldReturnFalseWhenScoredNetworkHasNoCurve() {
+ ScoredNetwork noCurve = buildScoredNetwork(VALID_KEY, null /* rssiCurve */);
+ mScoreCache.updateScores(ImmutableList.of(noCurve));
+
+ assertFalse(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT));
+ }
+
+ @Test
+ public void getNetworkScoreShouldReturnScore() {
+ final byte score = 50;
+ final int rssi = -70;
+ ScanResult result = new ScanResult(VALID_SCAN_RESULT);
+ result.level = rssi;
+
+ when(mockRssiCurve.lookupScore(rssi)).thenReturn(score);
+
+ assertEquals(score, mScoreCache.getNetworkScore(result));
+ }
+
+ @Test
+ public void getMeteredHintShouldReturnFalse() {
+ assertFalse(mScoreCache.getMeteredHint(VALID_SCAN_RESULT));
+ }
+
+ @Test
+ public void getMeteredHintShouldReturnTrue() {
+ ScoredNetwork network =
+ new ScoredNetwork(
+ new NetworkKey(VALID_KEY), mockRssiCurve, true /* metered Hint */);
+ mScoreCache.updateScores(ImmutableList.of(network));
+
+ assertTrue(mScoreCache.getMeteredHint(VALID_SCAN_RESULT));
+ }
+
+ @Test
+ public void updateScoresShouldInvokeCacheListener_networkCacheUpdated() {
+ mScoreCache = new WifiNetworkScoreCache(mockContext, mCacheListener);
+ initializeCacheWithValidScoredNetwork();
+
+ try {
+ mLatch.await(1, TimeUnit.SECONDS); // wait for listener to be executed
+ } catch (InterruptedException e) {
+ fail("Interrupted Exception while waiting for listener to be invoked.");
+ }
+ assertEquals("One network should be updated", 1, mUpdatedNetworksCaptor.size());
+ assertEquals(mValidScoredNetwork, mUpdatedNetworksCaptor.get(0));
+ }
}