Merge "Install constraints API"
This commit is contained in:
committed by
Android (Google) Code Review
commit
28b621620b
@@ -11663,6 +11663,7 @@ package android.content.pm {
|
||||
|
||||
public class PackageInstaller {
|
||||
method public void abandonSession(int);
|
||||
method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>);
|
||||
method public int createSession(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
|
||||
method @Deprecated @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession();
|
||||
method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getActiveStagedSessions();
|
||||
@@ -11707,6 +11708,35 @@ package android.content.pm {
|
||||
field public static final int STATUS_SUCCESS = 0; // 0x0
|
||||
}
|
||||
|
||||
public static final class PackageInstaller.InstallConstraints implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public boolean isRequireAppNotForeground();
|
||||
method public boolean isRequireAppNotInteracting();
|
||||
method public boolean isRequireAppNotTopVisible();
|
||||
method public boolean isRequireDeviceIdle();
|
||||
method public boolean isRequireNotInCall();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraints> CREATOR;
|
||||
field @NonNull public static final android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE;
|
||||
}
|
||||
|
||||
public static final class PackageInstaller.InstallConstraints.Builder {
|
||||
ctor public PackageInstaller.InstallConstraints.Builder();
|
||||
method @NonNull public android.content.pm.PackageInstaller.InstallConstraints build();
|
||||
method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground();
|
||||
method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting();
|
||||
method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible();
|
||||
method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle();
|
||||
method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall();
|
||||
}
|
||||
|
||||
public static final class PackageInstaller.InstallConstraintsResult implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public boolean isAllConstraintsSatisfied();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraintsResult> CREATOR;
|
||||
}
|
||||
|
||||
public static final class PackageInstaller.PreapprovalDetails implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method @Nullable public android.graphics.Bitmap getIcon();
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.content.pm.VersionedPackage;
|
||||
import android.content.IntentSender;
|
||||
import android.os.RemoteCallback;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
@@ -66,4 +67,6 @@ interface IPackageInstaller {
|
||||
|
||||
void setAllowUnlimitedSilentUpdates(String installerPackageName);
|
||||
void setSilentUpdatesThrottleTime(long throttleTimeInSeconds);
|
||||
void checkInstallConstraints(String installerPackageName, in List<String> packageNames,
|
||||
in PackageInstaller.InstallConstraints constraints, in RemoteCallback callback);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.content.pm;
|
||||
|
||||
parcelable PackageInstaller.InstallConstraints;
|
||||
parcelable PackageInstaller.SessionParams;
|
||||
parcelable PackageInstaller.SessionInfo;
|
||||
parcelable PackageInstaller.PreapprovalDetails;
|
||||
|
||||
@@ -36,6 +36,7 @@ import android.annotation.Nullable;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SdkConstant;
|
||||
import android.annotation.SdkConstant.SdkConstantType;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.TestApi;
|
||||
import android.app.ActivityManager;
|
||||
@@ -57,6 +58,7 @@ import android.os.Parcel;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Parcelable;
|
||||
import android.os.ParcelableException;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
@@ -89,6 +91,7 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Offers the ability to install, upgrade, and remove applications on the
|
||||
@@ -853,6 +856,29 @@ public class PackageInstaller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if install constraints are satisfied for the given packages.
|
||||
*
|
||||
* Note this query result is just a hint and subject to race because system states could
|
||||
* change anytime in-between this query and committing the session.
|
||||
*
|
||||
* The result is returned by a callback because some constraints might take a long time
|
||||
* to evaluate.
|
||||
*/
|
||||
public void checkInstallConstraints(@NonNull List<String> packageNames,
|
||||
@NonNull InstallConstraints constraints,
|
||||
@NonNull Consumer<InstallConstraintsResult> callback) {
|
||||
try {
|
||||
var remoteCallback = new RemoteCallback(b -> {
|
||||
callback.accept(b.getParcelable("result", InstallConstraintsResult.class));
|
||||
});
|
||||
mInstaller.checkInstallConstraints(
|
||||
mInstallerPackageName, packageNames, constraints, remoteCallback);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Events for observing session lifecycle.
|
||||
* <p>
|
||||
@@ -3647,4 +3673,362 @@ public class PackageInstaller {
|
||||
// End of generated code
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}.
|
||||
*/
|
||||
@DataClass(genParcelable = true, genHiddenConstructor = true)
|
||||
public static final class InstallConstraintsResult implements Parcelable {
|
||||
/**
|
||||
* True if all constraints are satisfied.
|
||||
*/
|
||||
private boolean mAllConstraintsSatisfied;
|
||||
|
||||
|
||||
|
||||
// Code below generated by codegen v1.0.23.
|
||||
//
|
||||
// DO NOT MODIFY!
|
||||
// CHECKSTYLE:OFF Generated code
|
||||
//
|
||||
// To regenerate run:
|
||||
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java
|
||||
//
|
||||
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
|
||||
// Settings > Editor > Code Style > Formatter Control
|
||||
//@formatter:off
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new InstallConstraintsResult.
|
||||
*
|
||||
* @param allConstraintsSatisfied
|
||||
* True if all constraints are satisfied.
|
||||
* @hide
|
||||
*/
|
||||
@DataClass.Generated.Member
|
||||
public InstallConstraintsResult(
|
||||
boolean allConstraintsSatisfied) {
|
||||
this.mAllConstraintsSatisfied = allConstraintsSatisfied;
|
||||
|
||||
// onConstructed(); // You can define this method to get a callback
|
||||
}
|
||||
|
||||
/**
|
||||
* True if all constraints are satisfied.
|
||||
*/
|
||||
@DataClass.Generated.Member
|
||||
public boolean isAllConstraintsSatisfied() {
|
||||
return mAllConstraintsSatisfied;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataClass.Generated.Member
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
// You can override field parcelling by defining methods like:
|
||||
// void parcelFieldName(Parcel dest, int flags) { ... }
|
||||
|
||||
byte flg = 0;
|
||||
if (mAllConstraintsSatisfied) flg |= 0x1;
|
||||
dest.writeByte(flg);
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataClass.Generated.Member
|
||||
public int describeContents() { return 0; }
|
||||
|
||||
/** @hide */
|
||||
@SuppressWarnings({"unchecked", "RedundantCast"})
|
||||
@DataClass.Generated.Member
|
||||
/* package-private */ InstallConstraintsResult(@NonNull Parcel in) {
|
||||
// You can override field unparcelling by defining methods like:
|
||||
// static FieldType unparcelFieldName(Parcel in) { ... }
|
||||
|
||||
byte flg = in.readByte();
|
||||
boolean allConstraintsSatisfied = (flg & 0x1) != 0;
|
||||
|
||||
this.mAllConstraintsSatisfied = allConstraintsSatisfied;
|
||||
|
||||
// onConstructed(); // You can define this method to get a callback
|
||||
}
|
||||
|
||||
@DataClass.Generated.Member
|
||||
public static final @NonNull Parcelable.Creator<InstallConstraintsResult> CREATOR
|
||||
= new Parcelable.Creator<InstallConstraintsResult>() {
|
||||
@Override
|
||||
public InstallConstraintsResult[] newArray(int size) {
|
||||
return new InstallConstraintsResult[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallConstraintsResult createFromParcel(@NonNull Parcel in) {
|
||||
return new InstallConstraintsResult(in);
|
||||
}
|
||||
};
|
||||
|
||||
@DataClass.Generated(
|
||||
time = 1668650523745L,
|
||||
codegenVersion = "1.0.23",
|
||||
sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
|
||||
inputSignatures = "private boolean mAllConstraintsSatisfied\nclass InstallConstraintsResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
|
||||
@Deprecated
|
||||
private void __metadata() {}
|
||||
|
||||
|
||||
//@formatter:on
|
||||
// End of generated code
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to encapsulate constraints for installation.
|
||||
*
|
||||
* When used with {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}, it
|
||||
* specifies the conditions to check against for the packages in question. This can be used
|
||||
* by app stores to deliver auto updates without disrupting the user experience (referred as
|
||||
* gentle update) - for example, an app store might hold off updates when it find out the
|
||||
* app to update is interacting with the user.
|
||||
*
|
||||
* Use {@link Builder} to create a new instance and call mutator methods to add constraints.
|
||||
* If no mutators were called, default constraints will be generated which implies no
|
||||
* constraints. It is recommended to use preset constraints which are useful in most
|
||||
* cases.
|
||||
*
|
||||
* For the purpose of gentle update, it is recommended to always use {@link #GENTLE_UPDATE}
|
||||
* for the system knows best how to do it. It will also benefits the installer as the
|
||||
* platform evolves and add more constraints to improve the accuracy and efficiency of
|
||||
* gentle update.
|
||||
*
|
||||
* Note the constraints are applied transitively. If app Foo is used by app Bar (via shared
|
||||
* library or bounded service), the constraints will also be applied to Bar.
|
||||
*/
|
||||
@DataClass(genParcelable = true, genHiddenConstructor = true)
|
||||
public static final class InstallConstraints implements Parcelable {
|
||||
/**
|
||||
* Preset constraints suitable for gentle update.
|
||||
*/
|
||||
@NonNull
|
||||
public static final InstallConstraints GENTLE_UPDATE =
|
||||
new Builder().requireAppNotInteracting().build();
|
||||
|
||||
private final boolean mRequireDeviceIdle;
|
||||
private final boolean mRequireAppNotForeground;
|
||||
private final boolean mRequireAppNotInteracting;
|
||||
private final boolean mRequireAppNotTopVisible;
|
||||
private final boolean mRequireNotInCall;
|
||||
|
||||
/**
|
||||
* Builder class for constructing {@link InstallConstraints}.
|
||||
*/
|
||||
public static final class Builder {
|
||||
private boolean mRequireDeviceIdle;
|
||||
private boolean mRequireAppNotForeground;
|
||||
private boolean mRequireAppNotInteracting;
|
||||
private boolean mRequireAppNotTopVisible;
|
||||
private boolean mRequireNotInCall;
|
||||
|
||||
/**
|
||||
* This constraint requires the device is idle.
|
||||
*/
|
||||
@SuppressLint("BuilderSetStyle")
|
||||
@NonNull
|
||||
public Builder requireDeviceIdle() {
|
||||
mRequireDeviceIdle = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This constraint requires the app in question is not in the foreground.
|
||||
*/
|
||||
@SuppressLint("BuilderSetStyle")
|
||||
@NonNull
|
||||
public Builder requireAppNotForeground() {
|
||||
mRequireAppNotForeground = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This constraint requires the app in question is not interacting with the user.
|
||||
* User interaction includes:
|
||||
* <ul>
|
||||
* <li>playing or recording audio/video</li>
|
||||
* <li>sending or receiving network data</li>
|
||||
* <li>being visible to the user</li>
|
||||
* </ul>
|
||||
*/
|
||||
@SuppressLint("BuilderSetStyle")
|
||||
@NonNull
|
||||
public Builder requireAppNotInteracting() {
|
||||
mRequireAppNotInteracting = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This constraint requires the app in question is not top-visible to the user.
|
||||
* A top-visible app is showing UI at the top of the screen that the user is
|
||||
* interacting with.
|
||||
*
|
||||
* Note this constraint is a subset of {@link #requireAppNotForeground()}
|
||||
* because a top-visible app is also a foreground app. This is also a subset
|
||||
* of {@link #requireAppNotInteracting()} because a top-visible app is interacting
|
||||
* with the user.
|
||||
*/
|
||||
@SuppressLint("BuilderSetStyle")
|
||||
@NonNull
|
||||
public Builder requireAppNotTopVisible() {
|
||||
mRequireAppNotTopVisible = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This constraint requires there is no ongoing call in the device.
|
||||
*/
|
||||
@SuppressLint("BuilderSetStyle")
|
||||
@NonNull
|
||||
public Builder requireNotInCall() {
|
||||
mRequireNotInCall = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link InstallConstraints} instance.
|
||||
*/
|
||||
@NonNull
|
||||
public InstallConstraints build() {
|
||||
return new InstallConstraints(mRequireDeviceIdle, mRequireAppNotForeground,
|
||||
mRequireAppNotInteracting, mRequireAppNotTopVisible, mRequireNotInCall);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Code below generated by codegen v1.0.23.
|
||||
//
|
||||
// DO NOT MODIFY!
|
||||
// CHECKSTYLE:OFF Generated code
|
||||
//
|
||||
// To regenerate run:
|
||||
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java
|
||||
//
|
||||
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
|
||||
// Settings > Editor > Code Style > Formatter Control
|
||||
//@formatter:off
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new InstallConstraints.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@DataClass.Generated.Member
|
||||
public InstallConstraints(
|
||||
boolean requireDeviceIdle,
|
||||
boolean requireAppNotForeground,
|
||||
boolean requireAppNotInteracting,
|
||||
boolean requireAppNotTopVisible,
|
||||
boolean requireNotInCall) {
|
||||
this.mRequireDeviceIdle = requireDeviceIdle;
|
||||
this.mRequireAppNotForeground = requireAppNotForeground;
|
||||
this.mRequireAppNotInteracting = requireAppNotInteracting;
|
||||
this.mRequireAppNotTopVisible = requireAppNotTopVisible;
|
||||
this.mRequireNotInCall = requireNotInCall;
|
||||
|
||||
// onConstructed(); // You can define this method to get a callback
|
||||
}
|
||||
|
||||
@DataClass.Generated.Member
|
||||
public boolean isRequireDeviceIdle() {
|
||||
return mRequireDeviceIdle;
|
||||
}
|
||||
|
||||
@DataClass.Generated.Member
|
||||
public boolean isRequireAppNotForeground() {
|
||||
return mRequireAppNotForeground;
|
||||
}
|
||||
|
||||
@DataClass.Generated.Member
|
||||
public boolean isRequireAppNotInteracting() {
|
||||
return mRequireAppNotInteracting;
|
||||
}
|
||||
|
||||
@DataClass.Generated.Member
|
||||
public boolean isRequireAppNotTopVisible() {
|
||||
return mRequireAppNotTopVisible;
|
||||
}
|
||||
|
||||
@DataClass.Generated.Member
|
||||
public boolean isRequireNotInCall() {
|
||||
return mRequireNotInCall;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataClass.Generated.Member
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
// You can override field parcelling by defining methods like:
|
||||
// void parcelFieldName(Parcel dest, int flags) { ... }
|
||||
|
||||
byte flg = 0;
|
||||
if (mRequireDeviceIdle) flg |= 0x1;
|
||||
if (mRequireAppNotForeground) flg |= 0x2;
|
||||
if (mRequireAppNotInteracting) flg |= 0x4;
|
||||
if (mRequireAppNotTopVisible) flg |= 0x8;
|
||||
if (mRequireNotInCall) flg |= 0x10;
|
||||
dest.writeByte(flg);
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataClass.Generated.Member
|
||||
public int describeContents() { return 0; }
|
||||
|
||||
/** @hide */
|
||||
@SuppressWarnings({"unchecked", "RedundantCast"})
|
||||
@DataClass.Generated.Member
|
||||
/* package-private */ InstallConstraints(@NonNull Parcel in) {
|
||||
// You can override field unparcelling by defining methods like:
|
||||
// static FieldType unparcelFieldName(Parcel in) { ... }
|
||||
|
||||
byte flg = in.readByte();
|
||||
boolean requireDeviceIdle = (flg & 0x1) != 0;
|
||||
boolean requireAppNotForeground = (flg & 0x2) != 0;
|
||||
boolean requireAppNotInteracting = (flg & 0x4) != 0;
|
||||
boolean requireAppNotTopVisible = (flg & 0x8) != 0;
|
||||
boolean requireNotInCall = (flg & 0x10) != 0;
|
||||
|
||||
this.mRequireDeviceIdle = requireDeviceIdle;
|
||||
this.mRequireAppNotForeground = requireAppNotForeground;
|
||||
this.mRequireAppNotInteracting = requireAppNotInteracting;
|
||||
this.mRequireAppNotTopVisible = requireAppNotTopVisible;
|
||||
this.mRequireNotInCall = requireNotInCall;
|
||||
|
||||
// onConstructed(); // You can define this method to get a callback
|
||||
}
|
||||
|
||||
@DataClass.Generated.Member
|
||||
public static final @NonNull Parcelable.Creator<InstallConstraints> CREATOR
|
||||
= new Parcelable.Creator<InstallConstraints>() {
|
||||
@Override
|
||||
public InstallConstraints[] newArray(int size) {
|
||||
return new InstallConstraints[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallConstraints createFromParcel(@NonNull Parcel in) {
|
||||
return new InstallConstraints(in);
|
||||
}
|
||||
};
|
||||
|
||||
@DataClass.Generated(
|
||||
time = 1668650523752L,
|
||||
codegenVersion = "1.0.23",
|
||||
sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
|
||||
inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final boolean mRequireDeviceIdle\nprivate final boolean mRequireAppNotForeground\nprivate final boolean mRequireAppNotInteracting\nprivate final boolean mRequireAppNotTopVisible\nprivate final boolean mRequireNotInCall\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mRequireDeviceIdle\nprivate boolean mRequireAppNotForeground\nprivate boolean mRequireAppNotInteracting\nprivate boolean mRequireAppNotTopVisible\nprivate boolean mRequireNotInCall\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
|
||||
@Deprecated
|
||||
private void __metadata() {}
|
||||
|
||||
|
||||
//@formatter:on
|
||||
// End of generated code
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7291,6 +7291,10 @@
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" >
|
||||
</service>
|
||||
|
||||
<service android:name="com.android.server.pm.GentleUpdateHelper$Service"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" >
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name="com.android.server.autofill.AutofillCompatAccessibilityService"
|
||||
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
|
||||
|
||||
172
services/core/java/com/android/server/pm/AppStateHelper.java
Normal file
172
services/core/java/com/android/server/pm/AppStateHelper.java
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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 com.android.server.pm;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManager.RunningAppProcessInfo;
|
||||
import android.content.Context;
|
||||
import android.media.IAudioService;
|
||||
import android.os.ServiceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A helper class to provide queries for app states concerning gentle-update.
|
||||
*/
|
||||
public class AppStateHelper {
|
||||
private final Context mContext;
|
||||
|
||||
public AppStateHelper(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the package is loaded into the process.
|
||||
*/
|
||||
private static boolean isPackageLoaded(RunningAppProcessInfo info, String packageName) {
|
||||
return ArrayUtils.contains(info.pkgList, packageName)
|
||||
|| ArrayUtils.contains(info.pkgDeps, packageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the importance of the given package.
|
||||
*/
|
||||
private int getImportance(String packageName) {
|
||||
var am = mContext.getSystemService(ActivityManager.class);
|
||||
return am.getPackageImportance(packageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the app owns the audio focus.
|
||||
*/
|
||||
private boolean hasAudioFocus(String packageName) {
|
||||
var audioService = IAudioService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.AUDIO_SERVICE));
|
||||
try {
|
||||
var focusInfos = audioService.getFocusStack();
|
||||
int size = focusInfos.size();
|
||||
var audioFocusPackage = (size > 0) ? focusInfos.get(size - 1).getPackageName() : null;
|
||||
return TextUtils.equals(packageName, audioFocusPackage);
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the app is in the foreground.
|
||||
*/
|
||||
private boolean isAppForeground(String packageName) {
|
||||
return getImportance(packageName) <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the app is currently at the top of the screen that the user is interacting with.
|
||||
*/
|
||||
public boolean isAppTopVisible(String packageName) {
|
||||
return getImportance(packageName) <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the app is playing/recording audio.
|
||||
*/
|
||||
private boolean hasActiveAudio(String packageName) {
|
||||
// TODO(b/235306967): also check recording
|
||||
return hasAudioFocus(packageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the app is sending or receiving network data.
|
||||
*/
|
||||
private boolean hasActiveNetwork(String packageName) {
|
||||
// To be implemented
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if any app is interacting with the user.
|
||||
*/
|
||||
public boolean hasInteractingApp(List<String> packageNames) {
|
||||
for (var packageName : packageNames) {
|
||||
if (hasActiveAudio(packageName)
|
||||
|| hasActiveNetwork(packageName)
|
||||
|| isAppTopVisible(packageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if any app is in the foreground.
|
||||
*/
|
||||
public boolean hasForegroundApp(List<String> packageNames) {
|
||||
for (var packageName : packageNames) {
|
||||
if (isAppForeground(packageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if any app is top visible.
|
||||
*/
|
||||
public boolean hasTopVisibleApp(List<String> packageNames) {
|
||||
for (var packageName : packageNames) {
|
||||
if (isAppTopVisible(packageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if there is an ongoing phone call.
|
||||
*/
|
||||
public boolean isInCall() {
|
||||
// To be implemented
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of packages which depend on {@code packageNames}. These are the packages
|
||||
* that will be affected when updating {@code packageNames} and should participate in
|
||||
* the evaluation of install constraints.
|
||||
*
|
||||
* TODO(b/235306967): Also include bounded services as dependency.
|
||||
*/
|
||||
public List<String> getDependencyPackages(List<String> packageNames) {
|
||||
var results = new ArraySet<String>();
|
||||
var am = mContext.getSystemService(ActivityManager.class);
|
||||
for (var info : am.getRunningAppProcesses()) {
|
||||
for (var packageName : packageNames) {
|
||||
if (!isPackageLoaded(info, packageName)) {
|
||||
continue;
|
||||
}
|
||||
for (var pkg : info.pkgList) {
|
||||
results.add(pkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(results);
|
||||
}
|
||||
}
|
||||
171
services/core/java/com/android/server/pm/GentleUpdateHelper.java
Normal file
171
services/core/java/com/android/server/pm/GentleUpdateHelper.java
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* 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 com.android.server.pm;
|
||||
|
||||
import android.annotation.WorkerThread;
|
||||
import android.app.ActivityThread;
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.app.job.JobService;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInstaller.InstallConstraints;
|
||||
import android.content.pm.PackageInstaller.InstallConstraintsResult;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A helper class to coordinate install flow for sessions with install constraints.
|
||||
* These sessions will be pending and wait until the constraints are satisfied to
|
||||
* resume installation.
|
||||
*/
|
||||
public class GentleUpdateHelper {
|
||||
private static final String TAG = "GentleUpdateHelper";
|
||||
private static final int JOB_ID = 235306967; // bug id
|
||||
// The timeout used to determine whether the device is idle or not.
|
||||
private static final long PENDING_CHECK_MILLIS = TimeUnit.SECONDS.toMillis(10);
|
||||
|
||||
/**
|
||||
* A wrapper class used by JobScheduler to schedule jobs.
|
||||
*/
|
||||
public static class Service extends JobService {
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters params) {
|
||||
try {
|
||||
var pis = (PackageInstallerService) ActivityThread.getPackageManager()
|
||||
.getPackageInstaller();
|
||||
var helper = pis.getGentleUpdateHelper();
|
||||
helper.mHandler.post(helper::runIdleJob);
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Failed to get PackageInstallerService", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters params) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static class PendingInstallConstraintsCheck {
|
||||
public final List<String> packageNames;
|
||||
public final InstallConstraints constraints;
|
||||
public final CompletableFuture<InstallConstraintsResult> future;
|
||||
PendingInstallConstraintsCheck(List<String> packageNames,
|
||||
InstallConstraints constraints,
|
||||
CompletableFuture<InstallConstraintsResult> future) {
|
||||
this.packageNames = packageNames;
|
||||
this.constraints = constraints;
|
||||
this.future = future;
|
||||
}
|
||||
}
|
||||
|
||||
private final Context mContext;
|
||||
private final Handler mHandler;
|
||||
private final AppStateHelper mAppStateHelper;
|
||||
// Worker thread only
|
||||
private final ArrayDeque<PendingInstallConstraintsCheck> mPendingChecks = new ArrayDeque<>();
|
||||
private boolean mHasPendingIdleJob;
|
||||
|
||||
GentleUpdateHelper(Context context, Looper looper, AppStateHelper appStateHelper) {
|
||||
mContext = context;
|
||||
mHandler = new Handler(looper);
|
||||
mAppStateHelper = appStateHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if install constraints are satisfied for the given packages.
|
||||
*/
|
||||
CompletableFuture<InstallConstraintsResult> checkInstallConstraints(
|
||||
List<String> packageNames, InstallConstraints constraints) {
|
||||
var future = new CompletableFuture<InstallConstraintsResult>();
|
||||
mHandler.post(() -> {
|
||||
var pendingCheck = new PendingInstallConstraintsCheck(
|
||||
packageNames, constraints, future);
|
||||
if (constraints.isRequireDeviceIdle()) {
|
||||
mPendingChecks.add(pendingCheck);
|
||||
// JobScheduler doesn't provide queries about whether the device is idle.
|
||||
// We schedule 2 tasks to determine device idle. If the idle job is executed
|
||||
// before the delayed runnable, we know the device is idle.
|
||||
// Note #processPendingCheck will be no-op for the task executed later.
|
||||
scheduleIdleJob();
|
||||
mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false),
|
||||
PENDING_CHECK_MILLIS);
|
||||
} else {
|
||||
processPendingCheck(pendingCheck, false);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void scheduleIdleJob() {
|
||||
if (mHasPendingIdleJob) {
|
||||
// No need to schedule the job again
|
||||
return;
|
||||
}
|
||||
mHasPendingIdleJob = true;
|
||||
var componentName = new ComponentName(
|
||||
mContext.getPackageName(), GentleUpdateHelper.Service.class.getName());
|
||||
var jobInfo = new JobInfo.Builder(JOB_ID, componentName)
|
||||
.setRequiresDeviceIdle(true)
|
||||
.build();
|
||||
var jobScheduler = mContext.getSystemService(JobScheduler.class);
|
||||
jobScheduler.schedule(jobInfo);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void runIdleJob() {
|
||||
mHasPendingIdleJob = false;
|
||||
processPendingChecksInIdle();
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void processPendingCheck(PendingInstallConstraintsCheck pendingCheck, boolean isIdle) {
|
||||
var future = pendingCheck.future;
|
||||
if (future.isDone()) {
|
||||
return;
|
||||
}
|
||||
var constraints = pendingCheck.constraints;
|
||||
var packageNames = mAppStateHelper.getDependencyPackages(pendingCheck.packageNames);
|
||||
var constraintsSatisfied = (!constraints.isRequireDeviceIdle() || isIdle)
|
||||
&& (!constraints.isRequireAppNotForeground()
|
||||
|| !mAppStateHelper.hasForegroundApp(packageNames))
|
||||
&& (!constraints.isRequireAppNotInteracting()
|
||||
|| !mAppStateHelper.hasInteractingApp(packageNames))
|
||||
&& (!constraints.isRequireAppNotTopVisible()
|
||||
|| !mAppStateHelper.hasTopVisibleApp(packageNames))
|
||||
&& (!constraints.isRequireNotInCall()
|
||||
|| !mAppStateHelper.isInCall());
|
||||
future.complete(new InstallConstraintsResult((constraintsSatisfied)));
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void processPendingChecksInIdle() {
|
||||
while (!mPendingChecks.isEmpty()) {
|
||||
processPendingCheck(mPendingChecks.remove(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ import android.content.pm.IPackageInstallerCallback;
|
||||
import android.content.pm.IPackageInstallerSession;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageInstaller.InstallConstraints;
|
||||
import android.content.pm.PackageInstaller.SessionInfo;
|
||||
import android.content.pm.PackageInstaller.SessionParams;
|
||||
import android.content.pm.PackageItemInfo;
|
||||
@@ -54,12 +55,14 @@ import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SELinux;
|
||||
@@ -88,6 +91,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
||||
import com.android.internal.notification.SystemNotificationChannels;
|
||||
import com.android.internal.util.ImageUtils;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.modules.utils.TypedXmlPullParser;
|
||||
import com.android.modules.utils.TypedXmlSerializer;
|
||||
import com.android.server.IoThread;
|
||||
@@ -186,6 +190,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
|
||||
|
||||
private final InternalCallback mInternalCallback = new InternalCallback();
|
||||
private final PackageSessionVerifier mSessionVerifier;
|
||||
private final GentleUpdateHelper mGentleUpdateHelper;
|
||||
|
||||
/**
|
||||
* Used for generating session IDs. Since this is created at boot time,
|
||||
@@ -272,6 +277,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
|
||||
mStagingManager = new StagingManager(context);
|
||||
mSessionVerifier = new PackageSessionVerifier(context, mPm, mApexManager,
|
||||
apexParserSupplier, mInstallThread.getLooper());
|
||||
mGentleUpdateHelper = new GentleUpdateHelper(
|
||||
context, mInstallThread.getLooper(), new AppStateHelper(context));
|
||||
|
||||
LocalServices.getService(SystemServiceManager.class).startService(
|
||||
new Lifecycle(context, this));
|
||||
@@ -1232,6 +1239,33 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkInstallConstraints(String installerPackageName, List<String> packageNames,
|
||||
InstallConstraints constraints, RemoteCallback callback) {
|
||||
Preconditions.checkArgument(packageNames != null);
|
||||
Preconditions.checkArgument(constraints != null);
|
||||
Preconditions.checkArgument(callback != null);
|
||||
|
||||
final var snapshot = mPm.snapshotComputer();
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
if (!isCalledBySystemOrShell(callingUid)) {
|
||||
for (var packageName : packageNames) {
|
||||
var ps = snapshot.getPackageStateInternal(packageName);
|
||||
if (ps == null || !TextUtils.equals(
|
||||
ps.getInstallSource().mInstallerPackageName, installerPackageName)) {
|
||||
throw new SecurityException("Caller has no access to package " + packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var future = mGentleUpdateHelper.checkInstallConstraints(packageNames, constraints);
|
||||
future.thenAccept(result -> {
|
||||
var b = new Bundle();
|
||||
b.putParcelable("result", result);
|
||||
callback.sendResult(b);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCallback(IPackageInstallerCallback callback, int userId) {
|
||||
final Computer snapshot = mPm.snapshotComputer();
|
||||
@@ -1264,6 +1298,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
|
||||
return mSessionVerifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GentleUpdateHelper getGentleUpdateHelper() {
|
||||
return mGentleUpdateHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bypassNextStagedInstallerCheck(boolean value) {
|
||||
if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
|
||||
|
||||
@@ -29,4 +29,9 @@ public interface PackageSessionProvider {
|
||||
PackageInstallerSession getSession(int sessionId);
|
||||
|
||||
PackageSessionVerifier getSessionVerifier();
|
||||
|
||||
/**
|
||||
* Get the GentleUpdateHelper instance.
|
||||
*/
|
||||
GentleUpdateHelper getGentleUpdateHelper();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user