Merge "MacAddress: address api review comments"
diff --git a/api/current.txt b/api/current.txt
index bd0887e..d40c0d7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -32264,6 +32264,7 @@
method public deprecated void setUserRestrictions(android.os.Bundle);
method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle);
method public static boolean supportsMultipleUsers();
+ method public boolean trySetQuietModeEnabled(boolean, android.os.UserHandle);
field public static final java.lang.String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
field public static final java.lang.String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
@@ -48635,6 +48636,7 @@
method public void cancel();
method public void commit();
method public void disableAutofillServices();
+ method public android.content.ComponentName getAutofillServiceComponentName();
method public android.service.autofill.UserData getUserData();
method public boolean hasEnabledAutofillServices();
method public boolean isAutofillSupported();
diff --git a/api/system-current.txt b/api/system-current.txt
index 30530a3..ec509ad 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -103,6 +103,7 @@
field public static final deprecated java.lang.String MODIFY_NETWORK_ACCOUNTING = "android.permission.MODIFY_NETWORK_ACCOUNTING";
field public static final java.lang.String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS";
field public static final java.lang.String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
+ field public static final java.lang.String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE";
field public static final java.lang.String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS";
field public static final java.lang.String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS";
field public static final java.lang.String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 6da6fb7..b7ce875 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -291,7 +291,7 @@
public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
Log.e(TAG, "Received a query callback on a non-query request");
transaction.setResponse(new ContextHubTransaction.Response<Void>(
- ContextHubTransaction.TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE, null));
+ ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE, null));
}
@Override
@@ -323,7 +323,7 @@
public void onTransactionComplete(int result) {
Log.e(TAG, "Received a non-query callback on a query request");
transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
- ContextHubTransaction.TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE, null));
+ ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE, null));
}
};
}
diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java
index 2a66cbc..ec1e68f 100644
--- a/core/java/android/hardware/location/ContextHubTransaction.java
+++ b/core/java/android/hardware/location/ContextHubTransaction.java
@@ -34,7 +34,7 @@
* through the ContextHubManager APIs. The caller can either retrieve the result
* synchronously through a blocking call ({@link #waitForResponse(long, TimeUnit)}) or
* asynchronously through a user-defined callback
- * ({@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}).
+ * ({@link #setOnCompleteCallback(ContextHubTransaction.Callback, Handler)}).
*
* @param <T> the type of the contents in the transaction response
*
@@ -66,51 +66,51 @@
* Constants describing the result of a transaction or request through the Context Hub Service.
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "TRANSACTION_" }, value = {
- TRANSACTION_SUCCESS,
- TRANSACTION_FAILED_UNKNOWN,
- TRANSACTION_FAILED_BAD_PARAMS,
- TRANSACTION_FAILED_UNINITIALIZED,
- TRANSACTION_FAILED_PENDING,
- TRANSACTION_FAILED_AT_HUB,
- TRANSACTION_FAILED_TIMEOUT,
- TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE,
- TRANSACTION_FAILED_HAL_UNAVAILABLE
+ @IntDef(prefix = { "RESULT_" }, value = {
+ RESULT_SUCCESS,
+ RESULT_FAILED_UNKNOWN,
+ RESULT_FAILED_BAD_PARAMS,
+ RESULT_FAILED_UNINITIALIZED,
+ RESULT_FAILED_PENDING,
+ RESULT_FAILED_AT_HUB,
+ RESULT_FAILED_TIMEOUT,
+ RESULT_FAILED_SERVICE_INTERNAL_FAILURE,
+ RESULT_FAILED_HAL_UNAVAILABLE
})
public @interface Result {}
- public static final int TRANSACTION_SUCCESS = 0;
+ public static final int RESULT_SUCCESS = 0;
/**
* Generic failure mode.
*/
- public static final int TRANSACTION_FAILED_UNKNOWN = 1;
+ public static final int RESULT_FAILED_UNKNOWN = 1;
/**
* Failure mode when the request parameters were not valid.
*/
- public static final int TRANSACTION_FAILED_BAD_PARAMS = 2;
+ public static final int RESULT_FAILED_BAD_PARAMS = 2;
/**
* Failure mode when the Context Hub is not initialized.
*/
- public static final int TRANSACTION_FAILED_UNINITIALIZED = 3;
+ public static final int RESULT_FAILED_UNINITIALIZED = 3;
/**
* Failure mode when there are too many transactions pending.
*/
- public static final int TRANSACTION_FAILED_PENDING = 4;
+ public static final int RESULT_FAILED_PENDING = 4;
/**
* Failure mode when the request went through, but failed asynchronously at the hub.
*/
- public static final int TRANSACTION_FAILED_AT_HUB = 5;
+ public static final int RESULT_FAILED_AT_HUB = 5;
/**
* Failure mode when the transaction has timed out.
*/
- public static final int TRANSACTION_FAILED_TIMEOUT = 6;
+ public static final int RESULT_FAILED_TIMEOUT = 6;
/**
* Failure mode when the transaction has failed internally at the service.
*/
- public static final int TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE = 7;
+ public static final int RESULT_FAILED_SERVICE_INTERNAL_FAILURE = 7;
/**
* Failure mode when the Context Hub HAL was not available.
*/
- public static final int TRANSACTION_FAILED_HAL_UNAVAILABLE = 8;
+ public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8;
/**
* A class describing the response for a ContextHubTransaction.
@@ -271,7 +271,7 @@
* A transaction can be invalidated if the process owning the transaction is no longer active
* and the reference to this object is lost.
*
- * This method or {@link #setCallbackOnComplete(ContextHubTransaction.Callback)} can only be
+ * This method or {@link #setOnCompleteCallback(ContextHubTransaction.Callback)} can only be
* invoked once, or an IllegalStateException will be thrown.
*
* @param callback the callback to be invoked upon completion
@@ -280,7 +280,7 @@
* @throws IllegalStateException if this method is called multiple times
* @throws NullPointerException if the callback or handler is null
*/
- public void setCallbackOnComplete(
+ public void setOnCompleteCallback(
@NonNull ContextHubTransaction.Callback<T> callback, @NonNull Handler handler) {
synchronized (this) {
if (callback == null) {
@@ -312,10 +312,10 @@
/**
* Sets a callback to be invoked when the transaction completes.
*
- * Equivalent to {@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}
+ * Equivalent to {@link #setOnCompleteCallback(ContextHubTransaction.Callback, Handler)}
* with the handler being that of the main thread's Looper.
*
- * This method or {@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}
+ * This method or {@link #setOnCompleteCallback(ContextHubTransaction.Callback, Handler)}
* can only be invoked once, or an IllegalStateException will be thrown.
*
* @param callback the callback to be invoked upon completion
@@ -323,8 +323,8 @@
* @throws IllegalStateException if this method is called multiple times
* @throws NullPointerException if the callback is null
*/
- public void setCallbackOnComplete(@NonNull ContextHubTransaction.Callback<T> callback) {
- setCallbackOnComplete(callback, new Handler(Looper.getMainLooper()));
+ public void setOnCompleteCallback(@NonNull ContextHubTransaction.Callback<T> callback) {
+ setOnCompleteCallback(callback, new Handler(Looper.getMainLooper()));
}
/**
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 9513b9b..430c28b 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -16,6 +16,7 @@
package android.os;
+import android.app.ActivityManager;
import android.app.job.JobParameters;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -33,6 +34,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.Display;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatteryStatsHelper;
@@ -327,7 +329,8 @@
*
* Other types might include times spent in foreground, background etc.
*/
- private final String UID_TIMES_TYPE_ALL = "A";
+ @VisibleForTesting
+ public static final String UID_TIMES_TYPE_ALL = "A";
/**
* State for keeping track of counting information.
@@ -507,6 +510,31 @@
}
/**
+ * Maps the ActivityManager procstate into corresponding BatteryStats procstate.
+ */
+ public static int mapToInternalProcessState(int procState) {
+ if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ return ActivityManager.PROCESS_STATE_NONEXISTENT;
+ } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
+ return Uid.PROCESS_STATE_TOP;
+ } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ // Persistent and other foreground states go here.
+ return Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+ } else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ // Persistent and other foreground states go here.
+ return Uid.PROCESS_STATE_FOREGROUND;
+ } else if (procState <= ActivityManager.PROCESS_STATE_RECEIVER) {
+ return Uid.PROCESS_STATE_BACKGROUND;
+ } else if (procState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+ return Uid.PROCESS_STATE_TOP_SLEEPING;
+ } else if (procState <= ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+ return Uid.PROCESS_STATE_HEAVY_WEIGHT;
+ } else {
+ return Uid.PROCESS_STATE_CACHED;
+ }
+ }
+
+ /**
* The statistics associated with a particular uid.
*/
public static abstract class Uid {
@@ -645,6 +673,15 @@
public abstract long[] getCpuFreqTimes(int which);
public abstract long[] getScreenOffCpuFreqTimes(int which);
+ /**
+ * Returns cpu times of an uid at a particular process state.
+ */
+ public abstract long[] getCpuFreqTimes(int which, int procState);
+ /**
+ * Returns cpu times of an uid while the screen if off at a particular process state.
+ */
+ public abstract long[] getScreenOffCpuFreqTimes(int which, int procState);
+
// Note: the following times are disjoint. They can be added together to find the
// total time a uid has had any processes running at all.
@@ -689,11 +726,32 @@
*/
public static final int NUM_PROCESS_STATE = 7;
+ // Used in dump
static final String[] PROCESS_STATE_NAMES = {
"Top", "Fg Service", "Foreground", "Background", "Top Sleeping", "Heavy Weight",
"Cached"
};
+ // Used in checkin dump
+ @VisibleForTesting
+ public static final String[] UID_PROCESS_TYPES = {
+ "T", // TOP
+ "FS", // FOREGROUND_SERVICE
+ "F", // FOREGROUND
+ "B", // BACKGROUND
+ "TS", // TOP_SLEEPING
+ "HW", // HEAVY_WEIGHT
+ "C" // CACHED
+ };
+
+ /**
+ * When the process exits one of these states, we need to make sure cpu time in this state
+ * is not attributed to any non-critical process states.
+ */
+ public static final int[] CRITICAL_PROC_STATES = {
+ PROCESS_STATE_TOP, PROCESS_STATE_FOREGROUND_SERVICE, PROCESS_STATE_FOREGROUND
+ };
+
public abstract long getProcessStateTime(int state, long elapsedRealtimeUs, int which);
public abstract Timer getProcessStateTimer(int state);
@@ -4002,6 +4060,29 @@
dumpLine(pw, uid, category, CPU_TIMES_AT_FREQ_DATA, UID_TIMES_TYPE_ALL,
cpuFreqTimeMs.length, sb.toString());
}
+
+ for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+ final long[] timesMs = u.getCpuFreqTimes(which, procState);
+ if (timesMs != null && timesMs.length == cpuFreqs.length) {
+ sb.setLength(0);
+ for (int i = 0; i < timesMs.length; ++i) {
+ sb.append((i == 0 ? "" : ",") + timesMs[i]);
+ }
+ final long[] screenOffTimesMs = u.getScreenOffCpuFreqTimes(
+ which, procState);
+ if (screenOffTimesMs != null) {
+ for (int i = 0; i < screenOffTimesMs.length; ++i) {
+ sb.append("," + screenOffTimesMs[i]);
+ }
+ } else {
+ for (int i = 0; i < timesMs.length; ++i) {
+ sb.append(",0");
+ }
+ }
+ dumpLine(pw, uid, category, CPU_TIMES_AT_FREQ_DATA,
+ Uid.UID_PROCESS_TYPES[procState], timesMs.length, sb.toString());
+ }
+ }
}
final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats
@@ -5612,6 +5693,30 @@
pw.println(sb.toString());
}
+ for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+ final long[] cpuTimes = u.getCpuFreqTimes(which, procState);
+ if (cpuTimes != null) {
+ sb.setLength(0);
+ sb.append(" Cpu times per freq at state "
+ + Uid.PROCESS_STATE_NAMES[procState] + ":");
+ for (int i = 0; i < cpuTimes.length; ++i) {
+ sb.append(" " + cpuTimes[i]);
+ }
+ pw.println(sb.toString());
+ }
+
+ final long[] screenOffCpuTimes = u.getScreenOffCpuFreqTimes(which, procState);
+ if (screenOffCpuTimes != null) {
+ sb.setLength(0);
+ sb.append(" Screen-off cpu times per freq at state "
+ + Uid.PROCESS_STATE_NAMES[procState] + ":");
+ for (int i = 0; i < screenOffCpuTimes.length; ++i) {
+ sb.append(" " + screenOffCpuTimes[i]);
+ }
+ pw.println(sb.toString());
+ }
+ }
+
final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats
= u.getProcessStats();
for (int ipr=processStats.size()-1; ipr>=0; ipr--) {
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 9c90c38..f643c57 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -79,9 +79,7 @@
void setDefaultGuestRestrictions(in Bundle restrictions);
Bundle getDefaultGuestRestrictions();
boolean markGuestForDeletion(int userHandle);
- void setQuietModeEnabled(int userHandle, boolean enableQuietMode, in IntentSender target);
boolean isQuietModeEnabled(int userHandle);
- boolean trySetQuietModeDisabled(int userHandle, in IntentSender target);
void setSeedAccountData(int userHandle, in String accountName,
in String accountType, in PersistableBundle accountOptions, boolean persist);
String getSeedAccountName();
@@ -99,4 +97,5 @@
boolean isUserRunning(int userId);
boolean isUserNameSet(int userHandle);
boolean hasRestrictedProfiles();
+ boolean trySetQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userHandle, in IntentSender target);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 3504142..75cbd57 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2130,15 +2130,46 @@
}
/**
- * Set quiet mode of a managed profile.
+ * Enables or disables quiet mode for a managed profile. If quiet mode is enabled, apps in a
+ * managed profile don't run, generate notifications, or consume data or battery.
+ * <p>
+ * If a user's credential is needed to turn off quiet mode, a confirm credential screen will be
+ * shown to the user.
+ * <p>
+ * The change may not happen instantly, however apps can listen for
+ * {@link Intent#ACTION_MANAGED_PROFILE_AVAILABLE} and
+ * {@link Intent#ACTION_MANAGED_PROFILE_UNAVAILABLE} broadcasts in order to be notified of
+ * the change of the quiet mode. Apps can also check the current state of quiet mode by
+ * calling {@link #isQuietModeEnabled(UserHandle)}.
+ * <p>
+ * The caller must either be the foreground default launcher or have one of these permissions:
+ * {@code MANAGE_USERS} or {@code MODIFY_QUIET_MODE}.
*
- * @param userHandle The user handle of the profile.
- * @param enableQuietMode Whether quiet mode should be enabled or disabled.
+ * @param enableQuietMode whether quiet mode should be enabled or disabled
+ * @param userHandle user handle of the profile
+ * @return {@code false} if user's credential is needed in order to turn off quiet mode,
+ * {@code true} otherwise
+ * @throws SecurityException if the caller is invalid
+ * @throws IllegalArgumentException if {@code userHandle} is not a managed profile
+ *
+ * @see #isQuietModeEnabled(UserHandle)
+ */
+ public boolean trySetQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) {
+ return trySetQuietModeEnabled(enableQuietMode, userHandle, null);
+ }
+
+ /**
+ * Similar to {@link #trySetQuietModeEnabled(boolean, UserHandle)}, except you can specify
+ * a target to start when user is unlocked.
+ *
+ * @see {@link #trySetQuietModeEnabled(boolean, UserHandle)}
* @hide
*/
- public void setQuietModeEnabled(@UserIdInt int userHandle, boolean enableQuietMode) {
+ public boolean trySetQuietModeEnabled(
+ boolean enableQuietMode, @NonNull UserHandle userHandle, IntentSender target) {
try {
- mService.setQuietModeEnabled(userHandle, enableQuietMode, null);
+ return mService.trySetQuietModeEnabled(
+ mContext.getPackageName(), enableQuietMode, userHandle.getIdentifier(), target);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -2160,27 +2191,6 @@
}
/**
- * Tries disabling quiet mode for a given user. If the user is still locked, we unlock the user
- * first by showing the confirm credentials screen and disable quiet mode upon successful
- * unlocking. If the user is already unlocked, we call through to {@link #setQuietModeEnabled}
- * directly.
- *
- * @param userHandle The user that is going to disable quiet mode.
- * @param target The target to launch when the user is unlocked.
- * @return {@code true} if quiet mode is disabled without showing confirm credentials screen,
- * {@code false} otherwise.
- * @hide
- */
- public boolean trySetQuietModeDisabled(
- @UserIdInt int userHandle, @Nullable IntentSender target) {
- try {
- return mService.trySetQuietModeDisabled(userHandle, target);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
- /**
* If the target user is a managed profile of the calling user or the caller
* is itself a managed profile, then this returns a badged copy of the given
* icon to be able to distinguish it from the original icon. For badging an
diff --git a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java b/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
index 2205c41..978e60e 100644
--- a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
+++ b/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
@@ -60,7 +60,7 @@
/**
* Creates instance of the class to to derive key using salted SHA256 hash.
*/
- public KeyDerivationParameters createSHA256Parameters(@NonNull byte[] salt) {
+ public static KeyDerivationParameters createSHA256Parameters(@NonNull byte[] salt) {
return new KeyDerivationParameters(ALGORITHM_SHA256, salt);
}
diff --git a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
index f2f225d..f88768b 100644
--- a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
+++ b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -29,6 +30,7 @@
import com.android.internal.widget.ILockSettings;
import java.util.List;
+import java.util.Map;
/**
* A wrapper around KeyStore which lets key be exported to trusted hardware on server side and
@@ -43,9 +45,23 @@
public static final int NO_ERROR = KeyStore.NO_ERROR;
public static final int SYSTEM_ERROR = KeyStore.SYSTEM_ERROR;
public static final int UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20;
- // Too many updates to recovery public key or server parameters.
+ /**
+ * Rate limit is enforced to prevent using too many trusted remote devices, since each device
+ * can have its own number of user secret guesses allowed.
+ *
+ * @hide
+ */
public static final int RATE_LIMIT_EXCEEDED = 21;
+ /** Key has been successfully synced. */
+ public static final int RECOVERY_STATUS_SYNCED = 0;
+ /** Waiting for recovery agent to sync the key. */
+ public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1;
+ /** Recovery account is not available. */
+ public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2;
+ /** Key cannot be synced. */
+ public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3;
+
private final ILockSettings mBinder;
private RecoverableKeyStoreLoader(ILockSettings binder) {
@@ -155,7 +171,7 @@
* @return Data necessary to recover keystore.
* @hide
*/
- public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
+ public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
throws RecoverableKeyStoreLoaderException {
try {
KeyStoreRecoveryData recoveryData =
@@ -169,6 +185,50 @@
}
/**
+ * Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link
+ * #getRecoveryData} can be used to get the snapshot. Note that every recovery agent can have at
+ * most one registered listener at any time.
+ *
+ * @param intent triggered when new snapshot is available. Unregisters listener if the value is
+ * {@code null}.
+ * @hide
+ */
+ public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ mBinder.setSnapshotCreatedPendingIntent(intent, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Returns a map from recovery agent accounts to corresponding KeyStore recovery snapshot
+ * version. Version zero is used, if no snapshots were created for the account.
+ *
+ * @return Map from recovery agent accounts to snapshot versions.
+ * @see KeyStoreRecoveryData.getSnapshotVersion
+ * @hide
+ */
+ public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ // IPC doesn't support generic Maps.
+ @SuppressWarnings("unchecked")
+ Map<byte[], Integer> result =
+ (Map<byte[], Integer>)
+ mBinder.getRecoverySnapshotVersions(UserHandle.getCallingUserId());
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
* Server parameters used to generate new recovery key blobs. This value will be included in
* {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included
* in vaultParams {@link startRecoverySession}
@@ -191,8 +251,8 @@
/**
* Updates recovery status for given keys. It is used to notify keystore that key was
- * successfully stored on the server or there were an error. Returned as a part of KeyInfo data
- * structure.
+ * successfully stored on the server or there were an error. Application can check this value
+ * using {@code getRecoveyStatus}.
*
* @param packageName Application whose recoverable keys' statuses are to be updated.
* @param aliases List of application-specific key aliases. If the array is empty, updates the
@@ -212,6 +272,39 @@
}
/**
+ * Returns a {@code Map} from Application's KeyStore key aliases to their recovery status.
+ * Negative status values are reserved for recovery agent specific codes. List of common codes:
+ *
+ * <ul>
+ * <li>{@link #RECOVERY_STATUS_SYNCED}
+ * <li>{@link #RECOVERY_STATUS_SYNC_IN_PROGRESS}
+ * <li>{@link #RECOVERY_STATUS_MISSING_ACCOUNT}
+ * <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE}
+ * </ul>
+ *
+ * @param packageName Application whose recoverable keys' statuses are to be retrieved. if
+ * {@code null} caller's package will be used.
+ * @return {@code Map} from KeyStore alias to recovery status.
+ * @see #setRecoveryStatus
+ * @hide
+ */
+ public Map<String, Integer> getRecoveryStatus(@Nullable String packageName)
+ throws RecoverableKeyStoreLoaderException {
+ try {
+ // IPC doesn't support generic Maps.
+ @SuppressWarnings("unchecked")
+ Map<String, Integer> result =
+ (Map<String, Integer>)
+ mBinder.getRecoveryStatus(packageName, UserHandle.getCallingUserId());
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
* Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them
* is necessary to recover data.
*
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 02beee0..cc63a62 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -26890,7 +26890,7 @@
if (mAttachInfo == null || mTooltipInfo == null) {
return false;
}
- if ((mViewFlags & ENABLED_MASK) != ENABLED) {
+ if (fromLongClick && (mViewFlags & ENABLED_MASK) != ENABLED) {
return false;
}
if (TextUtils.isEmpty(mTooltipInfo.mTooltipText)) {
@@ -26938,7 +26938,7 @@
}
switch(event.getAction()) {
case MotionEvent.ACTION_HOVER_MOVE:
- if ((mViewFlags & TOOLTIP) != TOOLTIP || (mViewFlags & ENABLED_MASK) != ENABLED) {
+ if ((mViewFlags & TOOLTIP) != TOOLTIP) {
break;
}
if (!mTooltipInfo.mTooltipFromLongClick && mTooltipInfo.updateAnchorPos(event)) {
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index d0dbff0e..2697454 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -530,10 +530,13 @@
* @return whether autofill is enabled for the current user.
*/
public boolean isEnabled() {
- if (!hasAutofillFeature() || isDisabledByService()) {
+ if (!hasAutofillFeature()) {
return false;
}
synchronized (mLock) {
+ if (isDisabledByServiceLocked()) {
+ return false;
+ }
ensureServiceClientAddedIfNeededLocked();
return mEnabled;
}
@@ -605,19 +608,16 @@
}
private boolean shouldIgnoreViewEnteredLocked(@NonNull View view, int flags) {
- if (isDisabledByService()) {
+ if (isDisabledByServiceLocked()) {
if (sVerbose) {
Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
+ ") on state " + getStateAsStringLocked());
}
return true;
}
- if (mState == STATE_FINISHED && (flags & FLAG_MANUAL_REQUEST) == 0) {
- if (sVerbose) {
- Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
- + ") on state " + getStateAsStringLocked());
- }
- return true;
+ if (sVerbose && isFinishedLocked()) {
+ Log.v(TAG, "not ignoring notifyViewEntered(flags=" + flags + ", view=" + view
+ + ") on state " + getStateAsStringLocked());
}
return false;
}
@@ -1009,6 +1009,21 @@
}
/**
+ * Returns the component name of the {@link AutofillService} that is enabled for the current
+ * user.
+ */
+ @Nullable
+ public ComponentName getAutofillServiceComponentName() {
+ if (mService == null) return null;
+
+ try {
+ return mService.getAutofillServiceComponentName();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets the user data used for
* <a href="AutofillService.html#FieldClassification">field classification</a>.
*
@@ -1139,10 +1154,10 @@
Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
+ ", flags=" + flags + ", state=" + getStateAsStringLocked());
}
- if (mState != STATE_UNKNOWN && (flags & FLAG_MANUAL_REQUEST) == 0) {
+ if (mState != STATE_UNKNOWN && !isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
if (sVerbose) {
Log.v(TAG, "not automatically starting session for " + id
- + " on state " + getStateAsStringLocked());
+ + " on state " + getStateAsStringLocked() + " and flags " + flags);
}
return;
}
@@ -1744,10 +1759,14 @@
return mState == STATE_ACTIVE;
}
- private boolean isDisabledByService() {
+ private boolean isDisabledByServiceLocked() {
return mState == STATE_DISABLED_BY_SERVICE;
}
+ private boolean isFinishedLocked() {
+ return mState == STATE_FINISHED;
+ }
+
private void post(Runnable runnable) {
final AutofillClient client = getClient();
if (client == null) {
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index f49aa5b..38bb311 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -57,4 +57,5 @@
UserData getUserData();
void setUserData(in UserData userData);
boolean isFieldClassificationEnabled();
+ ComponentName getAutofillServiceComponentName();
}
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 8016a65..2eadaf3 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -111,7 +111,7 @@
@Override
public void onClick(DialogInterface dialog, int which) {
if (mReason == UNLAUNCHABLE_REASON_QUIET_MODE && which == DialogInterface.BUTTON_POSITIVE) {
- UserManager.get(this).trySetQuietModeDisabled(mUserId, mTarget);
+ UserManager.get(this).trySetQuietModeEnabled(false, UserHandle.of(mUserId), mTarget);
}
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6510a70..c1e5af8 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -51,6 +51,7 @@
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.IntArray;
import android.util.Log;
import android.util.LogWriter;
import android.util.LongSparseArray;
@@ -120,7 +121,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 171 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 172 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS;
@@ -188,6 +189,8 @@
@VisibleForTesting
protected KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
new KernelUidCpuFreqTimeReader();
+ @VisibleForTesting
+ protected KernelSingleUidTimeReader mKernelSingleUidTimeReader;
private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
= new KernelMemoryBandwidthStats();
@@ -196,6 +199,18 @@
return mKernelMemoryStats;
}
+ @GuardedBy("this")
+ public boolean mPerProcStateCpuTimesAvailable = true;
+
+ /**
+ * Uids for which per-procstate cpu times need to be updated.
+ *
+ * Contains uid -> procState mappings.
+ */
+ @GuardedBy("this")
+ @VisibleForTesting
+ protected final SparseIntArray mPendingUids = new SparseIntArray();
+
/** Container for Resource Power Manager stats. Updated by updateRpmStatsLocked. */
private final RpmStats mTmpRpmStats = new RpmStats();
/** The soonest the RPM stats can be updated after it was last updated. */
@@ -268,6 +283,138 @@
}
}
+ /**
+ * Update per-freq cpu times for all the uids in {@link #mPendingUids}.
+ */
+ public void updateProcStateCpuTimes() {
+ final SparseIntArray uidStates;
+ synchronized (BatteryStatsImpl.this) {
+ if(!initKernelSingleUidTimeReaderLocked()) {
+ return;
+ }
+
+ if (mPendingUids.size() == 0) {
+ return;
+ }
+ uidStates = mPendingUids.clone();
+ mPendingUids.clear();
+ }
+ for (int i = uidStates.size() - 1; i >= 0; --i) {
+ final int uid = uidStates.keyAt(i);
+ final int procState = uidStates.valueAt(i);
+ final int[] isolatedUids;
+ final Uid u;
+ final boolean onBattery;
+ synchronized (BatteryStatsImpl.this) {
+ // It's possible that uid no longer exists and any internal references have
+ // already been deleted, so using {@link #getAvailableUidStatsLocked} to avoid
+ // creating an UidStats object if it doesn't already exist.
+ u = getAvailableUidStatsLocked(uid);
+ if (u == null) {
+ continue;
+ }
+ if (u.mChildUids == null) {
+ isolatedUids = null;
+ } else {
+ isolatedUids = u.mChildUids.toArray();
+ for (int j = isolatedUids.length - 1; j >= 0; --j) {
+ isolatedUids[j] = u.mChildUids.get(j);
+ }
+ }
+ onBattery = mOnBatteryInternal;
+ }
+ long[] cpuTimesMs = mKernelSingleUidTimeReader.readDeltaMs(uid);
+ if (isolatedUids != null) {
+ for (int j = isolatedUids.length - 1; j >= 0; --j) {
+ cpuTimesMs = addCpuTimes(cpuTimesMs,
+ mKernelSingleUidTimeReader.readDeltaMs(isolatedUids[j]));
+ }
+ }
+ if (onBattery && cpuTimesMs != null) {
+ synchronized (BatteryStatsImpl.this) {
+ u.addProcStateTimesMs(procState, cpuTimesMs);
+ u.addProcStateScreenOffTimesMs(procState, cpuTimesMs);
+ }
+ }
+ }
+ }
+
+ /**
+ * When the battery/screen state changes, we don't attribute the cpu times to any process
+ * but we still need to snapshots of all uids to get correct deltas later on. Since we
+ * already read this data for updating per-freq cpu times, we can use the same data for
+ * per-procstate cpu times.
+ */
+ public void copyFromAllUidsCpuTimes() {
+ synchronized (BatteryStatsImpl.this) {
+ if(!initKernelSingleUidTimeReaderLocked()) {
+ return;
+ }
+
+ final SparseArray<long[]> allUidCpuFreqTimesMs =
+ mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs();
+ for (int i = allUidCpuFreqTimesMs.size() - 1; i >= 0; --i) {
+ final int uid = allUidCpuFreqTimesMs.keyAt(i);
+ final Uid u = getAvailableUidStatsLocked(mapUid(uid));
+ if (u == null) {
+ continue;
+ }
+ final long[] cpuTimesMs = allUidCpuFreqTimesMs.valueAt(i);
+ if (cpuTimesMs == null) {
+ continue;
+ }
+ final long[] deltaTimesMs = mKernelSingleUidTimeReader.computeDelta(
+ uid, cpuTimesMs.clone());
+ if (mOnBatteryInternal && deltaTimesMs != null) {
+ final int procState;
+ final int idx = mPendingUids.indexOfKey(uid);
+ if (idx >= 0) {
+ procState = mPendingUids.valueAt(idx);
+ mPendingUids.removeAt(idx);
+ } else {
+ procState = u.mProcessState;
+ }
+ if (procState >= 0 && procState < Uid.NUM_PROCESS_STATE) {
+ u.addProcStateTimesMs(procState, deltaTimesMs);
+ u.addProcStateScreenOffTimesMs(procState, deltaTimesMs);
+ }
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public long[] addCpuTimes(long[] timesA, long[] timesB) {
+ if (timesA != null && timesB != null) {
+ for (int i = timesA.length - 1; i >= 0; --i) {
+ timesA[i] += timesB[i];
+ }
+ return timesA;
+ }
+ return timesA == null ? (timesB == null ? null : timesB) : timesA;
+ }
+
+ @GuardedBy("this")
+ private boolean initKernelSingleUidTimeReaderLocked() {
+ if (mKernelSingleUidTimeReader == null) {
+ if (mPowerProfile == null) {
+ return false;
+ }
+ if (mCpuFreqs == null) {
+ mCpuFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
+ }
+ if (mCpuFreqs != null) {
+ mKernelSingleUidTimeReader = new KernelSingleUidTimeReader(mCpuFreqs.length);
+ } else {
+ mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable();
+ return false;
+ }
+ }
+ mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable()
+ && mKernelSingleUidTimeReader.singleUidCpuTimesAvailable();
+ return true;
+ }
+
public interface Clocks {
public long elapsedRealtime();
public long uptimeMillis();
@@ -293,9 +440,11 @@
Future<?> scheduleSync(String reason, int flags);
Future<?> scheduleCpuSyncDueToRemovedUid(int uid);
+ Future<?> scheduleReadProcStateCpuTimes();
+ Future<?> scheduleCopyFromAllUidsCpuTimes();
}
- public final MyHandler mHandler;
+ public Handler mHandler;
private ExternalStatsSync mExternalSync = null;
@VisibleForTesting
protected UserInfoProvider mUserInfoProvider = null;
@@ -3623,6 +3772,7 @@
+ " and battery is " + (unplugged ? "on" : "off"));
}
updateCpuTimeLocked();
+ mExternalSync.scheduleCopyFromAllUidsCpuTimes();
mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
if (updateOnBatteryTimeBase) {
@@ -3652,6 +3802,8 @@
public void addIsolatedUidLocked(int isolatedUid, int appUid) {
mIsolatedUids.put(isolatedUid, appUid);
StatsLog.write(StatsLog.ISOLATED_UID_CHANGED, appUid, isolatedUid, 1);
+ final Uid u = getUidStatsLocked(appUid);
+ u.addIsolatedUid(isolatedUid);
}
/**
@@ -3672,11 +3824,17 @@
* @see #scheduleRemoveIsolatedUidLocked(int, int)
*/
public void removeIsolatedUidLocked(int isolatedUid) {
- StatsLog.write(
- StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1), isolatedUid, 0);
- mIsolatedUids.delete(isolatedUid);
- mKernelUidCpuTimeReader.removeUid(isolatedUid);
- mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
+ StatsLog.write(
+ StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1), isolatedUid, 0);
+ final int idx = mIsolatedUids.indexOfKey(isolatedUid);
+ if (idx >= 0) {
+ final int ownerUid = mIsolatedUids.valueAt(idx);
+ final Uid u = getUidStatsLocked(ownerUid);
+ u.removeIsolatedUid(isolatedUid);
+ mIsolatedUids.removeAt(idx);
+ }
+ mKernelUidCpuTimeReader.removeUid(isolatedUid);
+ mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
}
public int mapUid(int uid) {
@@ -5899,6 +6057,11 @@
LongSamplingCounterArray mCpuFreqTimeMs;
LongSamplingCounterArray mScreenOffCpuFreqTimeMs;
+ LongSamplingCounterArray[] mProcStateTimeMs;
+ LongSamplingCounterArray[] mProcStateScreenOffTimeMs;
+
+ IntArray mChildUids;
+
/**
* The statistics we have collected for this uid's wake locks.
*/
@@ -5984,40 +6147,107 @@
mProcessStateTimer = new StopwatchTimer[NUM_PROCESS_STATE];
}
+ @VisibleForTesting
+ public void setProcessStateForTest(int procState) {
+ mProcessState = procState;
+ }
+
@Override
public long[] getCpuFreqTimes(int which) {
- if (mCpuFreqTimeMs == null) {
+ return nullIfAllZeros(mCpuFreqTimeMs, which);
+ }
+
+ @Override
+ public long[] getScreenOffCpuFreqTimes(int which) {
+ return nullIfAllZeros(mScreenOffCpuFreqTimeMs, which);
+ }
+
+ @Override
+ public long[] getCpuFreqTimes(int which, int procState) {
+ if (which < 0 || which >= NUM_PROCESS_STATE) {
return null;
}
- final long[] cpuFreqTimes = mCpuFreqTimeMs.getCountsLocked(which);
- if (cpuFreqTimes == null) {
+ if (mProcStateTimeMs == null) {
return null;
}
- // Return cpuFreqTimes only if atleast one of the elements in non-zero.
- for (int i = 0; i < cpuFreqTimes.length; ++i) {
- if (cpuFreqTimes[i] != 0) {
- return cpuFreqTimes;
+ if (!mBsi.mPerProcStateCpuTimesAvailable) {
+ mProcStateTimeMs = null;
+ return null;
+ }
+ return nullIfAllZeros(mProcStateTimeMs[procState], which);
+ }
+
+ @Override
+ public long[] getScreenOffCpuFreqTimes(int which, int procState) {
+ if (which < 0 || which >= NUM_PROCESS_STATE) {
+ return null;
+ }
+ if (mProcStateScreenOffTimeMs == null) {
+ return null;
+ }
+ if (!mBsi.mPerProcStateCpuTimesAvailable) {
+ mProcStateScreenOffTimeMs = null;
+ return null;
+ }
+ return nullIfAllZeros(mProcStateScreenOffTimeMs[procState], which);
+ }
+
+ public void addIsolatedUid(int isolatedUid) {
+ if (mChildUids == null) {
+ mChildUids = new IntArray();
+ } else if (mChildUids.indexOf(isolatedUid) >= 0) {
+ return;
+ }
+ mChildUids.add(isolatedUid);
+ }
+
+ public void removeIsolatedUid(int isolatedUid) {
+ final int idx = mChildUids == null ? -1 : mChildUids.indexOf(isolatedUid);
+ if (idx < 0) {
+ return;
+ }
+ mChildUids.remove(idx);
+ }
+
+ private long[] nullIfAllZeros(LongSamplingCounterArray cpuTimesMs, int which) {
+ if (cpuTimesMs == null) {
+ return null;
+ }
+ final long[] counts = cpuTimesMs.getCountsLocked(which);
+ if (counts == null) {
+ return null;
+ }
+ // Return counts only if at least one of the elements is non-zero.
+ for (int i = counts.length - 1; i >= 0; --i) {
+ if (counts[i] != 0) {
+ return counts;
}
}
return null;
}
- @Override
- public long[] getScreenOffCpuFreqTimes(int which) {
- if (mScreenOffCpuFreqTimeMs == null) {
- return null;
+ private void addProcStateTimesMs(int procState, long[] cpuTimesMs) {
+ if (mProcStateTimeMs == null) {
+ mProcStateTimeMs = new LongSamplingCounterArray[NUM_PROCESS_STATE];
}
- final long[] cpuFreqTimes = mScreenOffCpuFreqTimeMs.getCountsLocked(which);
- if (cpuFreqTimes == null) {
- return null;
+ if (mProcStateTimeMs[procState] == null
+ || mProcStateTimeMs[procState].getSize() != cpuTimesMs.length) {
+ mProcStateTimeMs[procState] = new LongSamplingCounterArray(
+ mBsi.mOnBatteryTimeBase);
}
- // Return cpuFreqTimes only if atleast one of the elements in non-zero.
- for (int i = 0; i < cpuFreqTimes.length; ++i) {
- if (cpuFreqTimes[i] != 0) {
- return cpuFreqTimes;
- }
+ mProcStateTimeMs[procState].addCountLocked(cpuTimesMs);
+ }
+
+ private void addProcStateScreenOffTimesMs(int procState, long[] cpuTimesMs) {
+ if (mProcStateScreenOffTimeMs == null) {
+ mProcStateScreenOffTimeMs = new LongSamplingCounterArray[NUM_PROCESS_STATE];
}
- return null;
+ if (mProcStateScreenOffTimeMs[procState] == null
+ || mProcStateScreenOffTimeMs[procState].getSize() != cpuTimesMs.length) {
+ mProcStateScreenOffTimeMs[procState] = new LongSamplingCounterArray(
+ mBsi.mOnBatteryScreenOffTimeBase);
+ }
+ mProcStateScreenOffTimeMs[procState].addCountLocked(cpuTimesMs);
}
@Override
@@ -6999,6 +7229,21 @@
mScreenOffCpuFreqTimeMs.reset(false);
}
+ if (mProcStateTimeMs != null) {
+ for (LongSamplingCounterArray counters : mProcStateTimeMs) {
+ if (counters != null) {
+ counters.reset(false);
+ }
+ }
+ }
+ if (mProcStateScreenOffTimeMs != null) {
+ for (LongSamplingCounterArray counters : mProcStateScreenOffTimeMs) {
+ if (counters != null) {
+ counters.reset(false);
+ }
+ }
+ }
+
resetLongCounterIfNotNull(mMobileRadioApWakeupCount, false);
resetLongCounterIfNotNull(mWifiRadioApWakeupCount, false);
@@ -7189,6 +7434,20 @@
mScreenOffCpuFreqTimeMs.detach();
}
+ if (mProcStateTimeMs != null) {
+ for (LongSamplingCounterArray counters : mProcStateTimeMs) {
+ if (counters != null) {
+ counters.detach();
+ }
+ }
+ }
+ if (mProcStateScreenOffTimeMs != null) {
+ for (LongSamplingCounterArray counters : mProcStateScreenOffTimeMs) {
+ if (counters != null) {
+ counters.detach();
+ }
+ }
+ }
detachLongCounterIfNotNull(mMobileRadioApWakeupCount);
detachLongCounterIfNotNull(mWifiRadioApWakeupCount);
}
@@ -7449,6 +7708,22 @@
LongSamplingCounterArray.writeToParcel(out, mCpuFreqTimeMs);
LongSamplingCounterArray.writeToParcel(out, mScreenOffCpuFreqTimeMs);
+ if (mProcStateTimeMs != null) {
+ out.writeInt(mProcStateTimeMs.length);
+ for (LongSamplingCounterArray counters : mProcStateTimeMs) {
+ LongSamplingCounterArray.writeToParcel(out, counters);
+ }
+ } else {
+ out.writeInt(0);
+ }
+ if (mProcStateScreenOffTimeMs != null) {
+ out.writeInt(mProcStateScreenOffTimeMs.length);
+ for (LongSamplingCounterArray counters : mProcStateScreenOffTimeMs) {
+ LongSamplingCounterArray.writeToParcel(out, counters);
+ }
+ } else {
+ out.writeInt(0);
+ }
if (mMobileRadioApWakeupCount != null) {
out.writeInt(1);
@@ -7750,6 +8025,27 @@
mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel(
in, mBsi.mOnBatteryScreenOffTimeBase);
+ int length = in.readInt();
+ if (length == NUM_PROCESS_STATE) {
+ mProcStateTimeMs = new LongSamplingCounterArray[length];
+ for (int procState = 0; procState < length; ++procState) {
+ mProcStateTimeMs[procState] = LongSamplingCounterArray.readFromParcel(
+ in, mBsi.mOnBatteryTimeBase);
+ }
+ } else {
+ mProcStateTimeMs = null;
+ }
+ length = in.readInt();
+ if (length == NUM_PROCESS_STATE) {
+ mProcStateScreenOffTimeMs = new LongSamplingCounterArray[length];
+ for (int procState = 0; procState < length; ++procState) {
+ mProcStateScreenOffTimeMs[procState] = LongSamplingCounterArray.readFromParcel(
+ in, mBsi.mOnBatteryScreenOffTimeBase);
+ }
+ } else {
+ mProcStateScreenOffTimeMs = null;
+ }
+
if (in.readInt() != 0) {
mMobileRadioApWakeupCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
} else {
@@ -8671,25 +8967,7 @@
// Make special note of Foreground Services
final boolean userAwareService =
(procState == ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
- if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
- uidRunningState = ActivityManager.PROCESS_STATE_NONEXISTENT;
- } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
- uidRunningState = PROCESS_STATE_TOP;
- } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
- // Persistent and other foreground states go here.
- uidRunningState = PROCESS_STATE_FOREGROUND_SERVICE;
- } else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- // Persistent and other foreground states go here.
- uidRunningState = PROCESS_STATE_FOREGROUND;
- } else if (procState <= ActivityManager.PROCESS_STATE_RECEIVER) {
- uidRunningState = PROCESS_STATE_BACKGROUND;
- } else if (procState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
- uidRunningState = PROCESS_STATE_TOP_SLEEPING;
- } else if (procState <= ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
- uidRunningState = PROCESS_STATE_HEAVY_WEIGHT;
- } else {
- uidRunningState = PROCESS_STATE_CACHED;
- }
+ uidRunningState = BatteryStats.mapToInternalProcessState(procState);
if (mProcessState == uidRunningState && userAwareService == mInForegroundService) {
return;
@@ -8701,6 +8979,18 @@
if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
+
+ if (mBsi.mPerProcStateCpuTimesAvailable) {
+ if (mBsi.mPendingUids.size() == 0) {
+ mBsi.mExternalSync.scheduleReadProcStateCpuTimes();
+ }
+ if (mBsi.mPendingUids.indexOfKey(mUid) < 0
+ || ArrayUtils.contains(CRITICAL_PROC_STATES, mProcessState)) {
+ mBsi.mPendingUids.put(mUid, mProcessState);
+ }
+ } else {
+ mBsi.mPendingUids.clear();
+ }
}
mProcessState = uidRunningState;
if (uidRunningState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
@@ -11769,11 +12059,23 @@
return u;
}
+ /**
+ * Retrieve the statistics object for a particular uid. Returns null if the object is not
+ * available.
+ */
+ public Uid getAvailableUidStatsLocked(int uid) {
+ Uid u = mUidStats.get(uid);
+ return u;
+ }
+
public void onCleanupUserLocked(int userId) {
final int firstUidForUser = UserHandle.getUid(userId, 0);
final int lastUidForUser = UserHandle.getUid(userId, UserHandle.PER_USER_RANGE - 1);
mKernelUidCpuFreqTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
mKernelUidCpuTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
+ if (mKernelSingleUidTimeReader != null) {
+ mKernelSingleUidTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
+ }
}
public void onUserRemovedLocked(int userId) {
@@ -11792,6 +12094,9 @@
public void removeUidStatsLocked(int uid) {
mKernelUidCpuTimeReader.removeUid(uid);
mKernelUidCpuFreqTimeReader.removeUid(uid);
+ if (mKernelSingleUidTimeReader != null) {
+ mKernelSingleUidTimeReader.removeUid(uid);
+ }
mUidStats.remove(uid);
}
@@ -12393,6 +12698,28 @@
in, mOnBatteryTimeBase);
u.mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readSummaryFromParcelLocked(
in, mOnBatteryScreenOffTimeBase);
+ int length = in.readInt();
+ if (length == Uid.NUM_PROCESS_STATE) {
+ u.mProcStateTimeMs = new LongSamplingCounterArray[length];
+ for (int procState = 0; procState < length; ++procState) {
+ u.mProcStateTimeMs[procState]
+ = LongSamplingCounterArray.readSummaryFromParcelLocked(
+ in, mOnBatteryTimeBase);
+ }
+ } else {
+ u.mProcStateTimeMs = null;
+ }
+ length = in.readInt();
+ if (length == Uid.NUM_PROCESS_STATE) {
+ u.mProcStateScreenOffTimeMs = new LongSamplingCounterArray[length];
+ for (int procState = 0; procState < length; ++procState) {
+ u.mProcStateScreenOffTimeMs[procState]
+ = LongSamplingCounterArray.readSummaryFromParcelLocked(
+ in, mOnBatteryScreenOffTimeBase);
+ }
+ } else {
+ u.mProcStateScreenOffTimeMs = null;
+ }
if (in.readInt() != 0) {
u.mMobileRadioApWakeupCount = new LongSamplingCounter(mOnBatteryTimeBase);
@@ -12846,6 +13173,23 @@
LongSamplingCounterArray.writeSummaryToParcelLocked(out, u.mCpuFreqTimeMs);
LongSamplingCounterArray.writeSummaryToParcelLocked(out, u.mScreenOffCpuFreqTimeMs);
+ if (u.mProcStateTimeMs != null) {
+ out.writeInt(u.mProcStateTimeMs.length);
+ for (LongSamplingCounterArray counters : u.mProcStateTimeMs) {
+ LongSamplingCounterArray.writeSummaryToParcelLocked(out, counters);
+ }
+ } else {
+ out.writeInt(0);
+ }
+ if (u.mProcStateScreenOffTimeMs != null) {
+ out.writeInt(u.mProcStateScreenOffTimeMs.length);
+ for (LongSamplingCounterArray counters : u.mProcStateScreenOffTimeMs) {
+ LongSamplingCounterArray.writeSummaryToParcelLocked(out, counters);
+ }
+ } else {
+ out.writeInt(0);
+ }
+
if (u.mMobileRadioApWakeupCount != null) {
out.writeInt(1);
u.mMobileRadioApWakeupCount.writeSummaryFromParcelLocked(out);
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
new file mode 100644
index 0000000..ca635a4
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+@VisibleForTesting(visibility = PACKAGE)
+public class KernelSingleUidTimeReader {
+ private final String TAG = KernelUidCpuFreqTimeReader.class.getName();
+ private final boolean DBG = false;
+
+ private final String PROC_FILE_DIR = "/proc/uid/";
+ private final String PROC_FILE_NAME = "/time_in_state";
+
+ @VisibleForTesting
+ public static final int TOTAL_READ_ERROR_COUNT = 5;
+
+ @GuardedBy("this")
+ private final int mCpuFreqsCount;
+
+ @GuardedBy("this")
+ private final SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
+
+ @GuardedBy("this")
+ private int mReadErrorCounter;
+ @GuardedBy("this")
+ private boolean mSingleUidCpuTimesAvailable = true;
+
+ private final Injector mInjector;
+
+ KernelSingleUidTimeReader(int cpuFreqsCount) {
+ this(cpuFreqsCount, new Injector());
+ }
+
+ public KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector) {
+ mInjector = injector;
+ mCpuFreqsCount = cpuFreqsCount;
+ if (mCpuFreqsCount == 0) {
+ mSingleUidCpuTimesAvailable = false;
+ }
+ }
+
+ public boolean singleUidCpuTimesAvailable() {
+ return mSingleUidCpuTimesAvailable;
+ }
+
+ public long[] readDeltaMs(int uid) {
+ synchronized (this) {
+ if (!mSingleUidCpuTimesAvailable) {
+ return null;
+ }
+ // Read total cpu times from the proc file.
+ final String procFile = new StringBuilder(PROC_FILE_DIR)
+ .append(uid)
+ .append(PROC_FILE_NAME).toString();
+ final long[] cpuTimesMs = new long[mCpuFreqsCount];
+ try {
+ final byte[] data = mInjector.readData(procFile);
+ final ByteBuffer buffer = ByteBuffer.wrap(data);
+ buffer.order(ByteOrder.nativeOrder());
+ for (int i = 0; i < mCpuFreqsCount; ++i) {
+ // Times read will be in units of 10ms
+ cpuTimesMs[i] = buffer.getLong() * 10;
+ }
+ } catch (Exception e) {
+ if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+ mSingleUidCpuTimesAvailable = false;
+ }
+ if (DBG) Slog.e(TAG, "Some error occured while reading " + procFile, e);
+ return null;
+ }
+
+ return computeDelta(uid, cpuTimesMs);
+ }
+ }
+
+ /**
+ * Compute and return cpu times delta of an uid using previously read cpu times and
+ * {@param latestCpuTimesMs}.
+ *
+ * @return delta of cpu times if at least one of the cpu time at a freq is +ve, otherwise null.
+ */
+ public long[] computeDelta(int uid, @NonNull long[] latestCpuTimesMs) {
+ synchronized (this) {
+ if (!mSingleUidCpuTimesAvailable) {
+ return null;
+ }
+ // Subtract the last read cpu times to get deltas.
+ final long[] lastCpuTimesMs = mLastUidCpuTimeMs.get(uid);
+ final long[] deltaTimesMs = getDeltaLocked(lastCpuTimesMs, latestCpuTimesMs);
+ if (deltaTimesMs == null) {
+ if (DBG) Slog.e(TAG, "Malformed data read for uid=" + uid
+ + "; last=" + Arrays.toString(lastCpuTimesMs)
+ + "; latest=" + Arrays.toString(latestCpuTimesMs));
+ return null;
+ }
+ // If all elements are zero, return null to avoid unnecessary work on the caller side.
+ boolean hasNonZero = false;
+ for (int i = deltaTimesMs.length - 1; i >= 0; --i) {
+ if (deltaTimesMs[i] > 0) {
+ hasNonZero = true;
+ break;
+ }
+ }
+ if (hasNonZero) {
+ mLastUidCpuTimeMs.put(uid, latestCpuTimesMs);
+ return deltaTimesMs;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Returns null if the latest cpu times are not valid**, otherwise delta of
+ * {@param latestCpuTimesMs} and {@param lastCpuTimesMs}.
+ *
+ * **latest cpu times are considered valid if all the cpu times are +ve and
+ * greater than or equal to previously read cpu times.
+ */
+ @GuardedBy("this")
+ @VisibleForTesting(visibility = PACKAGE)
+ public long[] getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs) {
+ for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
+ if (latestCpuTimesMs[i] < 0) {
+ return null;
+ }
+ }
+ if (lastCpuTimesMs == null) {
+ return latestCpuTimesMs;
+ }
+ final long[] deltaTimesMs = new long[latestCpuTimesMs.length];
+ for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
+ deltaTimesMs[i] = latestCpuTimesMs[i] - lastCpuTimesMs[i];
+ if (deltaTimesMs[i] < 0) {
+ return null;
+ }
+ }
+ return deltaTimesMs;
+ }
+
+ public void removeUid(int uid) {
+ synchronized (this) {
+ mLastUidCpuTimeMs.delete(uid);
+ }
+ }
+
+ public void removeUidsInRange(int startUid, int endUid) {
+ if (endUid < startUid) {
+ return;
+ }
+ synchronized (this) {
+ mLastUidCpuTimeMs.put(startUid, null);
+ mLastUidCpuTimeMs.put(endUid, null);
+ final int startIdx = mLastUidCpuTimeMs.indexOfKey(startUid);
+ final int endIdx = mLastUidCpuTimeMs.indexOfKey(endUid);
+ mLastUidCpuTimeMs.removeAtRange(startIdx, endIdx - startIdx + 1);
+ }
+ }
+
+ @VisibleForTesting
+ public static class Injector {
+ public byte[] readData(String procFile) throws IOException {
+ return Files.readAllBytes(Paths.get(procFile));
+ }
+ }
+
+ @VisibleForTesting
+ public SparseArray<long[]> getLastUidCpuTimeMs() {
+ return mLastUidCpuTimeMs;
+ }
+
+ @VisibleForTesting
+ public void setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable) {
+ mSingleUidCpuTimesAvailable = singleUidCpuTimesAvailable;
+ }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
index a39997d..b8982cc 100644
--- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
@@ -66,13 +66,21 @@
// start reading) and if it is not available, we simply ignore further read requests.
private static final int TOTAL_READ_ERROR_COUNT = 5;
private int mReadErrorCounter;
- private boolean mProcFileAvailable;
private boolean mPerClusterTimesAvailable;
+ private boolean mAllUidTimesAvailable = true;
public boolean perClusterTimesAvailable() {
return mPerClusterTimesAvailable;
}
+ public boolean allUidTimesAvailable() {
+ return mAllUidTimesAvailable;
+ }
+
+ public SparseArray<long[]> getAllUidCpuFreqTimeMs() {
+ return mLastUidCpuFreqTimeMs;
+ }
+
public long[] readFreqs(@NonNull PowerProfile powerProfile) {
checkNotNull(powerProfile);
@@ -80,15 +88,16 @@
// No need to read cpu freqs more than once.
return mCpuFreqs;
}
- if (!mProcFileAvailable && mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+ if (!mAllUidTimesAvailable) {
return null;
}
final int oldMask = StrictMode.allowThreadDiskReadsMask();
try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
- mProcFileAvailable = true;
return readFreqs(reader, powerProfile);
} catch (IOException e) {
- mReadErrorCounter++;
+ if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+ mAllUidTimesAvailable = false;
+ }
Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
return null;
} finally {
@@ -107,7 +116,7 @@
}
public void readDelta(@Nullable Callback callback) {
- if (!mProcFileAvailable) {
+ if (mCpuFreqs == null) {
return;
}
final int oldMask = StrictMode.allowThreadDiskReadsMask();
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 164a745..43536a5 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -16,6 +16,7 @@
package com.android.internal.widget;
+import android.app.PendingIntent;
import android.app.trust.IStrongAuthTracker;
import android.os.Bundle;
import android.security.recoverablekeystore.KeyEntryRecoveryData;
@@ -24,6 +25,8 @@
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.VerifyCredentialResponse;
+import java.util.Map;
+
/** {@hide} */
interface ILockSettings {
void setBoolean(in String key, in boolean value, in int userId);
@@ -63,8 +66,11 @@
void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList,
int userId);
KeyStoreRecoveryData getRecoveryData(in byte[] account, int userId);
+ void setSnapshotCreatedPendingIntent(in PendingIntent intent, int userId);
+ Map getRecoverySnapshotVersions(int userId);
void setServerParameters(long serverParameters, int userId);
void setRecoveryStatus(in String packageName, in String[] aliases, int status, int userId);
+ Map getRecoveryStatus(in String packageName, int userId);
void setRecoverySecretTypes(in int[] secretTypes, int userId);
int[] getRecoverySecretTypes(int userId);
int[] getPendingRecoverySecretTypes(int userId);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 13fedfe..e303927 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3632,6 +3632,11 @@
<permission android:name="android.permission.READ_RUNTIME_PROFILES"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows an application to turn on / off quiet mode.
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MODIFY_QUIET_MODE"
+ android:protectionLevel="signature|privileged" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
diff --git a/core/res/res/raw/color_fade_vert.vert b/core/res/res/raw/color_fade_vert.vert
index d17437f..b501b06 100644
--- a/core/res/res/raw/color_fade_vert.vert
+++ b/core/res/res/raw/color_fade_vert.vert
@@ -1,6 +1,5 @@
uniform mat4 proj_matrix;
uniform mat4 tex_matrix;
-uniform float scale;
attribute vec2 position;
attribute vec2 uv;
varying vec2 UV;
@@ -9,5 +8,5 @@
{
vec4 transformed_uv = tex_matrix * vec4(uv.x, uv.y, 1.0, 1.0);
UV = transformed_uv.st / transformed_uv.q;
- gl_Position = vec4(scale, scale, 1.0, 1.0) * (proj_matrix * vec4(position.x, position.y, 0.0, 1.0));
+ gl_Position = proj_matrix * vec4(position.x, position.y, 0.0, 1.0);
}
diff --git a/core/tests/coretests/BstatsTestApp/AndroidManifest.xml b/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
index 0cb5498..1e6bdc6 100644
--- a/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
+++ b/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
@@ -17,9 +17,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.coretests.apps.bstatstestapp">
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25"/>
+
<application>
<activity android:name=".TestActivity"
android:exported="true" />
+ <service android:name=".TestService"
+ android:exported="true" />
<service android:name=".IsolatedTestService"
android:exported="true"
android:isolatedProcess="true" />
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java
new file mode 100644
index 0000000..2601f35
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.coretests.apps.bstatstestapp;
+
+import android.os.RemoteException;
+
+import com.android.frameworks.coretests.aidl.ICmdReceiver;
+
+public class BaseCmdReceiver extends ICmdReceiver.Stub {
+ @Override
+ public void doSomeWork(int durationMs) {}
+ @Override
+ public void showApplicationOverlay() throws RemoteException {}
+ @Override
+ public void finishHost() {}
+}
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java
new file mode 100644
index 0000000..d192fbd
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.coretests.apps.bstatstestapp;
+
+import com.android.frameworks.coretests.aidl.ICmdCallback;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+public class Common {
+ private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+
+ public static void doSomeWork(int durationMs) {
+ final long endTime = SystemClock.currentThreadTimeMillis() + durationMs;
+ double x;
+ double y;
+ double z;
+ while (SystemClock.currentThreadTimeMillis() <= endTime) {
+ x = 0.02;
+ x *= 1000;
+ y = x % 5;
+ z = Math.sqrt(y / 100);
+ }
+ }
+
+ public static void notifyLaunched(Intent intent, IBinder binder, String tag) {
+ if (intent == null) {
+ return;
+ }
+
+ final Bundle extras = intent.getExtras();
+ if (extras == null) {
+ return;
+ }
+ final ICmdCallback callback = ICmdCallback.Stub.asInterface(
+ extras.getBinder(EXTRA_KEY_CMD_RECEIVER));
+ try {
+ callback.onLaunched(binder);
+ } catch (RemoteException e) {
+ Log.e(tag, "Error occured while notifying the test: " + e);
+ }
+ }
+}
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
index 1f5f397..892f60e 100644
--- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
@@ -15,17 +15,14 @@
*/
package com.android.coretests.apps.bstatstestapp;
-import com.android.frameworks.coretests.aidl.ICmdReceiver;
-
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.Process;
-import android.os.SystemClock;
import android.util.Log;
public class IsolatedTestService extends Service {
- private static final String TAG = IsolatedTestService.class.getName();
+ private static final String TAG = IsolatedTestService.class.getSimpleName();
@Override
public void onCreate() {
@@ -34,23 +31,20 @@
@Override
public IBinder onBind(Intent intent) {
+ Log.d(TAG, "onBind called. myUid=" + Process.myUid());
return mReceiver.asBinder();
}
- private ICmdReceiver mReceiver = new ICmdReceiver.Stub() {
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "onDestroy called. myUid=" + Process.myUid());
+ }
+
+ private BaseCmdReceiver mReceiver = new BaseCmdReceiver() {
@Override
public void doSomeWork(int durationMs) {
- final long endTime = SystemClock.uptimeMillis() + durationMs;
- double x;
- double y;
- double z;
- while (SystemClock.uptimeMillis() <= endTime) {
- x = 0.02;
- x *= 1000;
- y = x % 5;
- z = Math.sqrt(y / 100);
- }
- };
+ Common.doSomeWork(durationMs);
+ }
@Override
public void finishHost() {
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
index 87b14d9..5c551d5 100644
--- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
@@ -15,19 +15,12 @@
*/
package com.android.coretests.apps.bstatstestapp;
-import com.android.frameworks.coretests.aidl.ICmdCallback;
-import com.android.frameworks.coretests.aidl.ICmdReceiver;
-
import android.app.Activity;
import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.SystemClock;
import android.util.Log;
public class TestActivity extends Activity {
- private static final String TAG = TestActivity.class.getName();
-
- private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+ private static final String TAG = TestActivity.class.getSimpleName();
@Override
public void onCreate(Bundle icicle) {
@@ -37,21 +30,7 @@
}
private void notifyActivityLaunched() {
- if (getIntent() == null) {
- return;
- }
-
- final Bundle extras = getIntent().getExtras();
- if (extras == null) {
- return;
- }
- final ICmdCallback callback = ICmdCallback.Stub.asInterface(
- extras.getBinder(EXTRA_KEY_CMD_RECEIVER));
- try {
- callback.onActivityLaunched(mReceiver.asBinder());
- } catch (RemoteException e) {
- Log.e(TAG, "Error occured while notifying the test: " + e);
- }
+ Common.notifyLaunched(getIntent(), mReceiver.asBinder(), TAG);
}
@Override
@@ -60,24 +39,17 @@
Log.d(TAG, "finish called");
}
- private ICmdReceiver mReceiver = new ICmdReceiver.Stub() {
+ private BaseCmdReceiver mReceiver = new BaseCmdReceiver() {
@Override
public void doSomeWork(int durationMs) {
- final long endTime = SystemClock.uptimeMillis() + durationMs;
- double x;
- double y;
- double z;
- while (SystemClock.uptimeMillis() <= endTime) {
- x = 0.02;
- x *= 1000;
- y = x % 5;
- z = Math.sqrt(y / 100);
- }
- };
+ Common.doSomeWork(durationMs);
+ }
@Override
public void finishHost() {
- finish();
+ if (!isFinishing()) {
+ finish();
+ }
}
};
}
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java
new file mode 100644
index 0000000..8a22aca
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.coretests.apps.bstatstestapp;
+
+import android.R;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class TestService extends Service {
+ private static final String TAG = TestService.class.getSimpleName();
+
+ private static final int FLAG_START_FOREGROUND = 1;
+
+ private static final String NOTIFICATION_CHANNEL_ID = TAG;
+ private static final int NOTIFICATION_ID = 42;
+
+ private static final int TIMEOUT_OVERLAY_SEC = 2;
+
+ private View mOverlay;
+
+ @Override
+ public void onCreate() {
+ Log.d(TAG, "onCreate called. myUid=" + Process.myUid());
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.d(TAG, "onStartCommand called. myUid=" + Process.myUid());
+ if (intent != null && (intent.getFlags() & FLAG_START_FOREGROUND) != 0) {
+ startForeground();
+ }
+ notifyServiceLaunched(intent);
+ return START_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.d(TAG, "onBind called. myUid=" + Process.myUid());
+ return null;
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "onDestroy called. myUid=" + Process.myUid());
+ removeOverlays();
+ }
+
+ private void notifyServiceLaunched(Intent intent) {
+ Common.notifyLaunched(intent, mReceiver.asBinder(), TAG);
+ }
+
+ private void startForeground() {
+ final NotificationManager noMan = getSystemService(NotificationManager.class);
+ noMan.createNotificationChannel(new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT));
+ Log.d(TAG, "Starting foreground. myUid=" + Process.myUid());
+ startForeground(NOTIFICATION_ID,
+ new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_dialog_alert)
+ .build());
+ }
+
+ private void removeOverlays() {
+ if (mOverlay != null) {
+ final WindowManager wm = TestService.this.getSystemService(WindowManager.class);
+ wm.removeView(mOverlay);
+ mOverlay = null;
+ }
+ }
+
+ private BaseCmdReceiver mReceiver = new BaseCmdReceiver() {
+ @Override
+ public void doSomeWork(int durationMs) {
+ Common.doSomeWork(durationMs);
+ }
+
+ @Override
+ public void showApplicationOverlay() throws RemoteException {
+ final WindowManager wm = TestService.this.getSystemService(WindowManager.class);
+ final Point size = new Point();
+ wm.getDefaultDisplay().getSize(size);
+
+ final WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+ wmlp.width = size.x / 2;
+ wmlp.height = size.y / 2;
+ wmlp.gravity = Gravity.CENTER | Gravity.LEFT;
+ wmlp.setTitle(TAG);
+
+ final ViewGroup.LayoutParams vglp = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+
+ mOverlay = new View(TestService.this);
+ mOverlay.setBackgroundColor(Color.GREEN);
+ mOverlay.setLayoutParams(vglp);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Handler handler = new Handler(TestService.this.getMainLooper());
+ handler.post(() -> {
+ wm.addView(mOverlay, wmlp);
+ latch.countDown();
+ });
+ try {
+ if (!latch.await(TIMEOUT_OVERLAY_SEC, TimeUnit.SECONDS)) {
+ throw new RemoteException("Timed out waiting for the overlay");
+ }
+ } catch (InterruptedException e) {
+ throw new RemoteException("Error while adding overlay: " + e.toString());
+ }
+ Log.d(TAG, "Overlay displayed, myUid=" + Process.myUid());
+ }
+
+ @Override
+ public void finishHost() {
+ removeOverlays();
+ stopSelf();
+ }
+ };
+}
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
index 53a181a..6d0239b 100644
--- a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
@@ -17,5 +17,5 @@
package com.android.frameworks.coretests.aidl;
interface ICmdCallback {
- void onActivityLaunched(IBinder receiver);
+ void onLaunched(IBinder receiver);
}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
index c406570..cce8e28 100644
--- a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
@@ -18,5 +18,6 @@
interface ICmdReceiver {
void doSomeWork(int durationMs);
+ void showApplicationOverlay();
void finishHost();
}
\ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
index 6ff0ab2..b5a7bec 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
@@ -960,7 +960,7 @@
}
@Test
- public void testReadKernelUiidCpuFreqTimesLocked_invalidUid() {
+ public void testReadKernelUidCpuFreqTimesLocked_invalidUid() {
// PRECONDITIONS
updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
new file mode 100644
index 0000000..3794b5f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import static android.os.BatteryStats.STATS_SINCE_CHARGED;
+import static android.os.BatteryStats.Uid.NUM_PROCESS_STATE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_CACHED;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.os.BatteryStats;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.Display;
+
+import com.android.internal.util.ArrayUtils;
+
+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 java.util.Arrays;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsImplTest {
+ @Mock
+ private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
+ @Mock
+ private KernelSingleUidTimeReader mKernelSingleUidTimeReader;
+
+ private MockBatteryStatsImpl mBatteryStatsImpl;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
+ when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
+ mBatteryStatsImpl = new MockBatteryStatsImpl()
+ .setKernelUidCpuFreqTimeReader(mKernelUidCpuFreqTimeReader)
+ .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader);
+ }
+
+ @Test
+ public void testUpdateProcStateCpuTimes() {
+ mBatteryStatsImpl.setOnBatteryInternal(true);
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
+
+ final int[] testUids = {10032, 10048, 10145, 10139};
+ final int[] testProcStates = {
+ PROCESS_STATE_BACKGROUND,
+ PROCESS_STATE_FOREGROUND_SERVICE,
+ PROCESS_STATE_TOP,
+ PROCESS_STATE_CACHED
+ };
+ addPendingUids(testUids, testProcStates);
+ final long[][] cpuTimes = {
+ {349734983, 394982394832l, 909834, 348934, 9838},
+ {7498, 1239890, 988, 13298, 98980},
+ {989834, 384098, 98483, 23809, 4984},
+ {4859048, 348903, 4578967, 5973894, 298549}
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(cpuTimes[i]);
+
+ // Verify there are no cpu times initially.
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+
+ mBatteryStatsImpl.updateProcStateCpuTimes();
+
+ verifyNoPendingUids();
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ if (procState == testProcStates[i]) {
+ assertArrayEquals("Uid=" + testUids[i], cpuTimes[i],
+ u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ } else {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+
+ final long[][] delta1 = {
+ {9589, 148934, 309894, 3098493, 98754},
+ {21983, 94983, 4983, 9878493, 84854},
+ {945894, 9089432, 19478, 3834, 7845},
+ {843895, 43948, 949582, 99, 384}
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(delta1[i]);
+ }
+ addPendingUids(testUids, testProcStates);
+
+ mBatteryStatsImpl.updateProcStateCpuTimes();
+
+ verifyNoPendingUids();
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ if (procState == testProcStates[i]) {
+ long[] expectedCpuTimes = cpuTimes[i].clone();
+ for (int j = 0; j < expectedCpuTimes.length; ++j) {
+ expectedCpuTimes[j] += delta1[i][j];
+ }
+ assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes,
+ u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ } else {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ final long[][] delta2 = {
+ {95932, 2943, 49834, 89034, 139},
+ {349, 89605, 5896, 845, 98444},
+ {678, 7498, 9843, 889, 4894},
+ {488, 998, 8498, 394, 574}
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(delta2[i]);
+ }
+ addPendingUids(testUids, testProcStates);
+
+ mBatteryStatsImpl.updateProcStateCpuTimes();
+
+ verifyNoPendingUids();
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ if (procState == testProcStates[i]) {
+ long[] expectedCpuTimes = cpuTimes[i].clone();
+ for (int j = 0; j < expectedCpuTimes.length; ++j) {
+ expectedCpuTimes[j] += delta1[i][j] + delta2[i][j];
+ }
+ assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes,
+ u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ assertArrayEquals("Uid=" + testUids[i], delta2[i],
+ u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ } else {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+ }
+
+ final long[][] delta3 = {
+ {98545, 95768795, 76586, 548945, 57846},
+ {788876, 586, 578459, 8776984, 9578923},
+ {3049509483598l, 4597834, 377654, 94589035, 7854},
+ {9493, 784, 99895, 8974893, 9879843}
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(
+ delta3[i].clone());
+ }
+ addPendingUids(testUids, testProcStates);
+ final int parentUid = testUids[1];
+ final int childUid = 99099;
+ addIsolatedUid(parentUid, childUid);
+ final long[] isolatedUidCpuTimes = {495784, 398473, 4895, 4905, 30984093};
+ when(mKernelSingleUidTimeReader.readDeltaMs(childUid)).thenReturn(isolatedUidCpuTimes);
+
+ mBatteryStatsImpl.updateProcStateCpuTimes();
+
+ verifyNoPendingUids();
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ if (procState == testProcStates[i]) {
+ long[] expectedCpuTimes = cpuTimes[i].clone();
+ for (int j = 0; j < expectedCpuTimes.length; ++j) {
+ expectedCpuTimes[j] += delta1[i][j] + delta2[i][j] + delta3[i][j]
+ + (testUids[i] == parentUid ? isolatedUidCpuTimes[j] : 0);
+ }
+ assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes,
+ u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ long[] expectedScreenOffTimes = delta2[i].clone();
+ for (int j = 0; j < expectedScreenOffTimes.length; ++j) {
+ expectedScreenOffTimes[j] += delta3[i][j]
+ + (testUids[i] == parentUid ? isolatedUidCpuTimes[j] : 0);
+ }
+ assertArrayEquals("Uid=" + testUids[i], expectedScreenOffTimes,
+ u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ } else {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testCopyFromAllUidsCpuTimes() {
+ mBatteryStatsImpl.setOnBatteryInternal(true);
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
+
+ final int[] testUids = {10032, 10048, 10145, 10139};
+ final int[] testProcStates = {
+ PROCESS_STATE_BACKGROUND,
+ PROCESS_STATE_FOREGROUND_SERVICE,
+ PROCESS_STATE_TOP,
+ PROCESS_STATE_CACHED
+ };
+ final int[] pendingUidIdx = {1, 2};
+ updateProcessStates(testUids, testProcStates, pendingUidIdx);
+
+ final SparseArray<long[]> allUidCpuTimes = new SparseArray<>();
+ long[][] allCpuTimes = {
+ {938483, 4985984, 439893},
+ {499, 94904, 27694},
+ {302949085, 39789473, 34792839},
+ {9809485, 9083475, 347889834},
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ allUidCpuTimes.put(testUids[i], allCpuTimes[i]);
+ }
+ when(mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs()).thenReturn(allUidCpuTimes);
+ long[][] expectedCpuTimes = {
+ {843598745, 397843, 32749, 99854},
+ {9834, 5885, 487589, 394},
+ {203984, 439, 9859, 30948},
+ {9389, 858, 239, 349}
+ };
+ for (int i = 0; i < testUids.length; ++i) {
+ final int idx = i;
+ final ArgumentMatcher<long[]> matcher = times -> Arrays.equals(times, allCpuTimes[idx]);
+ when(mKernelSingleUidTimeReader.computeDelta(eq(testUids[i]), argThat(matcher)))
+ .thenReturn(expectedCpuTimes[i]);
+ }
+
+ mBatteryStatsImpl.copyFromAllUidsCpuTimes();
+
+ verifyNoPendingUids();
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ if (procState == testProcStates[i]) {
+ assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes[i],
+ u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ } else {
+ assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+ }
+ }
+ }
+
+ @Test
+ public void testAddCpuTimes() {
+ long[] timesA = null;
+ long[] timesB = null;
+ assertNull(mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+
+ timesA = new long[] {34, 23, 45, 24};
+ assertArrayEquals(timesA, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+
+ timesB = timesA;
+ timesA = null;
+ assertArrayEquals(timesB, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+
+ final long[] expected = {434, 6784, 34987, 9984};
+ timesA = new long[timesB.length];
+ for (int i = 0; i < timesA.length; ++i) {
+ timesA[i] = expected[i] - timesB[i];
+ }
+ assertArrayEquals(expected, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+ }
+
+ private void addIsolatedUid(int parentUid, int childUid) {
+ final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(parentUid);
+ u.addIsolatedUid(childUid);
+ }
+
+ private void addPendingUids(int[] uids, int[] procStates) {
+ final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
+ for (int i = 0; i < uids.length; ++i) {
+ pendingUids.put(uids[i], procStates[i]);
+ }
+ }
+
+ private void updateProcessStates(int[] uids, int[] procStates,
+ int[] pendingUidsIdx) {
+ final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
+ for (int i = 0; i < uids.length; ++i) {
+ final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(uids[i]);
+ if (ArrayUtils.contains(pendingUidsIdx, i)) {
+ u.setProcessStateForTest(PROCESS_STATE_TOP);
+ pendingUids.put(uids[i], procStates[i]);
+ } else {
+ u.setProcessStateForTest(procStates[i]);
+ }
+ }
+ }
+
+ private void verifyNoPendingUids() {
+ final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
+ assertEquals("There shouldn't be any pending uids left: " + pendingUids,
+ 0, pendingUids.size());
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 4e83221..0afec34 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -170,7 +170,6 @@
elapsedTimeUs, STATS_SINCE_CHARGED);
expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND)
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BACKUP)
- + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_HEAVY_WEIGHT)
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_SERVICE)
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 12f5b70..e8f2456 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -26,6 +26,7 @@
BatteryStatsDualTimerTest.class,
BatteryStatsDurationTimerTest.class,
BatteryStatsHelperTest.class,
+ BatteryStatsImplTest.class,
BatteryStatsNoteTest.class,
BatteryStatsSamplingTimerTest.class,
BatteryStatsSensorTest.class,
@@ -36,6 +37,7 @@
BatteryStatsUidTest.class,
BatteryStatsUserLifecycleTests.class,
KernelMemoryBandwidthStatsTest.class,
+ KernelSingleUidTimeReaderTest.class,
KernelUidCpuFreqTimeReaderTest.class,
KernelWakelockReaderTest.class,
LongSamplingCounterArrayTest.class
diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
index 4b197e4..e54fe7d 100644
--- a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
@@ -15,19 +15,25 @@
*/
package com.android.internal.os;
-import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.os.BatteryStats.UID_TIMES_TYPE_ALL;
+import static android.os.BatteryStats.Uid.NUM_PROCESS_STATE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_CACHED;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING;
+import static android.os.BatteryStats.Uid.UID_PROCESS_TYPES;
-import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
import com.android.frameworks.coretests.aidl.ICmdCallback;
import com.android.frameworks.coretests.aidl.ICmdReceiver;
+import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.Context;
@@ -36,6 +42,7 @@
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.BatteryManager;
+import android.os.BatteryStats;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
@@ -45,9 +52,9 @@
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.UiDevice;
+import android.util.DebugUtils;
import android.util.Log;
-import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -61,22 +68,26 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
public class BstatsCpuTimesValidationTest {
- private static final String TAG = BstatsCpuTimesValidationTest.class.getName();
+ private static final String TAG = BstatsCpuTimesValidationTest.class.getSimpleName();
private static final String TEST_PKG = "com.android.coretests.apps.bstatstestapp";
private static final String TEST_ACTIVITY = TEST_PKG + ".TestActivity";
+ private static final String TEST_SERVICE = TEST_PKG + ".TestService";
private static final String ISOLATED_TEST_SERVICE = TEST_PKG + ".IsolatedTestService";
private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+ private static final int FLAG_START_FOREGROUND = 1;
private static final int BATTERY_STATE_TIMEOUT_MS = 2000;
private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 200;
private static final int START_ACTIVITY_TIMEOUT_MS = 2000;
+ private static final int START_FG_SERVICE_TIMEOUT_MS = 2000;
+ private static final int START_SERVICE_TIMEOUT_MS = 2000;
private static final int START_ISOLATED_SERVICE_TIMEOUT_MS = 2000;
- private static final int GENERAL_TIMEOUT_MS = 1000;
- private static final int GENERAL_INTERVAL_MS = 100;
+ private static final int GENERAL_TIMEOUT_MS = 4000;
+ private static final int GENERAL_INTERVAL_MS = 200;
private static final int WORK_DURATION_MS = 2000;
@@ -84,6 +95,7 @@
private static UiDevice sUiDevice;
private static int sTestPkgUid;
private static boolean sCpuFreqTimesAvailable;
+ private static boolean sPerProcStateTimesAvailable;
@BeforeClass
public static void setupOnce() throws Exception {
@@ -92,14 +104,20 @@
sContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_PKG, 0);
- sCpuFreqTimesAvailable = cpuFreqTimesAvailable();
+ checkCpuTimesAvailability();
}
// Checks cpu freq times of system uid as an indication of whether /proc/uid_time_in_state
- // kernel node is available.
- private static boolean cpuFreqTimesAvailable() throws Exception {
- final long[] cpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID);
- return cpuTimes != null;
+ // and /proc/uid/<uid>/time_in_state kernel nodes are available.
+ private static void checkCpuTimesAvailability() throws Exception {
+ batteryOn();
+ SystemClock.sleep(GENERAL_TIMEOUT_MS);
+ batteryOff();
+ final long[] totalCpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID);
+ sCpuFreqTimesAvailable = totalCpuTimes != null;
+ final long[] fgSvcCpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE);
+ sPerProcStateTimesAvailable = fgSvcCpuTimes != null;
}
@Test
@@ -112,7 +130,8 @@
forceStop();
resetBatteryStats();
final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
- assertNull("Initial snapshot should be null", initialSnapshot);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
doSomeWork();
forceStop();
@@ -136,7 +155,8 @@
forceStop();
resetBatteryStats();
final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
- assertNull("Initial snapshot should be null", initialSnapshot);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
doSomeWork();
forceStop();
@@ -166,7 +186,8 @@
forceStop();
resetBatteryStats();
final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
- assertNull("Initial snapshot should be null", initialSnapshot);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
doSomeWorkInIsolatedProcess();
forceStop();
@@ -180,6 +201,224 @@
batteryOffScreenOn();
}
+ @Test
+ public void testCpuFreqTimes_stateTop() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOn();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));
+
+ doSomeWork(PROCESS_STATE_TOP);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testIsolatedCpuFreqTimes_stateService() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOn();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));
+
+ final ICmdReceiver activityReceiver = ICmdReceiver.Stub.asInterface(startActivity());
+ final ICmdReceiver isolatedReceiver = ICmdReceiver.Stub.asInterface(startIsolatedService());
+ try {
+ assertProcState(PROCESS_STATE_TOP);
+ isolatedReceiver.doSomeWork(WORK_DURATION_MS);
+ } finally {
+ activityReceiver.finishHost();
+ isolatedReceiver.finishHost();
+ }
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testCpuFreqTimes_stateTopSleeping() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOff();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP_SLEEPING));
+
+ doSomeWork(PROCESS_STATE_TOP_SLEEPING);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP_SLEEPING);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = cpuTimesMs.length / 2; i < cpuTimesMs.length; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testCpuFreqTimes_stateFgService() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOff();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND_SERVICE));
+
+ doSomeWork(PROCESS_STATE_FOREGROUND_SERVICE);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND_SERVICE);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testCpuFreqTimes_stateFg() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOn();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND));
+
+ doSomeWork(PROCESS_STATE_FOREGROUND);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOff();
+ }
+
+ @Test
+ public void testCpuFreqTimes_stateBg() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOff();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_BACKGROUND));
+
+ doSomeWork(PROCESS_STATE_BACKGROUND);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_BACKGROUND);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testCpuFreqTimes_stateCached() throws Exception {
+ if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+ return;
+ }
+
+ batteryOnScreenOn();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+ initialSnapshot);
+ assertNull("Initial top state snapshot should be null",
+ getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_CACHED));
+
+ doSomeWork(PROCESS_STATE_CACHED);
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_CACHED);
+ final String msgCpuTimes = getAllCpuTimesMsg();
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+ WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
private void assertCpuTimesValid(long[] cpuTimes) {
assertNotNull(cpuTimes);
for (int i = 0; i < cpuTimes.length; ++i) {
@@ -219,6 +458,66 @@
receiver.finishHost();
}
+ private void doSomeWork(int procState) throws Exception {
+ final ICmdReceiver receiver;
+ switch (procState) {
+ case PROCESS_STATE_TOP:
+ receiver = ICmdReceiver.Stub.asInterface(startActivity());
+ break;
+ case PROCESS_STATE_TOP_SLEEPING:
+ receiver = ICmdReceiver.Stub.asInterface(startActivity());
+ break;
+ case PROCESS_STATE_FOREGROUND_SERVICE:
+ receiver = ICmdReceiver.Stub.asInterface(startForegroundService());
+ break;
+ case PROCESS_STATE_FOREGROUND:
+ receiver = ICmdReceiver.Stub.asInterface(startService());
+ receiver.showApplicationOverlay();
+ break;
+ case PROCESS_STATE_BACKGROUND:
+ receiver = ICmdReceiver.Stub.asInterface(startService());
+ break;
+ case PROCESS_STATE_CACHED:
+ receiver = ICmdReceiver.Stub.asInterface(startActivity());
+ receiver.finishHost();
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown state: " + procState);
+ }
+ try {
+ assertProcState(procState);
+ receiver.doSomeWork(WORK_DURATION_MS);
+ } finally {
+ receiver.finishHost();
+ }
+ }
+
+ private void assertProcState(String state) throws Exception {
+ final String expectedState = "(" + state + ")";
+ assertDelayedCondition("", () -> {
+ final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
+ final String actualState = uidStateStr.split(" ")[1];
+ return expectedState.equals(actualState) ? null
+ : "expected=" + expectedState + ", actual" + actualState;
+ });
+ }
+
+ private void assertProcState(int expectedState) throws Exception {
+ assertDelayedCondition("Unexpected proc state", () -> {
+ final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
+ final int amProcState = Integer.parseInt(uidStateStr.split(" ")[0]);
+ final int actualState = BatteryStats.mapToInternalProcessState(amProcState);
+ return (actualState == expectedState) ? null
+ : "expected=" + getStateName(BatteryStats.Uid.class, expectedState)
+ + ", actual=" + getStateName(BatteryStats.Uid.class, actualState)
+ + ", amState=" + getStateName(ActivityManager.class, amProcState);
+ });
+ }
+
+ private String getStateName(Class clazz, int procState) {
+ return DebugUtils.valueToString(clazz, "PROCESS_STATE_", procState);
+ }
+
private IBinder startIsolatedService() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final IBinder[] binders = new IBinder[1];
@@ -248,6 +547,59 @@
return null;
}
+ private IBinder startForegroundService() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Intent launchIntent = new Intent()
+ .setComponent(new ComponentName(TEST_PKG, TEST_SERVICE))
+ .setFlags(FLAG_START_FOREGROUND);
+ final Bundle extras = new Bundle();
+ final IBinder[] binders = new IBinder[1];
+ extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
+ @Override
+ public void onLaunched(IBinder receiver) {
+ binders[0] = receiver;
+ latch.countDown();
+ }
+ });
+ launchIntent.putExtras(extras);
+ sContext.startForegroundService(launchIntent);
+ if (latch.await(START_FG_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ if (binders[0] == null) {
+ fail("Receiver binder should not be null");
+ }
+ return binders[0];
+ } else {
+ fail("Timed out waiting for the test fg service to start; testUid=" + sTestPkgUid);
+ }
+ return null;
+ }
+
+ private IBinder startService() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Intent launchIntent = new Intent()
+ .setComponent(new ComponentName(TEST_PKG, TEST_SERVICE));
+ final Bundle extras = new Bundle();
+ final IBinder[] binders = new IBinder[1];
+ extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
+ @Override
+ public void onLaunched(IBinder receiver) {
+ binders[0] = receiver;
+ latch.countDown();
+ }
+ });
+ launchIntent.putExtras(extras);
+ sContext.startService(launchIntent);
+ if (latch.await(START_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ if (binders[0] == null) {
+ fail("Receiver binder should not be null");
+ }
+ return binders[0];
+ } else {
+ fail("Timed out waiting for the test service to start; testUid=" + sTestPkgUid);
+ }
+ return null;
+ }
+
private IBinder startActivity() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final Intent launchIntent = new Intent()
@@ -256,7 +608,7 @@
final IBinder[] binders = new IBinder[1];
extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
@Override
- public void onActivityLaunched(IBinder receiver) {
+ public void onLaunched(IBinder receiver) {
binders[0] = receiver;
latch.countDown();
}
@@ -274,21 +626,63 @@
return null;
}
+ private static String getAllCpuTimesMsg() throws Exception {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("uid=" + sTestPkgUid + ";");
+ sb.append(UID_TIMES_TYPE_ALL + "=" + getMsgCpuTimesSum(getAllCpuFreqTimes(sTestPkgUid)));
+ for (int i = 0; i < NUM_PROCESS_STATE; ++i) {
+ sb.append("|");
+ sb.append(UID_PROCESS_TYPES[i] + "="
+ + getMsgCpuTimesSum(getAllCpuFreqTimes(sTestPkgUid, i)));
+ }
+ return sb.toString();
+ }
+
+ private static String getMsgCpuTimesSum(long[] cpuTimes) throws Exception {
+ if (cpuTimes == null) {
+ return "(0,0)";
+ }
+ long totalTime = 0;
+ for (int i = 0; i < cpuTimes.length / 2; ++i) {
+ totalTime += cpuTimes[i];
+ }
+ long screenOffTime = 0;
+ for (int i = cpuTimes.length / 2; i < cpuTimes.length; ++i) {
+ screenOffTime += cpuTimes[i];
+ }
+ return "(" + totalTime + "," + screenOffTime + ")";
+ }
+
private static long[] getAllCpuFreqTimes(int uid) throws Exception {
final String checkinDump = executeCmdSilent("dumpsys batterystats --checkin");
- final Pattern pattern = Pattern.compile(uid + ",l,ctf,A,(.*?)\n");
+ final Pattern pattern = Pattern.compile(uid + ",l,ctf," + UID_TIMES_TYPE_ALL + ",(.*?)\n");
final Matcher matcher = pattern.matcher(checkinDump);
if (!matcher.find()) {
return null;
}
- final String[] uidTimesStr = matcher.group(1).split(",");
- final int freqCount = Integer.parseInt(uidTimesStr[0]);
- if (uidTimesStr.length != (2 * freqCount + 1)) {
- fail("Malformed data: " + Arrays.toString(uidTimesStr));
+ return parseCpuTimesStr(matcher.group(1));
+ }
+
+ private static long[] getAllCpuFreqTimes(int uid, int procState) throws Exception {
+ final String checkinDump = executeCmdSilent("dumpsys batterystats --checkin");
+ final Pattern pattern = Pattern.compile(
+ uid + ",l,ctf," + UID_PROCESS_TYPES[procState] + ",(.*?)\n");
+ final Matcher matcher = pattern.matcher(checkinDump);
+ if (!matcher.find()) {
+ return null;
+ }
+ return parseCpuTimesStr(matcher.group(1));
+ }
+
+ private static long[] parseCpuTimesStr(String str) {
+ final String[] cpuTimesStr = str.split(",");
+ final int freqCount = Integer.parseInt(cpuTimesStr[0]);
+ if (cpuTimesStr.length != (2 * freqCount + 1)) {
+ fail("Malformed data: " + Arrays.toString(cpuTimesStr));
}
final long[] cpuTimes = new long[freqCount * 2];
for (int i = 0; i < cpuTimes.length; ++i) {
- cpuTimes[i] = Long.parseLong(uidTimesStr[i + 1]);
+ cpuTimes[i] = Long.parseLong(cpuTimesStr[i + 1]);
}
return cpuTimes;
}
@@ -312,12 +706,12 @@
screenOn();
}
- private void batteryOn() throws Exception {
+ private static void batteryOn() throws Exception {
executeCmd("dumpsys battery unplug");
assertBatteryState(false);
}
- private void batteryOff() throws Exception {
+ private static void batteryOff() throws Exception {
executeCmd("dumpsys battery reset");
assertBatteryState(true);
}
@@ -336,43 +730,41 @@
private void forceStop() throws Exception {
executeCmd("cmd activity force-stop " + TEST_PKG);
- assertUidState(PROCESS_STATE_NONEXISTENT);
+ assertProcState("NONEXISTENT");
}
- private void assertUidState(int state) throws Exception {
- final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
- final int uidState = Integer.parseInt(uidStateStr.split(" ")[0]);
- assertEquals(state, uidState);
- }
-
- private void assertKeyguardUnLocked() {
+ private void assertKeyguardUnLocked() throws Exception {
final KeyguardManager keyguardManager =
(KeyguardManager) sContext.getSystemService(Context.KEYGUARD_SERVICE);
- assertDelayedCondition("Keyguard should be unlocked",
- () -> !keyguardManager.isKeyguardLocked());
+ assertDelayedCondition("Unexpected Keyguard state", () ->
+ keyguardManager.isKeyguardLocked() ? "expected=unlocked" : null
+ );
}
- private void assertScreenInteractive(boolean interactive) {
+ private void assertScreenInteractive(boolean interactive) throws Exception {
final PowerManager powerManager =
(PowerManager) sContext.getSystemService(Context.POWER_SERVICE);
- assertDelayedCondition("Unexpected screen interactive state",
- () -> interactive == powerManager.isInteractive());
+ assertDelayedCondition("Unexpected screen interactive state", () ->
+ interactive == powerManager.isInteractive() ? null : "expected=" + interactive
+ );
}
- private void assertDelayedCondition(String errorMsg, ExpectedCondition condition) {
+ private void assertDelayedCondition(String errMsgPrefix, ExpectedCondition condition)
+ throws Exception {
final long endTime = SystemClock.uptimeMillis() + GENERAL_TIMEOUT_MS;
while (SystemClock.uptimeMillis() <= endTime) {
- if (condition.isTrue()) {
+ if (condition.getErrIfNotTrue() == null) {
return;
}
SystemClock.sleep(GENERAL_INTERVAL_MS);
}
- if (!condition.isTrue()) {
- fail(errorMsg);
+ final String errMsg = condition.getErrIfNotTrue();
+ if (errMsg != null) {
+ fail(errMsgPrefix + ": " + errMsg);
}
}
- private void assertBatteryState(boolean pluggedIn) throws Exception {
+ private static void assertBatteryState(boolean pluggedIn) throws Exception {
final long endTime = SystemClock.uptimeMillis() + BATTERY_STATE_TIMEOUT_MS;
while (isDevicePluggedIn() != pluggedIn && SystemClock.uptimeMillis() <= endTime) {
Thread.sleep(BATTERY_STATE_CHECK_INTERVAL_MS);
@@ -383,13 +775,13 @@
}
}
- private boolean isDevicePluggedIn() {
+ private static boolean isDevicePluggedIn() {
final Intent batteryIntent = sContext.registerReceiver(null,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
return batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0;
}
- private String executeCmd(String cmd) throws Exception {
+ private static String executeCmd(String cmd) throws Exception {
final String result = sUiDevice.executeShellCommand(cmd).trim();
Log.d(TAG, String.format("Result for '%s': %s", cmd, result));
return result;
@@ -400,6 +792,6 @@
}
private interface ExpectedCondition {
- boolean isTrue();
+ String getErrIfNotTrue() throws Exception;
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
new file mode 100644
index 0000000..5d72942
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+
+import com.android.internal.os.KernelSingleUidTimeReader.Injector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelSingleUidTimeReaderTest {
+ private final static int TEST_UID = 2222;
+ private final static int TEST_FREQ_COUNT = 5;
+
+ private KernelSingleUidTimeReader mReader;
+ private TestInjector mInjector;
+
+ @Before
+ public void setUp() {
+ mInjector = new TestInjector();
+ mReader = new KernelSingleUidTimeReader(TEST_FREQ_COUNT, mInjector);
+ }
+
+ @Test
+ public void readDelta() {
+ final SparseArray<long[]> allLastCpuTimes = mReader.getLastUidCpuTimeMs();
+ long[] latestCpuTimes = new long[] {120, 130, 140, 150, 160};
+ mInjector.setData(latestCpuTimes);
+ long[] deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+ assertCpuTimesEqual(latestCpuTimes, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ long[] expectedDeltaTimes = new long[] {200, 340, 1230, 490, 4890};
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ latestCpuTimes[i] += expectedDeltaTimes[i];
+ }
+ mInjector.setData(latestCpuTimes);
+ deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+ assertCpuTimesEqual(expectedDeltaTimes, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // delta should be null if cpu times haven't changed
+ deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // Malformed data (-ve)
+ long[] malformedLatestTimes = new long[latestCpuTimes.length];
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ if (i == 1) {
+ malformedLatestTimes[i] = -4;
+ } else {
+ malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+ }
+ }
+ mInjector.setData(malformedLatestTimes);
+ deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // Malformed data (decreased)
+ malformedLatestTimes = new long[latestCpuTimes.length];
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ if (i == 1) {
+ malformedLatestTimes[i] = latestCpuTimes[i] - 4;
+ } else {
+ malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+ }
+ }
+ mInjector.setData(malformedLatestTimes);
+ deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+ }
+
+ @Test
+ public void readDelta_fileNotAvailable() {
+ mInjector.letReadDataThrowException(true);
+
+ for (int i = 0; i < KernelSingleUidTimeReader.TOTAL_READ_ERROR_COUNT; ++i) {
+ assertTrue(mReader.singleUidCpuTimesAvailable());
+ mReader.readDeltaMs(TEST_UID);
+ }
+ assertFalse(mReader.singleUidCpuTimesAvailable());
+ }
+
+ @Test
+ public void testComputeDelta() {
+ // proc file not available
+ mReader.setSingleUidCpuTimesAvailable(false);
+ long[] latestCpuTimes = new long[] {12, 13, 14, 15, 16};
+ long[] deltaCpuTimes = mReader.computeDelta(TEST_UID, latestCpuTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+
+ // cpu times have changed
+ mReader.setSingleUidCpuTimesAvailable(true);
+ SparseArray<long[]> allLastCpuTimes = mReader.getLastUidCpuTimeMs();
+ long[] lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+ allLastCpuTimes.put(TEST_UID, lastCpuTimes);
+ long[] expectedDeltaTimes = new long[] {123, 324, 43, 989, 80};
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ latestCpuTimes[i] = lastCpuTimes[i] + expectedDeltaTimes[i];
+ }
+ deltaCpuTimes = mReader.computeDelta(TEST_UID, latestCpuTimes);
+ assertCpuTimesEqual(expectedDeltaTimes, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // no change in cpu times
+ deltaCpuTimes = mReader.computeDelta(TEST_UID, latestCpuTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // Malformed cpu times (-ve)
+ long[] malformedLatestTimes = new long[latestCpuTimes.length];
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ if (i == 1) {
+ malformedLatestTimes[i] = -4;
+ } else {
+ malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+ }
+ }
+ deltaCpuTimes = mReader.computeDelta(TEST_UID, malformedLatestTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+ // Malformed cpu times (decreased)
+ for (int i = 0; i < latestCpuTimes.length; ++i) {
+ if (i == 1) {
+ malformedLatestTimes[i] = latestCpuTimes[i] - 4;
+ } else {
+ malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+ }
+ }
+ deltaCpuTimes = mReader.computeDelta(TEST_UID, malformedLatestTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+ }
+
+ @Test
+ public void testGetDelta() {
+ // No last cpu times
+ long[] lastCpuTimes = null;
+ long[] latestCpuTimes = new long[] {12, 13, 14, 15, 16};
+ long[] deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+ assertCpuTimesEqual(latestCpuTimes, deltaCpuTimes);
+
+ // Latest cpu times are -ve
+ lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+ latestCpuTimes = new long[] {15, -10, 19, 21, 23};
+ deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+
+ // Latest cpu times are less than last cpu times
+ lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+ latestCpuTimes = new long[] {15, 11, 21, 34, 171};
+ deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+ assertCpuTimesEqual(null, deltaCpuTimes);
+
+ lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+ latestCpuTimes = new long[] {112, 213, 314, 415, 516};
+ deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+ assertCpuTimesEqual(new long[] {100, 200, 300, 400, 500}, deltaCpuTimes);
+ }
+
+ @Test
+ public void testRemoveUid() {
+ final SparseArray<long[]> lastUidCpuTimes = mReader.getLastUidCpuTimeMs();
+ lastUidCpuTimes.put(12, new long[] {});
+ lastUidCpuTimes.put(16, new long[] {});
+
+ mReader.removeUid(12);
+ assertFalse("Removal failed, cpuTimes=" + lastUidCpuTimes,
+ lastUidCpuTimes.indexOfKey(12) >= 0);
+ mReader.removeUid(16);
+ assertFalse("Removal failed, cpuTimes=" + lastUidCpuTimes,
+ lastUidCpuTimes.indexOfKey(16) >= 0);
+ }
+
+ @Test
+ public void testRemoveUidsRange() {
+ final SparseArray<long[]> lastUidCpuTimes = mReader.getLastUidCpuTimeMs();
+ final int startUid = 12;
+ final int endUid = 24;
+
+ for (int i = startUid; i <= endUid; ++i) {
+ lastUidCpuTimes.put(startUid, new long[] {});
+ }
+ mReader.removeUidsInRange(startUid, endUid);
+ assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+ 0, lastUidCpuTimes.size());
+
+ for (int i = startUid; i <= endUid; ++i) {
+ lastUidCpuTimes.put(startUid, new long[] {});
+ }
+ mReader.removeUidsInRange(startUid - 1, endUid);
+ assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+ 0, lastUidCpuTimes.size());
+
+ for (int i = startUid; i <= endUid; ++i) {
+ lastUidCpuTimes.put(startUid, new long[] {});
+ }
+ mReader.removeUidsInRange(startUid, endUid + 1);
+ assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+ 0, lastUidCpuTimes.size());
+
+ for (int i = startUid; i <= endUid; ++i) {
+ lastUidCpuTimes.put(startUid, new long[] {});
+ }
+ mReader.removeUidsInRange(startUid - 1, endUid + 1);
+ assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+ 0, lastUidCpuTimes.size());
+ }
+
+ private void assertCpuTimesEqual(long[] expected, long[] actual) {
+ assertArrayEquals("Expected=" + Arrays.toString(expected)
+ + ", Actual=" + Arrays.toString(actual), expected, actual);
+ }
+
+ class TestInjector extends Injector {
+ private byte[] mData;
+ private boolean mThrowExcpetion;
+
+ @Override
+ public byte[] readData(String procFile) throws IOException {
+ if (mThrowExcpetion) {
+ throw new IOException("In the test");
+ } else {
+ return mData;
+ }
+ }
+
+ public void setData(long[] cpuTimes) {
+ final ByteBuffer buffer = ByteBuffer.allocate(cpuTimes.length * Long.BYTES);
+ buffer.order(ByteOrder.nativeOrder());
+ for (long time : cpuTimes) {
+ buffer.putLong(time / 10);
+ }
+ mData = buffer.array();
+ }
+
+ public void letReadDataThrowException(boolean throwException) {
+ mThrowExcpetion = throwException;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 63d1e5a..de2fd12 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -16,7 +16,10 @@
package com.android.internal.os;
+import android.util.SparseIntArray;
+
import java.util.ArrayList;
+import java.util.concurrent.Future;
/**
* Mocks a BatteryStatsImpl object.
@@ -33,6 +36,7 @@
mScreenDozeTimer = new BatteryStatsImpl.StopwatchTimer(clocks, null, -1, null,
mOnBatteryTimeBase);
mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
+ setExternalStatsSyncLocked(new DummyExternalStatsSync());
}
MockBatteryStatsImpl() {
@@ -78,6 +82,11 @@
return this;
}
+ public MockBatteryStatsImpl setKernelSingleUidTimeReader(KernelSingleUidTimeReader reader) {
+ mKernelSingleUidTimeReader = reader;
+ return this;
+ }
+
public MockBatteryStatsImpl setKernelCpuSpeedReaders(KernelCpuSpeedReader[] readers) {
mKernelCpuSpeedReaders = readers;
return this;
@@ -102,5 +111,32 @@
mOnBatteryInternal = onBatteryInternal;
return this;
}
+
+ public SparseIntArray getPendingUids() {
+ return mPendingUids;
+ }
+
+ private class DummyExternalStatsSync implements ExternalStatsSync {
+ @Override
+ public Future<?> scheduleSync(String reason, int flags) {
+ return null;
+ }
+
+ @Override
+ public Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
+ return null;
+ }
+
+ @Override
+ public Future<?> scheduleReadProcStateCpuTimes() {
+ return null;
+ }
+
+ @Override
+ public Future<?> scheduleCopyFromAllUidsCpuTimes() {
+ return null;
+ }
+
+ }
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 8e7147c..7bb2859 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -236,6 +236,7 @@
<permission name="android.permission.CHANGE_CONFIGURATION"/>
<permission name="android.permission.DELETE_PACKAGES"/>
<permission name="android.permission.FORCE_STOP_PACKAGES"/>
+ <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
<permission name="android.permission.MANAGE_FINGERPRINT"/>
<permission name="android.permission.MANAGE_USB"/>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 75321fd..1cbb440 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -98,7 +98,7 @@
setClipToActualHeight(false);
setClipChildren(false);
setClipToPadding(false);
- mShelfIcons.setShowAllIcons(false);
+ mShelfIcons.setIsStaticLayout(false);
mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
NotificationPanelView.DOZE_ANIMATION_DURATION);
mShelfState = new ShelfState();
@@ -681,7 +681,8 @@
if (isLayoutRtl()) {
start = getWidth() - start - mCollapsedIcons.getWidth();
}
- int width = (int) NotificationUtils.interpolate(start + mCollapsedIcons.getWidth(),
+ int width = (int) NotificationUtils.interpolate(
+ start + mCollapsedIcons.getFinalTranslationX(),
mShelfIcons.getWidth(),
openedAmount);
mShelfIcons.setActualLayoutWidth(width);
@@ -691,6 +692,9 @@
// we have to ensure that adding the low priority notification won't lead to an
// overflow
collapsedPadding -= (1.0f + OVERFLOW_EARLY_AMOUNT) * mCollapsedIcons.getIconSize();
+ } else {
+ // Partial overflow padding will fill enough space to add extra dots
+ collapsedPadding -= mCollapsedIcons.getPartialOverflowExtraPadding();
}
float padding = NotificationUtils.interpolate(collapsedPadding,
mShelfIcons.getPaddingEnd(),
@@ -700,7 +704,6 @@
mShelfIcons.getPaddingStart(), openedAmount);
mShelfIcons.setActualPaddingStart(paddingStart);
mShelfIcons.setOpenedAmount(openedAmount);
- mShelfIcons.setVisualOverflowAdaption(mCollapsedIcons.getVisualOverflowAdaption());
}
public void setMaxLayoutHeight(int maxLayoutHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 316bd5b..7f4deb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -61,14 +61,10 @@
public void setWorkModeEnabled(boolean enableWorkMode) {
synchronized (mProfiles) {
for (UserInfo ui : mProfiles) {
- if (enableWorkMode) {
- if (!mUserManager.trySetQuietModeDisabled(ui.id, null)) {
- StatusBarManager statusBarManager = (StatusBarManager) mContext
- .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
- statusBarManager.collapsePanels();
- }
- } else {
- mUserManager.setQuietModeEnabled(ui.id, true);
+ if (!mUserManager.trySetQuietModeEnabled(!enableWorkMode, UserHandle.of(ui.id))) {
+ StatusBarManager statusBarManager = (StatusBarManager) mContext
+ .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
+ statusBarManager.collapsePanels();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index a1b49c1..91cae0af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -100,8 +100,10 @@
}.setDuration(200).setDelay(50);
public static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5;
+ public static final int MAX_STATIC_ICONS = 4;
+ private static final int MAX_DOTS = 3;
- private boolean mShowAllIcons = true;
+ private boolean mIsStaticLayout = true;
private final HashMap<View, IconState> mIconStates = new HashMap<>();
private int mDotPadding;
private int mStaticDotRadius;
@@ -115,11 +117,13 @@
private int mSpeedBumpIndex = -1;
private int mIconSize;
private float mOpenedAmount = 0.0f;
- private float mVisualOverflowAdaption;
private boolean mDisallowNextAnimation;
private boolean mAnimationsEnabled = true;
private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons;
private int mDarkOffsetX;
+ // Keep track of the last visible icon so collapsed container can report on its location
+ private IconState mLastVisibleIconState;
+
public NotificationIconContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -163,7 +167,7 @@
mIconSize = child.getWidth();
}
}
- if (mShowAllIcons) {
+ if (mIsStaticLayout) {
resetViewStates();
calculateIconTranslations();
applyIconStates();
@@ -287,7 +291,8 @@
float translationX = getActualPaddingStart();
int firstOverflowIndex = -1;
int childCount = getChildCount();
- int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK : childCount;
+ int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK :
+ mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
float layoutEnd = getLayoutEnd();
float overflowStart = layoutEnd - mIconSize * (2 + OVERFLOW_EARLY_AMOUNT);
boolean hasAmbient = mSpeedBumpIndex != -1 && mSpeedBumpIndex < getChildCount();
@@ -320,23 +325,6 @@
visualOverflowStart += (translationX - overflowStart) / mIconSize
* (mStaticDotRadius * 2 + mDotPadding);
}
- if (mShowAllIcons) {
- // We want to perfectly position the overflow in the static state, such that
- // it's perfectly centered instead of measuring it from the end.
- mVisualOverflowAdaption = 0;
- if (firstOverflowIndex != -1) {
- View firstOverflowView = getChildAt(i);
- IconState overflowState = mIconStates.get(firstOverflowView);
- float totalAmount = layoutEnd - overflowState.xTranslation;
- float newPosition = overflowState.xTranslation + totalAmount / 2
- - totalDotLength / 2
- - mIconSize * 0.5f + mStaticDotRadius;
- mVisualOverflowAdaption = newPosition - visualOverflowStart;
- visualOverflowStart = newPosition;
- }
- } else {
- visualOverflowStart += mVisualOverflowAdaption * (1f - mOpenedAmount);
- }
}
translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
}
@@ -348,20 +336,24 @@
IconState iconState = mIconStates.get(view);
int dotWidth = mStaticDotRadius * 2 + mDotPadding;
iconState.xTranslation = translationX;
- if (numDots <= 3) {
+ if (numDots <= MAX_DOTS) {
if (numDots == 1 && iconState.iconAppearAmount < 0.8f) {
iconState.visibleState = StatusBarIconView.STATE_ICON;
numDots--;
} else {
iconState.visibleState = StatusBarIconView.STATE_DOT;
}
- translationX += (numDots == 3 ? 3 * dotWidth : dotWidth)
+ translationX += (numDots == MAX_DOTS ? MAX_DOTS * dotWidth : dotWidth)
* iconState.iconAppearAmount;
+ mLastVisibleIconState = iconState;
} else {
iconState.visibleState = StatusBarIconView.STATE_HIDDEN;
}
numDots++;
}
+ } else if (childCount > 0) {
+ View lastChild = getChildAt(childCount - 1);
+ mLastVisibleIconState = mIconStates.get(lastChild);
}
boolean center = mDark;
if (center && translationX < getLayoutEnd()) {
@@ -415,13 +407,13 @@
}
/**
- * Sets whether the layout should always show all icons.
+ * Sets whether the layout should always show the same number of icons.
* If this is true, the icon positions will be updated on layout.
* If this if false, the layout is managed from the outside and layouting won't trigger a
* repositioning of the icons.
*/
- public void setShowAllIcons(boolean showAllIcons) {
- mShowAllIcons = showAllIcons;
+ public void setIsStaticLayout(boolean isStaticLayout) {
+ mIsStaticLayout = isStaticLayout;
}
public void setActualLayoutWidth(int actualLayoutWidth) {
@@ -452,6 +444,14 @@
return mActualLayoutWidth;
}
+ public int getFinalTranslationX() {
+ if (mLastVisibleIconState == null) {
+ return 0;
+ }
+
+ return (int) (mLastVisibleIconState.xTranslation + mIconSize * (1 + OVERFLOW_EARLY_AMOUNT));
+ }
+
public void setChangingViewPositions(boolean changingViewPositions) {
mChangingViewPositions = changingViewPositions;
}
@@ -479,19 +479,43 @@
mOpenedAmount = expandAmount;
}
- public float getVisualOverflowAdaption() {
- return mVisualOverflowAdaption;
- }
-
- public void setVisualOverflowAdaption(float visualOverflowAdaption) {
- mVisualOverflowAdaption = visualOverflowAdaption;
- }
-
public boolean hasOverflow() {
+ if (mIsStaticLayout) {
+ return getChildCount() > MAX_STATIC_ICONS;
+ }
+
float width = (getChildCount() + OVERFLOW_EARLY_AMOUNT) * mIconSize;
return width - (getWidth() - getActualPaddingStart() - getActualPaddingEnd()) > 0;
}
+ /**
+ * If the overflow is in the range [1, max_dots - 1) (basically 1 or 2 dots), then
+ * extra padding will have to be accounted for
+ *
+ * This method has no meaning for non-static containers
+ */
+ public boolean hasPartialOverflow() {
+ if (mIsStaticLayout) {
+ int count = getChildCount();
+ return count > MAX_STATIC_ICONS && count <= MAX_STATIC_ICONS + MAX_DOTS;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get padding that can account for extra dots up to the max. The only valid values for
+ * this method are for 1 or 2 dots.
+ * @return only extraDotPadding or extraDotPadding * 2
+ */
+ public int getPartialOverflowExtraPadding() {
+ if (!hasPartialOverflow()) {
+ return 0;
+ }
+
+ return (MAX_STATIC_ICONS + MAX_DOTS - getChildCount()) * (mStaticDotRadius + mDotPadding);
+ }
+
public int getIconSize() {
return mIconSize;
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 690c45b..e1cb154 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -568,13 +568,12 @@
@Override
public FillEventHistory getFillEventHistory() throws RemoteException {
- UserHandle user = getCallingUserHandle();
- int uid = getCallingUid();
+ final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
- AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getFillEventHistory(uid);
+ return service.getFillEventHistory(getCallingUid());
}
}
@@ -583,13 +582,12 @@
@Override
public UserData getUserData() throws RemoteException {
- UserHandle user = getCallingUserHandle();
- int uid = getCallingUid();
+ final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
- AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getUserData(uid);
+ return service.getUserData(getCallingUid());
}
}
@@ -598,26 +596,24 @@
@Override
public void setUserData(UserData userData) throws RemoteException {
- UserHandle user = getCallingUserHandle();
- int uid = getCallingUid();
+ final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
- AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- service.setUserData(uid, userData);
+ service.setUserData(getCallingUid(), userData);
}
}
}
@Override
public boolean isFieldClassificationEnabled() throws RemoteException {
- UserHandle user = getCallingUserHandle();
- int uid = getCallingUid();
+ final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
- AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.isFieldClassificationEnabled();
+ return service.isFieldClassificationEnabled(getCallingUid());
}
}
@@ -625,6 +621,20 @@
}
@Override
+ public ComponentName getAutofillServiceComponentName() throws RemoteException {
+ final int userId = UserHandle.getCallingUserId();
+
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ return service.getServiceComponentName();
+ }
+ }
+
+ return null;
+ }
+
+ @Override
public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback)
throws RemoteException {
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 3361824..4e92108 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -835,7 +835,7 @@
pw.println(mContext.getString(R.string.config_defaultAutofillService));
pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
pw.print(prefix); pw.print("Field classification enabled: ");
- pw.println(isFieldClassificationEnabled());
+ pw.println(isFieldClassificationEnabledLocked());
pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
@@ -1095,7 +1095,18 @@
return false;
}
- boolean isFieldClassificationEnabled() {
+ // Called by AutofillManager, checks UID.
+ boolean isFieldClassificationEnabled(int uid) {
+ synchronized (mLock) {
+ if (!isCalledByServiceLocked("isFieldClassificationEnabled", uid)) {
+ return false;
+ }
+ return isFieldClassificationEnabledLocked();
+ }
+ }
+
+ // Called by internally, no need to check UID.
+ boolean isFieldClassificationEnabledLocked() {
return Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION, 0,
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 7b85a6c..ad43ec2 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -500,6 +500,8 @@
@Override
public void onFillRequestSuccess(int requestFlags, @Nullable FillResponse response,
@NonNull String servicePackageName) {
+ final AutofillId[] fieldClassificationIds;
+
synchronized (mLock) {
if (mDestroyed) {
Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
@@ -510,13 +512,13 @@
processNullResponseLocked(requestFlags);
return;
}
- }
- final AutofillId[] fieldClassificationIds = response.getFieldClassificationIds();
- if (fieldClassificationIds != null && !mService.isFieldClassificationEnabled()) {
- Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
- processNullResponseLocked(requestFlags);
- return;
+ fieldClassificationIds = response.getFieldClassificationIds();
+ if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) {
+ Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
+ processNullResponseLocked(requestFlags);
+ return;
+ }
}
mService.setLastResponse(id, response);
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index f3ccba5..4582430 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -116,6 +116,50 @@
return scheduleSyncLocked("remove-uid", UPDATE_CPU);
}
+ @Override
+ public Future<?> scheduleReadProcStateCpuTimes() {
+ synchronized (mStats) {
+ if (!mStats.mPerProcStateCpuTimesAvailable) {
+ return null;
+ }
+ }
+ synchronized (BatteryExternalStatsWorker.this) {
+ if (!mExecutorService.isShutdown()) {
+ return mExecutorService.submit(mReadProcStateCpuTimesTask);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Future<?> scheduleCopyFromAllUidsCpuTimes() {
+ synchronized (mStats) {
+ if (!mStats.mPerProcStateCpuTimesAvailable) {
+ return null;
+ }
+ }
+ synchronized (BatteryExternalStatsWorker.this) {
+ if (!mExecutorService.isShutdown()) {
+ return mExecutorService.submit(mCopyFromAllUidsCpuTimesTask);
+ }
+ }
+ return null;
+ }
+
+ private final Runnable mReadProcStateCpuTimesTask = new Runnable() {
+ @Override
+ public void run() {
+ mStats.updateProcStateCpuTimes();
+ }
+ };
+
+ private final Runnable mCopyFromAllUidsCpuTimesTask = new Runnable() {
+ @Override
+ public void run() {
+ mStats.copyFromAllUidsCpuTimes();
+ }
+ };
+
public synchronized Future<?> scheduleWrite() {
if (mExecutorService.isShutdown()) {
return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
@@ -185,6 +229,10 @@
}
}
+ if ((updateFlags & UPDATE_CPU) != 0) {
+ mStats.copyFromAllUidsCpuTimes();
+ }
+
// Clean up any UIDs if necessary.
synchronized (mStats) {
for (int uid : uidsToRemove) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 93fb3e3..a057a99 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -207,6 +207,10 @@
}
}
+ private void syncStats(String reason, int flags) {
+ awaitUninterruptibly(mWorker.scheduleSync(reason, flags));
+ }
+
/**
* At the time when the constructor runs, the power manager has not yet been
* initialized. So we initialize the low power observer later.
@@ -225,7 +229,7 @@
public void shutdown() {
Slog.w("BatteryStats", "Writing battery stats before shutdown...");
- awaitUninterruptibly(mWorker.scheduleSync("shutdown", BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("shutdown", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
mStats.shutdownLocked();
@@ -357,7 +361,7 @@
//Slog.i("foo", "SENDING BATTERY INFO:");
//mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
Parcel out = Parcel.obtain();
- awaitUninterruptibly(mWorker.scheduleSync("get-stats", BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
mStats.writeToParcel(out, 0);
}
@@ -372,7 +376,7 @@
//Slog.i("foo", "SENDING BATTERY INFO:");
//mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
Parcel out = Parcel.obtain();
- awaitUninterruptibly(mWorker.scheduleSync("get-stats", BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
mStats.writeToParcel(out, 0);
}
@@ -1237,8 +1241,7 @@
}
mWorker.scheduleSync("dump", BatteryExternalStatsWorker.UPDATE_ALL);
} else if ("--write".equals(arg)) {
- awaitUninterruptibly(mWorker.scheduleSync("dump",
- BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
mStats.writeSyncLocked();
pw.println("Battery stats written.");
@@ -1302,7 +1305,7 @@
flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY;
}
// Fetch data from external sources and update the BatteryStatsImpl object with them.
- awaitUninterruptibly(mWorker.scheduleSync("dump", BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1415,8 +1418,7 @@
}
long ident = Binder.clearCallingIdentity();
try {
- awaitUninterruptibly(mWorker.scheduleSync("get-health-stats-for-uids",
- BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("get-health-stats-for-uids", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
return getHealthStatsForUidLocked(requestUid);
}
@@ -1440,8 +1442,7 @@
long ident = Binder.clearCallingIdentity();
int i=-1;
try {
- awaitUninterruptibly(mWorker.scheduleSync("get-health-stats-for-uids",
- BatteryExternalStatsWorker.UPDATE_ALL));
+ syncStats("get-health-stats-for-uids", BatteryExternalStatsWorker.UPDATE_ALL);
synchronized (mStats) {
final int N = requestUids.length;
final HealthStatsParceler[] results = new HealthStatsParceler[N];
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index c2167eb..85686ae 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -99,7 +99,7 @@
private final float mProjMatrix[] = new float[16];
private final int[] mGLBuffers = new int[2];
private int mTexCoordLoc, mVertexLoc, mTexUnitLoc, mProjMatrixLoc, mTexMatrixLoc;
- private int mOpacityLoc, mScaleLoc, mGammaLoc, mSaturationLoc;
+ private int mOpacityLoc, mGammaLoc, mSaturationLoc;
private int mProgram;
// Vertex and corresponding texture coordinates.
@@ -246,7 +246,6 @@
mOpacityLoc = GLES20.glGetUniformLocation(mProgram, "opacity");
mGammaLoc = GLES20.glGetUniformLocation(mProgram, "gamma");
mSaturationLoc = GLES20.glGetUniformLocation(mProgram, "saturation");
- mScaleLoc = GLES20.glGetUniformLocation(mProgram, "scale");
mTexUnitLoc = GLES20.glGetUniformLocation(mProgram, "texUnit");
GLES20.glUseProgram(mProgram);
@@ -395,9 +394,8 @@
double sign = cos < 0 ? -1 : 1;
float opacity = (float) -Math.pow(one_minus_level, 2) + 1;
float saturation = (float) Math.pow(level, 4);
- float scale = (float) ((-Math.pow(one_minus_level, 2) + 1) * 0.1d + 0.9d);
float gamma = (float) ((0.5d * sign * Math.pow(cos, 2) + 0.5d) * 0.9d + 0.1d);
- drawFaded(opacity, 1.f / gamma, saturation, scale);
+ drawFaded(opacity, 1.f / gamma, saturation);
if (checkGlErrors("drawFrame")) {
return false;
}
@@ -409,10 +407,10 @@
return showSurface(1.0f);
}
- private void drawFaded(float opacity, float gamma, float saturation, float scale) {
+ private void drawFaded(float opacity, float gamma, float saturation) {
if (DEBUG) {
Slog.d(TAG, "drawFaded: opacity=" + opacity + ", gamma=" + gamma +
- ", saturation=" + saturation + ", scale=" + scale);
+ ", saturation=" + saturation);
}
// Use shaders
GLES20.glUseProgram(mProgram);
@@ -423,7 +421,6 @@
GLES20.glUniform1f(mOpacityLoc, opacity);
GLES20.glUniform1f(mGammaLoc, gamma);
GLES20.glUniform1f(mSaturationLoc, saturation);
- GLES20.glUniform1f(mScaleLoc, scale);
// Use textures
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java
index 26edf62..dc95d41 100644
--- a/services/core/java/com/android/server/location/ContextHubService.java
+++ b/services/core/java/com/android/server/location/ContextHubService.java
@@ -482,7 +482,7 @@
IContextHubClient client = mDefaultClientMap.get(contextHubHandle);
success = (client.sendMessageToNanoApp(message) ==
- ContextHubTransaction.TRANSACTION_SUCCESS);
+ ContextHubTransaction.RESULT_SUCCESS);
} else {
Log.e(TAG, "Failed to send nanoapp message - nanoapp with handle "
+ nanoAppHandle + " does not exist.");
@@ -642,7 +642,7 @@
if (nanoAppBinary == null) {
Log.e(TAG, "NanoAppBinary cannot be null in loadNanoAppOnHub");
transactionCallback.onTransactionComplete(
- ContextHubTransaction.TRANSACTION_FAILED_BAD_PARAMS);
+ ContextHubTransaction.RESULT_FAILED_BAD_PARAMS);
return;
}
@@ -817,7 +817,7 @@
if (mContextHubProxy == null) {
try {
callback.onTransactionComplete(
- ContextHubTransaction.TRANSACTION_FAILED_HAL_UNAVAILABLE);
+ ContextHubTransaction.RESULT_FAILED_HAL_UNAVAILABLE);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException while calling onTransactionComplete", e);
}
@@ -828,7 +828,7 @@
+ ContextHubTransaction.typeToString(transactionType, false /* upperCase */)
+ " transaction for invalid hub ID " + contextHubId);
try {
- callback.onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_BAD_PARAMS);
+ callback.onTransactionComplete(ContextHubTransaction.RESULT_FAILED_BAD_PARAMS);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException while calling onTransactionComplete", e);
}
diff --git a/services/core/java/com/android/server/location/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
index 7a57dd3..c356b63 100644
--- a/services/core/java/com/android/server/location/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
@@ -215,17 +215,17 @@
static int toTransactionResult(int halResult) {
switch (halResult) {
case Result.OK:
- return ContextHubTransaction.TRANSACTION_SUCCESS;
+ return ContextHubTransaction.RESULT_SUCCESS;
case Result.BAD_PARAMS:
- return ContextHubTransaction.TRANSACTION_FAILED_BAD_PARAMS;
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
case Result.NOT_INIT:
- return ContextHubTransaction.TRANSACTION_FAILED_UNINITIALIZED;
+ return ContextHubTransaction.RESULT_FAILED_UNINITIALIZED;
case Result.TRANSACTION_PENDING:
- return ContextHubTransaction.TRANSACTION_FAILED_PENDING;
+ return ContextHubTransaction.RESULT_FAILED_PENDING;
case Result.TRANSACTION_FAILED:
case Result.UNKNOWN_FAILURE:
default: /* fall through */
- return ContextHubTransaction.TRANSACTION_FAILED_UNKNOWN;
+ return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
}
}
}
diff --git a/services/core/java/com/android/server/location/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/ContextHubTransactionManager.java
index 412d43d..cced781 100644
--- a/services/core/java/com/android/server/location/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/ContextHubTransactionManager.java
@@ -120,7 +120,7 @@
@Override
/* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
- if (result == ContextHubTransaction.TRANSACTION_SUCCESS) {
+ if (result == ContextHubTransaction.RESULT_SUCCESS) {
// NOTE: The legacy JNI code used to do a query right after a load success
// to synchronize the service cache. Instead store the binary that was
// requested to load to update the cache later without doing a query.
@@ -130,7 +130,7 @@
}
try {
onCompleteCallback.onTransactionComplete(result);
- if (result == ContextHubTransaction.TRANSACTION_SUCCESS) {
+ if (result == ContextHubTransaction.RESULT_SUCCESS) {
mClientManager.onNanoAppLoaded(contextHubId, nanoAppBinary.getNanoAppId());
}
} catch (RemoteException e) {
@@ -166,12 +166,12 @@
@Override
/* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
- if (result == ContextHubTransaction.TRANSACTION_SUCCESS) {
+ if (result == ContextHubTransaction.RESULT_SUCCESS) {
mNanoAppStateManager.removeNanoAppInstance(contextHubId, nanoAppId);
}
try {
onCompleteCallback.onTransactionComplete(result);
- if (result == ContextHubTransaction.TRANSACTION_SUCCESS) {
+ if (result == ContextHubTransaction.RESULT_SUCCESS) {
mClientManager.onNanoAppUnloaded(contextHubId, nanoAppId);
}
} catch (RemoteException e) {
@@ -334,8 +334,8 @@
transaction.onTransactionComplete(
(result == TransactionResult.SUCCESS) ?
- ContextHubTransaction.TRANSACTION_SUCCESS :
- ContextHubTransaction.TRANSACTION_FAILED_AT_HUB);
+ ContextHubTransaction.RESULT_SUCCESS :
+ ContextHubTransaction.RESULT_FAILED_AT_HUB);
removeTransactionAndStartNext();
}
@@ -356,7 +356,7 @@
return;
}
- transaction.onQueryResponse(ContextHubTransaction.TRANSACTION_SUCCESS, nanoAppStateList);
+ transaction.onQueryResponse(ContextHubTransaction.RESULT_SUCCESS, nanoAppStateList);
removeTransactionAndStartNext();
}
@@ -416,7 +416,7 @@
if (!transaction.isComplete()) {
Log.d(TAG, transaction + " timed out");
transaction.onTransactionComplete(
- ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
+ ContextHubTransaction.RESULT_FAILED_TIMEOUT);
removeTransactionAndStartNext();
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index aa55930..0481dab 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1943,6 +1943,15 @@
return mRecoverableKeyStoreManager.getRecoveryData(account, userId);
}
+ public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId)
+ throws RemoteException {
+ mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent, userId);
+ }
+
+ public Map getRecoverySnapshotVersions(int userId) throws RemoteException {
+ return mRecoverableKeyStoreManager.getRecoverySnapshotVersions(userId);
+ }
+
@Override
public void setServerParameters(long serverParameters, int userId) throws RemoteException {
mRecoverableKeyStoreManager.setServerParameters(serverParameters, userId);
@@ -1954,6 +1963,10 @@
mRecoverableKeyStoreManager.setRecoveryStatus(packageName, aliases, status, userId);
}
+ public Map getRecoveryStatus(@Nullable String packageName, int userId) throws RemoteException {
+ return mRecoverableKeyStoreManager.getRecoveryStatus(packageName, userId);
+ }
+
@Override
public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType
int[] secretTypes, int userId) throws RemoteException {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
index 37aeb3a..25428e7 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
@@ -20,10 +20,13 @@
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
+import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
@@ -39,6 +42,7 @@
*/
public class KeySyncUtils {
+ private static final String PUBLIC_KEY_FACTORY_ALGORITHM = "EC";
private static final String RECOVERY_KEY_ALGORITHM = "AES";
private static final int RECOVERY_KEY_SIZE_BITS = 256;
@@ -237,6 +241,21 @@
}
/**
+ * Deserializes a X509 public key.
+ *
+ * @param key The bytes of the key.
+ * @return The key.
+ * @throws NoSuchAlgorithmException if the public key algorithm is unavailable.
+ * @throws InvalidKeySpecException if the bytes of the key are not a valid key.
+ */
+ public static PublicKey deserializePublicKey(byte[] key)
+ throws NoSuchAlgorithmException, InvalidKeySpecException {
+ KeyFactory keyFactory = KeyFactory.getInstance(PUBLIC_KEY_FACTORY_ALGORITHM);
+ X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(key);
+ return keyFactory.generatePublic(publicKeySpec);
+ }
+
+ /**
* Returns the concatenation of all the given {@code arrays}.
*/
@VisibleForTesting
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java
new file mode 100644
index 0000000..0f17294
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * In memory storage for listeners to be notified when new recovery snapshot is available.
+ * Note: implementation is not thread safe and it is used to mock final {@link PendingIntent}
+ * class.
+ *
+ * @hide
+ */
+public class ListenersStorage {
+ private Map<Integer, PendingIntent> mAgentIntents = new HashMap<>();
+
+ private static final ListenersStorage mInstance = new ListenersStorage();
+ public static ListenersStorage getInstance() {
+ return mInstance;
+ }
+
+ /**
+ * Sets new listener for the recovery agent, identified by {@code uid}
+ *
+ * @param recoveryAgentUid uid
+ * @param intent PendingIntent which will be triggered than new snapshot is available.
+ */
+ public void setSnapshotListener(int recoveryAgentUid, @Nullable PendingIntent intent) {
+ mAgentIntents.put(recoveryAgentUid, intent);
+ }
+
+ /**
+ * Notifies recovery agent, that new snapshot is available.
+ * Does nothing if a listener was not registered.
+ *
+ * @param recoveryAgentUid uid.
+ */
+ public void recoverySnapshotAvailable(int recoveryAgentUid) {
+ PendingIntent intent = mAgentIntents.get(recoveryAgentUid);
+ if (intent != null) {
+ try {
+ intent.send();
+ } catch (PendingIntent.CanceledException e) {
+ // Ignore - sending intent is not allowed.
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index e459f28..48f4626 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.Context;
import android.os.Binder;
import android.os.RemoteException;
@@ -30,9 +31,16 @@
import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* Class with {@link RecoverableKeyStoreLoader} API implementation and internal methods to interact
@@ -44,7 +52,10 @@
private static final String TAG = "RecoverableKeyStoreManager";
private static RecoverableKeyStoreManager mInstance;
- private Context mContext;
+
+ private final Context mContext;
+ private final RecoverableKeyStoreDb mDatabase;
+ private final RecoverySessionStorage mRecoverySessionStorage;
/**
* Returns a new or existing instance.
@@ -53,14 +64,23 @@
*/
public static synchronized RecoverableKeyStoreManager getInstance(Context mContext) {
if (mInstance == null) {
- mInstance = new RecoverableKeyStoreManager(mContext);
+ RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(mContext);
+ mInstance = new RecoverableKeyStoreManager(
+ mContext.getApplicationContext(),
+ db,
+ new RecoverySessionStorage());
}
return mInstance;
}
@VisibleForTesting
- RecoverableKeyStoreManager(Context context) {
+ RecoverableKeyStoreManager(
+ Context context,
+ RecoverableKeyStoreDb recoverableKeyStoreDb,
+ RecoverySessionStorage recoverySessionStorage) {
mContext = context;
+ mDatabase = recoverableKeyStoreDb;
+ mRecoverySessionStorage = recoverySessionStorage;
}
public int initRecoveryService(
@@ -77,7 +97,7 @@
* @return recovery data
* @hide
*/
- public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId)
+ public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId)
throws RemoteException {
checkRecoverKeyStorePermission();
final int callingUid = Binder.getCallingUid(); // Recovery agent uid.
@@ -100,6 +120,24 @@
RecoverableKeyStoreLoader.UNINITIALIZED_RECOVERY_PUBLIC_KEY);
}
+ public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId)
+ throws RemoteException {
+ checkRecoverKeyStorePermission();
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Gets recovery snapshot versions for all accounts. Note that snapshot may have 0 application
+ * keys, but it still needs to be synced, if previous versions were not empty.
+ *
+ * @return Map from Recovery agent account to snapshot version.
+ */
+ public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions(int userId)
+ throws RemoteException {
+ checkRecoverKeyStorePermission();
+ throw new UnsupportedOperationException();
+ }
+
public void setServerParameters(long serverParameters, int userId) throws RemoteException {
checkRecoverKeyStorePermission();
throw new UnsupportedOperationException();
@@ -113,6 +151,21 @@
}
/**
+ * Gets recovery status for keys {@code packageName}.
+ *
+ * @param packageName which recoverable keys statuses will be returned
+ * @return Map from KeyStore alias to recovery status
+ */
+ public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName, int userId)
+ throws RemoteException {
+ // Any application should be able to check status for its own keys.
+ // If caller is a recovery agent it can check statuses for other packages, but
+ // only for recoverable keys it manages.
+ checkRecoverKeyStorePermission();
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Sets recovery secrets list used by all recovery agents for given {@code userId}
*
* @hide
@@ -130,7 +183,7 @@
* @return secret types
* @hide
*/
- public int[] getRecoverySecretTypes(int userId) throws RemoteException {
+ public @NonNull int[] getRecoverySecretTypes(int userId) throws RemoteException {
checkRecoverKeyStorePermission();
throw new UnsupportedOperationException();
}
@@ -141,7 +194,7 @@
* @return secret types
* @hide
*/
- public int[] getPendingRecoverySecretTypes(int userId) throws RemoteException {
+ public @NonNull int[] getPendingRecoverySecretTypes(int userId) throws RemoteException {
checkRecoverKeyStorePermission();
throw new UnsupportedOperationException();
}
@@ -161,10 +214,16 @@
/**
* Initializes recovery session.
*
- * @return recovery claim
+ * @param sessionId A unique ID to identify the recovery session.
+ * @param verifierPublicKey X509-encoded public key.
+ * @param vaultParams Additional params associated with vault.
+ * @param vaultChallenge Challenge issued by vault service.
+ * @param secrets Lock-screen hashes. Should have a single element. TODO: why is this a list?
+ * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
+ *
* @hide
*/
- public byte[] startRecoverySession(
+ public @NonNull byte[] startRecoverySession(
@NonNull String sessionId,
@NonNull byte[] verifierPublicKey,
@NonNull byte[] vaultParams,
@@ -173,7 +232,40 @@
int userId)
throws RemoteException {
checkRecoverKeyStorePermission();
- throw new UnsupportedOperationException();
+
+ if (secrets.size() != 1) {
+ // TODO: support multiple secrets
+ throw new RemoteException("Only a single KeyStoreRecoveryMetadata is supported");
+ }
+
+ byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+ byte[] kfHash = secrets.get(0).getSecret();
+ mRecoverySessionStorage.add(
+ userId, new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant));
+
+ try {
+ byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash);
+ PublicKey publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey);
+ return KeySyncUtils.encryptRecoveryClaim(
+ publicKey,
+ vaultParams,
+ vaultChallenge,
+ thmKfHash,
+ keyClaimant);
+ } catch (NoSuchAlgorithmException e) {
+ // Should never happen: all the algorithms used are required by AOSP implementations.
+ throw new RemoteException(
+ "Missing required algorithm",
+ e,
+ /*enableSuppression=*/ true,
+ /*writeableStackTrace=*/ true);
+ } catch (InvalidKeySpecException | InvalidKeyException e) {
+ throw new RemoteException(
+ "Not a valid X509 key",
+ e,
+ /*enableSuppression=*/ true,
+ /*writeableStackTrace=*/ true);
+ }
}
public void recoverKeys(
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
new file mode 100644
index 0000000..bc56ae1
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore.storage;
+
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.security.auth.Destroyable;
+
+/**
+ * Stores pending recovery sessions in memory. We do not write these to disk, as it contains hashes
+ * of the user's lock screen.
+ *
+ * @hide
+ */
+public class RecoverySessionStorage implements Destroyable {
+
+ private final SparseArray<ArrayList<Entry>> mSessionsByUid = new SparseArray<>();
+
+ /**
+ * Returns the session for the given user with the given id.
+ *
+ * @param uid The uid of the recovery agent who created the session.
+ * @param sessionId The unique identifier for the session.
+ * @return The session info.
+ *
+ * @hide
+ */
+ @Nullable
+ public Entry get(int uid, String sessionId) {
+ ArrayList<Entry> userEntries = mSessionsByUid.get(uid);
+ if (userEntries == null) {
+ return null;
+ }
+ for (Entry entry : userEntries) {
+ if (sessionId.equals(entry.mSessionId)) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds a pending session for the given user.
+ *
+ * @param uid The uid of the recovery agent who created the session.
+ * @param entry The session info.
+ *
+ * @hide
+ */
+ public void add(int uid, Entry entry) {
+ if (mSessionsByUid.get(uid) == null) {
+ mSessionsByUid.put(uid, new ArrayList<>());
+ }
+ mSessionsByUid.get(uid).add(entry);
+ }
+
+ /**
+ * Removes all sessions associated with the given recovery agent uid.
+ *
+ * @param uid The uid of the recovery agent whose sessions to remove.
+ *
+ * @hide
+ */
+ public void remove(int uid) {
+ ArrayList<Entry> entries = mSessionsByUid.get(uid);
+ if (entries == null) {
+ return;
+ }
+ for (Entry entry : entries) {
+ entry.destroy();
+ }
+ mSessionsByUid.remove(uid);
+ }
+
+ /**
+ * Returns the total count of pending sessions.
+ *
+ * @hide
+ */
+ public int size() {
+ int size = 0;
+ int numberOfUsers = mSessionsByUid.size();
+ for (int i = 0; i < numberOfUsers; i++) {
+ ArrayList<Entry> entries = mSessionsByUid.valueAt(i);
+ size += entries.size();
+ }
+ return size;
+ }
+
+ /**
+ * Wipes the memory of any sensitive information (i.e., lock screen hashes and key claimants).
+ *
+ * @hide
+ */
+ @Override
+ public void destroy() {
+ int numberOfUids = mSessionsByUid.size();
+ for (int i = 0; i < numberOfUids; i++) {
+ ArrayList<Entry> entries = mSessionsByUid.valueAt(i);
+ for (Entry entry : entries) {
+ entry.destroy();
+ }
+ }
+ }
+
+ /**
+ * Information about a recovery session.
+ *
+ * @hide
+ */
+ public static class Entry implements Destroyable {
+ private final byte[] mLskfHash;
+ private final byte[] mKeyClaimant;
+ private final String mSessionId;
+
+ /**
+ * @hide
+ */
+ public Entry(String sessionId, byte[] lskfHash, byte[] keyClaimant) {
+ this.mLskfHash = lskfHash;
+ this.mSessionId = sessionId;
+ this.mKeyClaimant = keyClaimant;
+ }
+
+ /**
+ * Returns the hash of the lock screen associated with the recovery attempt.
+ *
+ * @hide
+ */
+ public byte[] getLskfHash() {
+ return mLskfHash;
+ }
+
+ /**
+ * Returns the key generated for this recovery attempt (used to decrypt data returned by
+ * the server).
+ *
+ * @hide
+ */
+ public byte[] getKeyClaimant() {
+ return mKeyClaimant;
+ }
+
+ /**
+ * Overwrites the memory for the lskf hash and key claimant.
+ *
+ * @hide
+ */
+ @Override
+ public void destroy() {
+ Arrays.fill(mLskfHash, (byte) 0);
+ Arrays.fill(mKeyClaimant, (byte) 0);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 03cd4f1..768eb8f 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -27,6 +27,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
+import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.IStopUserCallback;
import android.app.KeyguardManager;
@@ -38,6 +39,7 @@
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ShortcutServiceInternal;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -386,7 +388,7 @@
/**
* Start an {@link IntentSender} when user is unlocked after disabling quiet mode.
*
- * @see {@link #trySetQuietModeDisabled(int, IntentSender)}
+ * @see {@link #trySetQuietModeEnabled(String, boolean, int, IntentSender)}
*/
private class DisableQuietModeUserUnlockedCallback extends IProgressListener.Stub {
private final IntentSender mTarget;
@@ -784,48 +786,114 @@
}
@Override
- public void setQuietModeEnabled(int userHandle, boolean enableQuietMode, IntentSender target) {
- checkManageUsersPermission("silence profile");
- boolean changed = false;
- UserInfo profile, parent;
- synchronized (mPackagesLock) {
- synchronized (mUsersLock) {
- profile = getUserInfoLU(userHandle);
- parent = getProfileParentLU(userHandle);
+ public boolean trySetQuietModeEnabled(@NonNull String callingPackage, boolean enableQuietMode,
+ int userHandle, @Nullable IntentSender target) {
+ Preconditions.checkNotNull(callingPackage);
+ if (enableQuietMode && target != null) {
+ throw new IllegalArgumentException(
+ "target should only be specified when we are disabling quiet mode.");
+ }
+
+ if (!isAllowedToSetWorkMode(callingPackage, Binder.getCallingUid())) {
+ throw new SecurityException("Not allowed to call trySetQuietModeEnabled, "
+ + "caller is foreground default launcher "
+ + "nor with MANAGE_USERS/MODIFY_QUIET_MODE permission");
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (enableQuietMode) {
+ setQuietModeEnabled(userHandle, true /* enableQuietMode */, target);
+ return true;
+ } else {
+ boolean needToShowConfirmCredential =
+ mLockPatternUtils.isSecure(userHandle)
+ && !StorageManager.isUserKeyUnlocked(userHandle);
+ if (needToShowConfirmCredential) {
+ showConfirmCredentialToDisableQuietMode(userHandle, target);
+ return false;
+ } else {
+ setQuietModeEnabled(userHandle, false /* enableQuietMode */, target);
+ return true;
+ }
}
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * An app can modify quiet mode if the caller meets one of the condition:
+ * <ul>
+ * <li>Has system UID or root UID</li>
+ * <li>Has {@link Manifest.permission#MODIFY_QUIET_MODE}</li>
+ * <li>Has {@link Manifest.permission#MANAGE_USERS}</li>
+ * </ul>
+ */
+ private boolean isAllowedToSetWorkMode(String callingPackage, int callingUid) {
+ if (hasManageUsersPermission()) {
+ return true;
+ }
+
+ final boolean hasModifyQuietModePermission = ActivityManager.checkComponentPermission(
+ Manifest.permission.MODIFY_QUIET_MODE,
+ callingUid, -1, true) == PackageManager.PERMISSION_GRANTED;
+ if (hasModifyQuietModePermission) {
+ return true;
+ }
+
+ final ShortcutServiceInternal shortcutInternal =
+ LocalServices.getService(ShortcutServiceInternal.class);
+ if (shortcutInternal != null) {
+ boolean isForegroundLauncher =
+ shortcutInternal.isForegroundDefaultLauncher(callingPackage, callingUid);
+ if (isForegroundLauncher) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void setQuietModeEnabled(
+ int userHandle, boolean enableQuietMode, IntentSender target) {
+ final UserInfo profile, parent;
+ final UserData profileUserData;
+ synchronized (mUsersLock) {
+ profile = getUserInfoLU(userHandle);
+ parent = getProfileParentLU(userHandle);
+
if (profile == null || !profile.isManagedProfile()) {
throw new IllegalArgumentException("User " + userHandle + " is not a profile");
}
- if (profile.isQuietModeEnabled() != enableQuietMode) {
- profile.flags ^= UserInfo.FLAG_QUIET_MODE;
- writeUserLP(getUserDataLU(profile.id));
- changed = true;
+ if (profile.isQuietModeEnabled() == enableQuietMode) {
+ Slog.i(LOG_TAG, "Quiet mode is already " + enableQuietMode);
+ return;
}
+ profile.flags ^= UserInfo.FLAG_QUIET_MODE;
+ profileUserData = getUserDataLU(profile.id);
}
- if (changed) {
- long identity = Binder.clearCallingIdentity();
- try {
- if (enableQuietMode) {
- ActivityManager.getService().stopUser(userHandle, /* force */true, null);
- LocalServices.getService(ActivityManagerInternal.class)
- .killForegroundAppsForUser(userHandle);
- } else {
- IProgressListener callback = target != null
- ? new DisableQuietModeUserUnlockedCallback(target)
- : null;
- ActivityManager.getService().startUserInBackgroundWithListener(
- userHandle, callback);
- }
- } catch (RemoteException e) {
- Slog.e(LOG_TAG, "fail to start/stop user for quiet mode", e);
- } finally {
- Binder.restoreCallingIdentity(identity);
+ synchronized (mPackagesLock) {
+ writeUserLP(profileUserData);
+ }
+ try {
+ if (enableQuietMode) {
+ ActivityManager.getService().stopUser(userHandle, /* force */true, null);
+ LocalServices.getService(ActivityManagerInternal.class)
+ .killForegroundAppsForUser(userHandle);
+ } else {
+ IProgressListener callback = target != null
+ ? new DisableQuietModeUserUnlockedCallback(target)
+ : null;
+ ActivityManager.getService().startUserInBackgroundWithListener(
+ userHandle, callback);
}
-
- broadcastProfileAvailabilityChanges(profile.getUserHandle(), parent.getUserHandle(),
- enableQuietMode);
+ } catch (RemoteException e) {
+ // Should not happen, same process.
+ e.rethrowAsRuntimeException();
}
+ broadcastProfileAvailabilityChanges(profile.getUserHandle(), parent.getUserHandle(),
+ enableQuietMode);
}
@Override
@@ -842,54 +910,42 @@
}
}
- @Override
- public boolean trySetQuietModeDisabled(
+ /**
+ * Show confirm credential screen to unlock user in order to turn off quiet mode.
+ */
+ private void showConfirmCredentialToDisableQuietMode(
@UserIdInt int userHandle, @Nullable IntentSender target) {
- checkManageUsersPermission("silence profile");
- if (StorageManager.isUserKeyUnlocked(userHandle)
- || !mLockPatternUtils.isSecure(userHandle)) {
- // if the user is already unlocked, no need to show a profile challenge
- setQuietModeEnabled(userHandle, false, target);
- return true;
+ // otherwise, we show a profile challenge to trigger decryption of the user
+ final KeyguardManager km = (KeyguardManager) mContext.getSystemService(
+ Context.KEYGUARD_SERVICE);
+ // We should use userHandle not credentialOwnerUserId here, as even if it is unified
+ // lock, confirm screenlock page will know and show personal challenge, and unlock
+ // work profile when personal challenge is correct
+ final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null,
+ userHandle);
+ if (unlockIntent == null) {
+ return;
}
-
- long identity = Binder.clearCallingIdentity();
- try {
- // otherwise, we show a profile challenge to trigger decryption of the user
- final KeyguardManager km = (KeyguardManager) mContext.getSystemService(
- Context.KEYGUARD_SERVICE);
- // We should use userHandle not credentialOwnerUserId here, as even if it is unified
- // lock, confirm screenlock page will know and show personal challenge, and unlock
- // work profile when personal challenge is correct
- final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null,
- userHandle);
- if (unlockIntent == null) {
- return false;
- }
- final Intent callBackIntent = new Intent(
- ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK);
- if (target != null) {
- callBackIntent.putExtra(Intent.EXTRA_INTENT, target);
- }
- callBackIntent.putExtra(Intent.EXTRA_USER_ID, userHandle);
- callBackIntent.setPackage(mContext.getPackageName());
- callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(
- mContext,
- 0,
- callBackIntent,
- PendingIntent.FLAG_CANCEL_CURRENT |
- PendingIntent.FLAG_ONE_SHOT |
- PendingIntent.FLAG_IMMUTABLE);
- // After unlocking the challenge, it will disable quiet mode and run the original
- // intentSender
- unlockIntent.putExtra(Intent.EXTRA_INTENT, pendingIntent.getIntentSender());
- unlockIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- mContext.startActivity(unlockIntent);
- } finally {
- Binder.restoreCallingIdentity(identity);
+ final Intent callBackIntent = new Intent(
+ ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK);
+ if (target != null) {
+ callBackIntent.putExtra(Intent.EXTRA_INTENT, target);
}
- return false;
+ callBackIntent.putExtra(Intent.EXTRA_USER_ID, userHandle);
+ callBackIntent.setPackage(mContext.getPackageName());
+ callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ mContext,
+ 0,
+ callBackIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT |
+ PendingIntent.FLAG_ONE_SHOT |
+ PendingIntent.FLAG_IMMUTABLE);
+ // After unlocking the challenge, it will disable quiet mode and run the original
+ // intentSender
+ unlockIntent.putExtra(Intent.EXTRA_INTENT, pendingIntent.getIntentSender());
+ unlockIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ mContext.startActivity(unlockIntent);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
new file mode 100644
index 0000000..35b18b1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore;
+
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN;
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.security.recoverablekeystore.KeyDerivationParameters;
+import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
+import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecoverableKeyStoreManagerTest {
+ private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
+
+ private static final String TEST_SESSION_ID = "karlin";
+ private static final byte[] TEST_PUBLIC_KEY = new byte[] {
+ (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
+ (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06,
+ (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x03,
+ (byte) 0x01, (byte) 0x07, (byte) 0x03, (byte) 0x42, (byte) 0x00, (byte) 0x04, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98, (byte) 0x1d, (byte) 0xf0, (byte) 0x6e,
+ (byte) 0xb4, (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda, (byte) 0x1c, (byte) 0x07,
+ (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a, (byte) 0xf6, (byte) 0x8d, (byte) 0xdc,
+ (byte) 0x61, (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
+ (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0, (byte) 0x3f, (byte) 0xd2,
+ (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d, (byte) 0x91, (byte) 0x55,
+ (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b, (byte) 0x32, (byte) 0x7d,
+ (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc, (byte) 0xa5, (byte) 0x65, (byte) 0xe1,
+ (byte) 0xbd, (byte) 0x21, (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa};
+ private static final byte[] TEST_SALT = getUtf8Bytes("salt");
+ private static final byte[] TEST_SECRET = getUtf8Bytes("password1234");
+ private static final byte[] TEST_VAULT_CHALLENGE = getUtf8Bytes("vault_challenge");
+ private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault_params");
+ private static final int TEST_USER_ID = 10009;
+ private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
+
+ @Mock private Context mMockContext;
+
+ private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+ private File mDatabaseFile;
+ private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
+ private RecoverySessionStorage mRecoverySessionStorage;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ Context context = InstrumentationRegistry.getTargetContext();
+ mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+ mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
+ mRecoverySessionStorage = new RecoverySessionStorage();
+ mRecoverableKeyStoreManager = new RecoverableKeyStoreManager(
+ mMockContext,
+ mRecoverableKeyStoreDb,
+ mRecoverySessionStorage);
+ }
+
+ @After
+ public void tearDown() {
+ mRecoverableKeyStoreDb.close();
+ mDatabaseFile.delete();
+ }
+
+ @Test
+ public void startRecoverySession_checksPermissionFirst() throws Exception {
+ mRecoverableKeyStoreManager.startRecoverySession(
+ TEST_SESSION_ID,
+ TEST_PUBLIC_KEY,
+ TEST_VAULT_PARAMS,
+ TEST_VAULT_CHALLENGE,
+ ImmutableList.of(new KeyStoreRecoveryMetadata(
+ TYPE_LOCKSCREEN,
+ TYPE_PASSWORD,
+ KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
+ TEST_SECRET)),
+ TEST_USER_ID);
+
+ verify(mMockContext, times(1)).enforceCallingOrSelfPermission(
+ eq(RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE),
+ any());
+ }
+
+ @Test
+ public void startRecoverySession_storesTheSessionInfo() throws Exception {
+ mRecoverableKeyStoreManager.startRecoverySession(
+ TEST_SESSION_ID,
+ TEST_PUBLIC_KEY,
+ TEST_VAULT_PARAMS,
+ TEST_VAULT_CHALLENGE,
+ ImmutableList.of(new KeyStoreRecoveryMetadata(
+ TYPE_LOCKSCREEN,
+ TYPE_PASSWORD,
+ KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
+ TEST_SECRET)),
+ TEST_USER_ID);
+
+ assertEquals(1, mRecoverySessionStorage.size());
+ RecoverySessionStorage.Entry entry = mRecoverySessionStorage.get(
+ TEST_USER_ID, TEST_SESSION_ID);
+ assertArrayEquals(TEST_SECRET, entry.getLskfHash());
+ assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length);
+ }
+
+ @Test
+ public void startRecoverySession_throwsIfBadNumberOfSecrets() throws Exception {
+ try {
+ mRecoverableKeyStoreManager.startRecoverySession(
+ TEST_SESSION_ID,
+ TEST_PUBLIC_KEY,
+ TEST_VAULT_PARAMS,
+ TEST_VAULT_CHALLENGE,
+ ImmutableList.of(),
+ TEST_USER_ID);
+ } catch (RemoteException e) {
+ assertEquals("Only a single KeyStoreRecoveryMetadata is supported",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void startRecoverySession_throwsIfBadKey() throws Exception {
+ try {
+ mRecoverableKeyStoreManager.startRecoverySession(
+ TEST_SESSION_ID,
+ getUtf8Bytes("0"),
+ TEST_VAULT_PARAMS,
+ TEST_VAULT_CHALLENGE,
+ ImmutableList.of(new KeyStoreRecoveryMetadata(
+ TYPE_LOCKSCREEN,
+ TYPE_PASSWORD,
+ KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
+ TEST_SECRET)),
+ TEST_USER_ID);
+ } catch (RemoteException e) {
+ assertEquals("Not a valid X509 key",
+ e.getMessage());
+ }
+ }
+
+ private static byte[] getUtf8Bytes(String s) {
+ return s.getBytes(StandardCharsets.UTF_8);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
new file mode 100644
index 0000000..6aeff28
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings.recoverablekeystore.storage;
+
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecoverySessionStorageTest {
+
+ private static final String TEST_SESSION_ID = "peter";
+ private static final int TEST_USER_ID = 696;
+ private static final byte[] TEST_LSKF_HASH = getUtf8Bytes("lskf");
+ private static final byte[] TEST_KEY_CLAIMANT = getUtf8Bytes("0000111122223333");
+
+ @Test
+ public void size_isZeroForEmpty() {
+ assertEquals(0, new RecoverySessionStorage().size());
+ }
+
+ @Test
+ public void size_incrementsAfterAdd() {
+ RecoverySessionStorage storage = new RecoverySessionStorage();
+ storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry(
+ TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()));
+
+ assertEquals(1, storage.size());
+ }
+
+ @Test
+ public void size_decrementsAfterRemove() {
+ RecoverySessionStorage storage = new RecoverySessionStorage();
+ storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry(
+ TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()));
+ storage.remove(TEST_USER_ID);
+
+ assertEquals(0, storage.size());
+ }
+
+ @Test
+ public void remove_overwritesLskfHashMemory() {
+ RecoverySessionStorage storage = new RecoverySessionStorage();
+ RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
+ TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+ storage.add(TEST_USER_ID, entry);
+
+ storage.remove(TEST_USER_ID);
+
+ assertZeroedOut(entry.getLskfHash());
+ }
+
+ @Test
+ public void remove_overwritesKeyClaimantMemory() {
+ RecoverySessionStorage storage = new RecoverySessionStorage();
+ RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
+ TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+ storage.add(TEST_USER_ID, entry);
+
+ storage.remove(TEST_USER_ID);
+
+ assertZeroedOut(entry.getKeyClaimant());
+ }
+
+ @Test
+ public void destroy_overwritesLskfHashMemory() {
+ RecoverySessionStorage storage = new RecoverySessionStorage();
+ RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
+ TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+ storage.add(TEST_USER_ID, entry);
+
+ storage.destroy();
+
+ assertZeroedOut(entry.getLskfHash());
+ }
+
+ @Test
+ public void destroy_overwritesKeyClaimantMemory() {
+ RecoverySessionStorage storage = new RecoverySessionStorage();
+ RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
+ TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+ storage.add(TEST_USER_ID, entry);
+
+ storage.destroy();
+
+ assertZeroedOut(entry.getKeyClaimant());
+ }
+
+ private static void assertZeroedOut(byte[] bytes) {
+ for (byte b : bytes) {
+ if (b != (byte) 0) {
+ fail("Bytes were not all zeroed out.");
+ }
+ }
+ }
+
+ private static byte[] lskfHashFixture() {
+ return Arrays.copyOf(TEST_LSKF_HASH, TEST_LSKF_HASH.length);
+ }
+
+ private static byte[] keyClaimantFixture() {
+ return Arrays.copyOf(TEST_KEY_CLAIMANT, TEST_KEY_CLAIMANT.length);
+ }
+
+ private static byte[] getUtf8Bytes(String s) {
+ return s.getBytes(StandardCharsets.UTF_8);
+ }
+}