Merge "Introduce API to get hibernation stats"
This commit is contained in:
@@ -2356,12 +2356,21 @@ package android.apphibernation {
|
||||
|
||||
public class AppHibernationManager {
|
||||
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.List<java.lang.String> getHibernatingPackagesForUser();
|
||||
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.Map<java.lang.String,android.apphibernation.HibernationStats> getHibernationStatsForUser(@NonNull java.util.Set<java.lang.String>);
|
||||
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.Map<java.lang.String,android.apphibernation.HibernationStats> getHibernationStatsForUser();
|
||||
method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingForUser(@NonNull String);
|
||||
method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingGlobally(@NonNull String);
|
||||
method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void setHibernatingForUser(@NonNull String, boolean);
|
||||
method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void setHibernatingGlobally(@NonNull String, boolean);
|
||||
}
|
||||
|
||||
public final class HibernationStats implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public long getDiskBytesSaved();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.apphibernation.HibernationStats> CREATOR;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.companion {
|
||||
|
||||
@@ -24,7 +24,10 @@ import android.content.Context;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class provides an API surface for system apps to manipulate the app hibernation
|
||||
@@ -129,4 +132,38 @@ public class AppHibernationManager {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stats from app hibernation for each package provided.
|
||||
*
|
||||
* @param packageNames the set of packages to return stats for
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION)
|
||||
public @NonNull Map<String, HibernationStats> getHibernationStatsForUser(
|
||||
@NonNull Set<String> packageNames) {
|
||||
try {
|
||||
return mIAppHibernationService.getHibernationStatsForUser(
|
||||
new ArrayList(packageNames), mContext.getUserId());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stats from app hibernation for all packages for the user
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION)
|
||||
public @NonNull Map<String, HibernationStats> getHibernationStatsForUser() {
|
||||
try {
|
||||
return mIAppHibernationService.getHibernationStatsForUser(
|
||||
null /* packageNames */, mContext.getUserId());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
core/java/android/apphibernation/HibernationStats.aidl
Normal file
19
core/java/android/apphibernation/HibernationStats.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2022, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.apphibernation;
|
||||
|
||||
parcelable HibernationStats;
|
||||
70
core/java/android/apphibernation/HibernationStats.java
Normal file
70
core/java/android/apphibernation/HibernationStats.java
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.apphibernation;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Stats for a hibernating package.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class HibernationStats implements Parcelable {
|
||||
private final long mDiskBytesSaved;
|
||||
|
||||
/** @hide */
|
||||
public HibernationStats(long diskBytesSaved) {
|
||||
mDiskBytesSaved = diskBytesSaved;
|
||||
}
|
||||
|
||||
private HibernationStats(@NonNull Parcel in) {
|
||||
mDiskBytesSaved = in.readLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the disk storage saved from hibernation in bytes.
|
||||
*/
|
||||
public long getDiskBytesSaved() {
|
||||
return mDiskBytesSaved;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeLong(mDiskBytesSaved);
|
||||
}
|
||||
|
||||
public static final @NonNull Creator<HibernationStats> CREATOR =
|
||||
new Creator<HibernationStats>() {
|
||||
@Override
|
||||
public HibernationStats createFromParcel(Parcel in) {
|
||||
return new HibernationStats(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HibernationStats[] newArray(int size) {
|
||||
return new HibernationStats[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package android.apphibernation;
|
||||
|
||||
import android.apphibernation.HibernationStats;
|
||||
|
||||
/**
|
||||
* Binder interface to communicate with AppHibernationService.
|
||||
* @hide
|
||||
@@ -26,4 +28,6 @@ interface IAppHibernationService {
|
||||
boolean isHibernatingGlobally(String packageName);
|
||||
void setHibernatingGlobally(String packageName, boolean isHibernating);
|
||||
List<String> getHibernatingPackagesForUser(int userId);
|
||||
Map<String, HibernationStats> getHibernationStatsForUser(in List<String> packageNames,
|
||||
int userId);
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import android.app.StatsManager.StatsPullAtomCallback;
|
||||
import android.app.usage.UsageEvents;
|
||||
import android.app.usage.UsageStatsManagerInternal;
|
||||
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
|
||||
import android.apphibernation.HibernationStats;
|
||||
import android.apphibernation.IAppHibernationService;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
@@ -221,7 +222,7 @@ public final class AppHibernationService extends SystemService {
|
||||
}
|
||||
getContext().enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.MANAGE_APP_HIBERNATION,
|
||||
"Caller does not have MANAGE_APP_HIBERNATION permission.");
|
||||
"Caller did not have permission while calling " + methodName);
|
||||
userId = handleIncomingUser(userId, methodName);
|
||||
synchronized (mLock) {
|
||||
if (!checkUserStatesExist(userId, methodName)) {
|
||||
@@ -379,6 +380,46 @@ public final class AppHibernationService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the stats from app hibernation for each package provided.
|
||||
*
|
||||
* @param packageNames the set of packages to return stats for. Returns all if null
|
||||
* @return map from package to stats for that package
|
||||
*/
|
||||
public Map<String, HibernationStats> getHibernationStatsForUser(
|
||||
@Nullable Set<String> packageNames, int userId) {
|
||||
Map<String, HibernationStats> statsMap = new ArrayMap<>();
|
||||
String methodName = "getHibernationStatsForUser";
|
||||
getContext().enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.MANAGE_APP_HIBERNATION,
|
||||
"Caller does not have MANAGE_APP_HIBERNATION permission.");
|
||||
userId = handleIncomingUser(userId, methodName);
|
||||
synchronized (mLock) {
|
||||
if (!checkUserStatesExist(userId, methodName)) {
|
||||
return statsMap;
|
||||
}
|
||||
final Map<String, UserLevelState> userPackageStates = mUserStates.get(userId);
|
||||
Set<String> pkgs = packageNames != null ? packageNames : userPackageStates.keySet();
|
||||
for (String pkgName : pkgs) {
|
||||
if (!mPackageManagerInternal.canQueryPackage(Binder.getCallingUid(), pkgName)) {
|
||||
// Package not visible to caller
|
||||
continue;
|
||||
}
|
||||
if (!mGlobalHibernationStates.containsKey(pkgName)
|
||||
|| !userPackageStates.containsKey(pkgName)) {
|
||||
Slog.w(TAG, String.format(
|
||||
"No hibernation state associated with package %s user %d. Maybe"
|
||||
+ "the package was uninstalled? ", pkgName, userId));
|
||||
continue;
|
||||
}
|
||||
HibernationStats stats = new HibernationStats(
|
||||
mGlobalHibernationStates.get(pkgName).savedByte);
|
||||
statsMap.put(pkgName, stats);
|
||||
}
|
||||
}
|
||||
return statsMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put an app into hibernation for a given user, allowing user-level optimizations to occur. Do
|
||||
* not hold {@link #mLock} while calling this to avoid deadlock scenarios.
|
||||
@@ -787,6 +828,13 @@ public final class AppHibernationService extends SystemService {
|
||||
return mService.getHibernatingPackagesForUser(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, HibernationStats> getHibernationStatsForUser(
|
||||
@Nullable List<String> packageNames, int userId) {
|
||||
Set<String> pkgsSet = packageNames != null ? new ArraySet<>(packageNames) : null;
|
||||
return mService.getHibernationStatsForUser(pkgsSet, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
|
||||
@Nullable FileDescriptor err, @NonNull String[] args,
|
||||
|
||||
@@ -23,6 +23,7 @@ import static android.content.pm.PackageManager.MATCH_ANY_USER;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.AdditionalAnswers.returnsArgAt;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -40,6 +41,7 @@ import android.app.IActivityManager;
|
||||
import android.app.usage.UsageEvents.Event;
|
||||
import android.app.usage.UsageStatsManagerInternal;
|
||||
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
|
||||
import android.apphibernation.HibernationStats;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -68,6 +70,8 @@ import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
@@ -126,7 +130,7 @@ public final class AppHibernationServiceTest {
|
||||
mUsageEventListener = mUsageEventListenerCaptor.getValue();
|
||||
|
||||
doReturn(mUserInfos).when(mUserManager).getUsers();
|
||||
|
||||
doReturn(true).when(mPackageManagerInternal).canQueryPackage(anyInt(), any());
|
||||
doAnswer(returnsArgAt(2)).when(mIActivityManager).handleIncomingUser(anyInt(), anyInt(),
|
||||
anyInt(), anyBoolean(), anyBoolean(), any(), any());
|
||||
|
||||
@@ -376,6 +380,58 @@ public final class AppHibernationServiceTest {
|
||||
assertFalse(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHibernationStatsForUser_getsStatsForPackage() {
|
||||
// GIVEN a package is hibernating globally and for a user
|
||||
mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true);
|
||||
mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true);
|
||||
|
||||
// WHEN we ask for the hibernation stats for the package
|
||||
Map<String, HibernationStats> stats =
|
||||
mAppHibernationService.getHibernationStatsForUser(
|
||||
Set.of(PACKAGE_NAME_1), USER_ID_1);
|
||||
|
||||
// THEN the stats exist for the package
|
||||
assertTrue(stats.containsKey(PACKAGE_NAME_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHibernationStatsForUser_noExceptionThrownWhenPackageDoesntExist() {
|
||||
// WHEN we ask for the hibernation stats for a package that doesn't exist
|
||||
Map<String, HibernationStats> stats =
|
||||
mAppHibernationService.getHibernationStatsForUser(
|
||||
Set.of(PACKAGE_NAME_1), USER_ID_1);
|
||||
|
||||
// THEN no exception is thrown and empty stats are returned
|
||||
assertNotNull(stats);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHibernationStatsForUser_returnsAllIfNoPackagesSpecified()
|
||||
throws RemoteException {
|
||||
// GIVEN an unlocked user with all packages installed and they're all hibernating
|
||||
UserInfo userInfo =
|
||||
addUser(USER_ID_2, new String[]{PACKAGE_NAME_1, PACKAGE_NAME_2, PACKAGE_NAME_3});
|
||||
doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2);
|
||||
mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
|
||||
mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true);
|
||||
mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true);
|
||||
mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_2, true);
|
||||
mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_2, USER_ID_2, true);
|
||||
mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_3, true);
|
||||
mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_3, USER_ID_2, true);
|
||||
|
||||
// WHEN we ask for the hibernation stats with no package specified
|
||||
Map<String, HibernationStats> stats =
|
||||
mAppHibernationService.getHibernationStatsForUser(
|
||||
null /* packageNames */, USER_ID_2);
|
||||
|
||||
// THEN all the package stats are returned
|
||||
assertTrue(stats.containsKey(PACKAGE_NAME_1));
|
||||
assertTrue(stats.containsKey(PACKAGE_NAME_2));
|
||||
assertTrue(stats.containsKey(PACKAGE_NAME_3));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock a usage event occurring.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user