Merge "Introduce API to get hibernation stats"

This commit is contained in:
Kevin Han
2022-01-28 02:24:30 +00:00
committed by Android (Google) Code Review
7 changed files with 245 additions and 2 deletions

View File

@@ -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 {

View File

@@ -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();
}
}
}

View 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;

View 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];
}
};
}

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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.
*