Add SystemApi to change app launch time estimates.

An SystemApis that allow a prediction app to update app launch time
estimates.

Bug: 194532703
Test: atest CtsPermissionTestCases:AppIdleStatePermissionTest
Test: atest CtsPermission2TestCases
Change-Id: I0fde220fa038f7f1f4d543a8906e92ca625bf097
This commit is contained in:
Kweku Adams
2021-11-05 11:03:46 -07:00
parent ef6564cbfb
commit e566f33a82
6 changed files with 227 additions and 0 deletions

View File

@@ -83,6 +83,7 @@ package android {
field public static final String CAPTURE_TV_INPUT = "android.permission.CAPTURE_TV_INPUT";
field public static final String CAPTURE_VOICE_COMMUNICATION_OUTPUT = "android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT";
field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
field public static final String CHANGE_APP_LAUNCH_TIME_ESTIMATE = "android.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE";
field public static final String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST";
field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
field public static final String COMPANION_APPROVE_WIFI_CONNECTIONS = "android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS";
@@ -1923,6 +1924,8 @@ package android.app.usage {
method public void reportUsageStop(@NonNull android.app.Activity, @NonNull String);
method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBucket(String, int);
method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBuckets(java.util.Map<java.lang.String,java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE) public void setEstimatedLaunchTime(@NonNull String, long);
method @RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE) public void setEstimatedLaunchTimes(@NonNull java.util.Map<java.lang.String,java.lang.Long>);
method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void unregisterAppUsageLimitObserver(int);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterAppUsageObserver(int);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterUsageSessionObserver(int);

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2021 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.app.usage;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
/**
* A pair of {package, estimated launch time} to denote the estimated launch time for a given
* package.
* Used as a vehicle of data across the binder IPC.
*
* @hide
*/
public final class AppLaunchEstimateInfo implements Parcelable {
public final String packageName;
@CurrentTimeMillisLong
public final long estimatedLaunchTime;
private AppLaunchEstimateInfo(Parcel in) {
packageName = in.readString();
estimatedLaunchTime = in.readLong();
}
public AppLaunchEstimateInfo(String packageName,
@CurrentTimeMillisLong long estimatedLaunchTime) {
this.packageName = packageName;
this.estimatedLaunchTime = estimatedLaunchTime;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(packageName);
dest.writeLong(estimatedLaunchTime);
}
@NonNull
public static final Creator<AppLaunchEstimateInfo> CREATOR =
new Creator<AppLaunchEstimateInfo>() {
@Override
public AppLaunchEstimateInfo createFromParcel(Parcel source) {
return new AppLaunchEstimateInfo(source);
}
@Override
public AppLaunchEstimateInfo[] newArray(int size) {
return new AppLaunchEstimateInfo[size];
}
};
}

View File

@@ -51,6 +51,8 @@ interface IUsageStatsManager {
void setAppStandbyBucket(String packageName, int bucket, int userId);
ParceledListSlice getAppStandbyBuckets(String callingPackage, int userId);
void setAppStandbyBuckets(in ParceledListSlice appBuckets, int userId);
void setEstimatedLaunchTime(String packageName, long estimatedLaunchTime, int userId);
void setEstimatedLaunchTimes(in ParceledListSlice appLaunchTimes, int userId);
void registerAppUsageObserver(int observerId, in String[] packages, long timeLimitMs,
in PendingIntent callback, String callingPackage);
void unregisterAppUsageObserver(int observerId, String callingPackage);

View File

@@ -16,6 +16,7 @@
package android.app.usage;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -771,6 +772,72 @@ public final class UsageStatsManager {
}
}
/**
* Changes an app's estimated launch time. An app is considered "launched" when a user opens
* one of its {@link android.app.Activity Activities}. The provided time is persisted across
* reboots and is used unless 1) the time is more than a week in the future and the platform
* thinks the app will be launched sooner, 2) the estimated time has passed. Passing in
* {@link Long#MAX_VALUE} effectively clears the previously set launch time for the app.
*
* @param packageName The package name of the app to set the bucket for.
* @param estimatedLaunchTime The next time the app is expected to be launched. Units are in
* milliseconds since epoch (the same as
* {@link System#currentTimeMillis()}).
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE)
public void setEstimatedLaunchTime(@NonNull String packageName,
@CurrentTimeMillisLong long estimatedLaunchTime) {
if (packageName == null) {
throw new NullPointerException("package name cannot be null");
}
if (estimatedLaunchTime <= 0) {
throw new IllegalArgumentException("estimated launch time must be positive");
}
try {
mService.setEstimatedLaunchTime(packageName, estimatedLaunchTime, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Changes the estimated launch times for multiple apps at once. The map is keyed by the
* package name and the value is the estimated launch time.
*
* @param estimatedLaunchTimes A map of package name to estimated launch time.
* @see #setEstimatedLaunchTime(String, long)
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE)
public void setEstimatedLaunchTimes(@NonNull Map<String, Long> estimatedLaunchTimes) {
if (estimatedLaunchTimes == null) {
throw new NullPointerException("estimatedLaunchTimes cannot be null");
}
final List<AppLaunchEstimateInfo> estimateList =
new ArrayList<>(estimatedLaunchTimes.size());
for (Map.Entry<String, Long> estimateEntry : estimatedLaunchTimes.entrySet()) {
final String pkgName = estimateEntry.getKey();
if (pkgName == null) {
throw new NullPointerException("package name cannot be null");
}
final Long estimatedLaunchTime = estimateEntry.getValue();
if (estimatedLaunchTime == null || estimatedLaunchTime <= 0) {
throw new IllegalArgumentException("estimated launch time must be positive");
}
estimateList.add(new AppLaunchEstimateInfo(pkgName, estimatedLaunchTime));
}
final ParceledListSlice<AppLaunchEstimateInfo> slice =
new ParceledListSlice<>(estimateList);
try {
mService.setEstimatedLaunchTimes(slice, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
* Register an app usage limit observer that receives a callback on the provided intent when

View File

@@ -4880,6 +4880,11 @@
<permission android:name="android.permission.CHANGE_APP_IDLE_STATE"
android:protectionLevel="signature|privileged" />
<!-- @hide @SystemApi Allows an application to change the estimated launch time of an app.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE"
android:protectionLevel="signature|privileged" />
<!-- @hide @SystemApi Allows an application to temporarily allowlist an inactive app to
access the network and acquire wakelocks.
<p>Not for use by third-party applications. -->

View File

@@ -41,6 +41,7 @@ import android.app.AppOpsManager;
import android.app.IUidObserver;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.usage.AppLaunchEstimateInfo;
import android.app.usage.AppStandbyInfo;
import android.app.usage.ConfigurationStats;
import android.app.usage.EventStats;
@@ -1554,6 +1555,52 @@ public class UsageStatsService extends SystemService implements
}
}
private void setEstimatedLaunchTime(int userId, String packageName,
@CurrentTimeMillisLong long estimatedLaunchTime) {
final long now = System.currentTimeMillis();
if (estimatedLaunchTime <= now) {
if (DEBUG) {
Slog.w(TAG, "Ignoring new estimate for "
+ userId + ":" + packageName + " because it's old");
}
return;
}
final long oldEstimatedLaunchTime = mAppStandby.getEstimatedLaunchTime(packageName, userId);
if (estimatedLaunchTime != oldEstimatedLaunchTime) {
mAppStandby.setEstimatedLaunchTime(packageName, userId, estimatedLaunchTime);
mHandler.obtainMessage(
MSG_NOTIFY_ESTIMATED_LAUNCH_TIME_CHANGED, userId, 0, packageName)
.sendToTarget();
}
}
private void setEstimatedLaunchTimes(int userId, List<AppLaunchEstimateInfo> launchEstimates) {
final ArraySet<String> changedTimes = new ArraySet<>();
final long now = System.currentTimeMillis();
for (int i = launchEstimates.size() - 1; i >= 0; --i) {
AppLaunchEstimateInfo estimate = launchEstimates.get(i);
if (estimate.estimatedLaunchTime <= now) {
if (DEBUG) {
Slog.w(TAG, "Ignoring new estimate for "
+ userId + ":" + estimate.packageName + " because it's old");
}
continue;
}
final long oldEstimatedLaunchTime =
mAppStandby.getEstimatedLaunchTime(estimate.packageName, userId);
if (estimate.estimatedLaunchTime != oldEstimatedLaunchTime) {
mAppStandby.setEstimatedLaunchTime(
estimate.packageName, userId, estimate.estimatedLaunchTime);
changedTimes.add(estimate.packageName);
}
}
if (changedTimes.size() > 0) {
mHandler.obtainMessage(
MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED, userId, 0, changedTimes)
.sendToTarget();
}
}
/**
* Called via the local interface.
*/
@@ -2292,6 +2339,37 @@ public class UsageStatsService extends SystemService implements
}
}
@Override
public void setEstimatedLaunchTime(String packageName, long estimatedLaunchTime,
int userId) {
getContext().enforceCallingPermission(
Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE,
"No permission to change app launch estimates");
final long token = Binder.clearCallingIdentity();
try {
UsageStatsService.this
.setEstimatedLaunchTime(userId, packageName, estimatedLaunchTime);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void setEstimatedLaunchTimes(ParceledListSlice estimatedLaunchTimes, int userId) {
getContext().enforceCallingPermission(
Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE,
"No permission to change app launch estimates");
final long token = Binder.clearCallingIdentity();
try {
UsageStatsService.this
.setEstimatedLaunchTimes(userId, estimatedLaunchTimes.getList());
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void onCarrierPrivilegedAppsChanged() {
if (DEBUG) {