Merge "[incremental/pm] app states and transitions"

This commit is contained in:
Songchun Fan
2020-10-02 18:14:40 +00:00
committed by Android (Google) Code Review
16 changed files with 979 additions and 34 deletions

View File

@@ -2731,6 +2731,54 @@ public class Intent implements Parcelable, Cloneable {
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_MY_PACKAGE_UNSUSPENDED = "android.intent.action.MY_PACKAGE_UNSUSPENDED";
/**
* Broadcast Action: Sent to indicate that the package becomes startable.
* The intent will have the following extra values:
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. </li>
* <li> {@link #EXTRA_PACKAGE_NAME} containing the package name. </li>
* </li>
* </ul>
*
* <p class="note">This is a protected intent that can only be sent by the system.
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGE_STARTABLE = "android.intent.action.PACKAGE_STARTABLE";
/**
* Broadcast Action: Sent to indicate that the package becomes unstartable.
* The intent will have the following extra values:
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. </li>
* <li> {@link #EXTRA_PACKAGE_NAME} containing the package name. </li>
* <li> {@link #EXTRA_REASON} containing the integer indicating the reason for the state change,
* @see PackageManager.UnstartableReason
* </li>
* </ul>
*
* <p class="note">This is a protected intent that can only be sent by the system.
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGE_UNSTARTABLE =
"android.intent.action.PACKAGE_UNSTARTABLE";
/**
* Broadcast Action: Sent to indicate that the package is fully loaded.
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. </li>
* <li> {@link #EXTRA_PACKAGE_NAME} containing the package name. </li>
* </li>
* </ul>
*
* <p class="note">This is a protected intent that can only be sent by the system.
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGE_FULLY_LOADED =
"android.intent.action.PACKAGE_FULLY_LOADED";
/**
* Broadcast Action: A user ID has been removed from the system. The user
* ID number is stored in the extra data under {@link #EXTRA_UID}.

View File

@@ -50,7 +50,30 @@ oneway interface IDataLoaderStatusListener {
* fail and all retry limits are exceeded. */
const int DATA_LOADER_UNRECOVERABLE = 8;
/** There are no known issues with the data stream. */
const int STREAM_HEALTHY = 0;
/** There are issues with the current transport layer (network, adb connection, etc.) that may
* recover automatically or could eventually require user intervention. */
const int STREAM_TRANSPORT_ERROR = 1;
/** Integrity failures in the data stream, this could be due to file corruption, decompression
* issues or similar. This indicates a likely unrecoverable error. */
const int STREAM_INTEGRITY_ERROR = 2;
/** There are issues with the source of the data, e.g., backend availability issues, account
* issues. This indicates a potentially recoverable error, but one that may take a long time to
* resolve. */
const int STREAM_SOURCE_ERROR = 3;
/** The device or app is low on storage and cannot complete the stream as a result.
* A subsequent page miss resulting in app failure will transition app to unstartable state. */
const int STREAM_STORAGE_ERROR = 4;
/** Data loader status callback */
void onStatusChanged(in int dataLoaderId, in int status);
/** Callback to report streaming health status of a specific data loader */
void reportStreamHealth(in int dataLoaderId, in int streamStatus);
}

View File

@@ -0,0 +1,20 @@
/*
**
** Copyright 2020, 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;
parcelable IncrementalStatesInfo;

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2020 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.content.pm;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Info about a package's states in Parcelable format.
* @hide
*/
public class IncrementalStatesInfo implements Parcelable {
private boolean mIsStartable;
private boolean mIsLoading;
private float mProgress;
public IncrementalStatesInfo(boolean isStartable, boolean isLoading, float progress) {
mIsStartable = isStartable;
mIsLoading = isLoading;
mProgress = progress;
}
private IncrementalStatesInfo(Parcel source) {
mIsStartable = source.readBoolean();
mIsLoading = source.readBoolean();
mProgress = source.readFloat();
}
public boolean isStartable() {
return mIsStartable;
}
public boolean isLoading() {
return mIsLoading;
}
public float getProgress() {
return mProgress;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeBoolean(mIsStartable);
dest.writeBoolean(mIsLoading);
dest.writeFloat(mProgress);
}
public static final @android.annotation.NonNull Creator<IncrementalStatesInfo> CREATOR =
new Creator<IncrementalStatesInfo>() {
public IncrementalStatesInfo createFromParcel(Parcel source) {
return new IncrementalStatesInfo(source);
}
public IncrementalStatesInfo[] newArray(int size) {
return new IncrementalStatesInfo[size];
}
};
}

View File

@@ -3786,6 +3786,39 @@ public abstract class PackageManager {
*/
public static final int SYSTEM_APP_STATE_UNINSTALLED = 3;
/**
* Reasons for why a package is unstartable.
* @hide
*/
@IntDef({UNSTARTABLE_REASON_UNKNOWN,
UNSTARTABLE_REASON_DATALOADER_TRANSPORT,
UNSTARTABLE_REASON_DATALOADER_STORAGE
})
@Retention(RetentionPolicy.SOURCE)
public @interface UnstartableReason {}
/**
* Unstartable state with no root cause specified. E.g., data loader seeing missing pages but
* unclear about the cause. This corresponds to a generic alert window shown to the user when
* the user attempts to launch the app.
* @hide
*/
public static final int UNSTARTABLE_REASON_UNKNOWN = 0;
/**
* Unstartable state after hint from dataloader of issues with the transport layer.
* This corresponds to an alert window shown to the user indicating network errors.
* @hide
*/
public static final int UNSTARTABLE_REASON_DATALOADER_TRANSPORT = 1;
/**
* Unstartable state after encountering storage limitations.
* This corresponds to an alert window indicating limited storage.
* @hide
*/
public static final int UNSTARTABLE_REASON_DATALOADER_STORAGE = 2;
/** {@hide} */
public int getUserId() {
return UserHandle.myUserId();

View File

@@ -44,7 +44,6 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

View File

@@ -124,6 +124,11 @@ message PackageProto {
optional string originating_package_name = 2;
}
message StatesProto {
optional bool is_startable = 1;
optional bool is_loading = 2;
}
// Name of package. e.g. "com.android.providers.telephony".
optional string name = 1;
// UID for this package as assigned by Android OS.
@@ -145,4 +150,6 @@ message PackageProto {
repeated UserInfoProto users = 9;
// Where the request to install this package came from,
optional InstallSourceProto install_source = 10;
// Whether the package is startable or is still loading
optional StatesProto states = 11;
}

View File

@@ -42,6 +42,9 @@
<protected-broadcast android:name="android.intent.action.PACKAGE_REMOVED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_CHANGED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_STARTABLE" />
<protected-broadcast android:name="android.intent.action.PACKAGE_UNSTARTABLE" />
<protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_LOADED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_ENABLE_ROLLBACK" />
<protected-broadcast android:name="android.intent.action.CANCEL_ENABLE_ROLLBACK" />
<protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" />

View File

@@ -0,0 +1,480 @@
/*
* Copyright (C) 2020 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.content.pm.IDataLoaderStatusListener;
import android.content.pm.IncrementalStatesInfo;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.incremental.IStorageHealthListener;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.function.pooled.PooledLambda;
import java.util.function.BiConsumer;
/**
* Manages state transitions of a package installed on Incremental File System. Currently manages:
* 1. startable state (whether a package is allowed to be launched), and
* 2. loading state (whether a package is still loading or has been fully loaded).
*
* The following events might change the states of a package:
* 1. Installation commit
* 2. Incremental storage health
* 3. Data loader stream health
* 4. Loading progress changes
*
* @hide
*/
public final class IncrementalStates {
private static final String TAG = "IncrementalStates";
private static final boolean DEBUG = false;
private final Handler mHandler = BackgroundThread.getHandler();
private final Object mLock = new Object();
@GuardedBy("mLock")
private int mStreamStatus = IDataLoaderStatusListener.STREAM_HEALTHY;
@GuardedBy("mLock")
private int mStorageHealthStatus = IStorageHealthListener.HEALTH_STATUS_OK;
@GuardedBy("mLock")
private final LoadingState mLoadingState;
@GuardedBy("mLock")
private StartableState mStartableState;
@GuardedBy("mLock")
private Callback mCallback = null;
private final BiConsumer<Integer, Integer> mStatusConsumer;
public IncrementalStates() {
// By default the package is not startable and not fully loaded (i.e., is loading)
this(false, true);
}
public IncrementalStates(boolean isStartable, boolean isLoading) {
mStartableState = new StartableState(isStartable);
mLoadingState = new LoadingState(isLoading);
mStatusConsumer = new StatusConsumer();
}
/**
* Callback interface to report that the startable state of this package has changed.
*/
public interface Callback {
/**
* Reports that the package is now unstartable and the unstartable reason.
*/
void onPackageUnstartable(int reason);
/**
* Reports that the package is now startable.
*/
void onPackageStartable();
/**
* Reports that package is fully loaded.
*/
void onPackageFullyLoaded();
}
/**
* By calling this method, the caller indicates that package installation has just been
* committed. The package becomes startable. Set the initial loading state after the package
* is committed. Incremental packages are by-default loading; non-Incremental packages are not.
*
* @param isIncremental whether a package is installed on Incremental or not.
*/
public void onCommit(boolean isIncremental) {
if (DEBUG) {
Slog.i(TAG, "received package commit event");
}
synchronized (mLock) {
if (!mStartableState.isStartable()) {
mStartableState.adoptNewStartableStateLocked(true);
}
if (mLoadingState.isLoading() != isIncremental) {
mLoadingState.adoptNewLoadingStateLocked(isIncremental);
}
}
mHandler.post(PooledLambda.obtainRunnable(
IncrementalStates::reportStartableState,
IncrementalStates.this).recycleOnUse());
if (!isIncremental) {
mHandler.post(PooledLambda.obtainRunnable(
IncrementalStates::reportFullyLoaded,
IncrementalStates.this).recycleOnUse());
}
}
private void reportStartableState() {
final Callback callback;
final boolean startable;
final int reason;
synchronized (mLock) {
callback = mCallback;
startable = mStartableState.isStartable();
reason = mStartableState.getUnstartableReason();
}
if (callback == null) {
return;
}
if (startable) {
callback.onPackageStartable();
} else {
callback.onPackageUnstartable(reason);
}
}
private void reportFullyLoaded() {
final Callback callback;
synchronized (mLock) {
callback = mCallback;
}
if (callback != null) {
callback.onPackageFullyLoaded();
}
}
private class StatusConsumer implements BiConsumer<Integer, Integer> {
@Override
public void accept(Integer streamStatus, Integer storageStatus) {
if (streamStatus == null && storageStatus == null) {
return;
}
final boolean oldState, newState;
synchronized (mLock) {
if (!mLoadingState.isLoading()) {
// Do nothing if the package is already fully loaded
return;
}
oldState = mStartableState.isStartable();
if (streamStatus != null) {
mStreamStatus = (Integer) streamStatus;
}
if (storageStatus != null) {
mStorageHealthStatus = (Integer) storageStatus;
}
updateStartableStateLocked();
newState = mStartableState.isStartable();
}
if (oldState != newState) {
mHandler.post(PooledLambda.obtainRunnable(IncrementalStates::reportStartableState,
IncrementalStates.this).recycleOnUse());
}
}
};
/**
* By calling this method, the caller indicates that there issues with the Incremental
* Storage,
* on which the package is installed. The state will change according to the status
* code defined in {@code IStorageHealthListener}.
*/
public void onStorageHealthStatusChanged(int storageHealthStatus) {
if (DEBUG) {
Slog.i(TAG, "received storage health status changed event : storageHealthStatus="
+ storageHealthStatus);
}
mStatusConsumer.accept(null, storageHealthStatus);
}
/**
* By calling this method, the caller indicates that the stream status of the package has
* been
* changed. This could indicate a streaming error. The state will change according to the
* status
* code defined in {@code IDataLoaderStatusListener}.
*/
public void onStreamStatusChanged(int streamState) {
if (DEBUG) {
Slog.i(TAG, "received stream status changed event : streamState=" + streamState);
}
mStatusConsumer.accept(streamState, null);
}
/**
* Use the specified callback to report state changing events.
*
* @param callback Object to report new state.
*/
public void setCallback(Callback callback) {
if (DEBUG) {
Slog.i(TAG, "registered callback");
}
synchronized (mLock) {
mCallback = callback;
}
}
/**
* Update the package loading progress to specified value. This might change startable state.
*
* @param progress Value between [0, 1].
*/
public void setProgress(float progress) {
final boolean newLoadingState;
final boolean oldStartableState, newStartableState;
synchronized (mLock) {
oldStartableState = mStartableState.isStartable();
updateProgressLocked(progress);
newLoadingState = mLoadingState.isLoading();
newStartableState = mStartableState.isStartable();
}
if (!newLoadingState) {
mHandler.post(PooledLambda.obtainRunnable(
IncrementalStates::reportFullyLoaded,
IncrementalStates.this).recycleOnUse());
}
if (newStartableState != oldStartableState) {
mHandler.post(PooledLambda.obtainRunnable(
IncrementalStates::reportStartableState,
IncrementalStates.this).recycleOnUse());
}
}
/**
* @return the current startable state.
*/
public boolean isStartable() {
synchronized (mLock) {
return mStartableState.isStartable();
}
}
/**
* @return Whether the package is still being loaded or has been fully loaded.
*/
public boolean isLoading() {
synchronized (mLock) {
return mLoadingState.isLoading();
}
}
/**
* @return all current states in a Parcelable.
*/
public IncrementalStatesInfo getIncrementalStatesInfo() {
synchronized (mLock) {
return new IncrementalStatesInfo(mStartableState.isStartable(),
mLoadingState.isLoading(),
mLoadingState.getProgress());
}
}
/**
* Determine the next state based on the current state, current stream status and storage
* health
* status. If the next state is different from the current state, proceed with state
* change.
*/
private void updateStartableStateLocked() {
final boolean currentState = mStartableState.isStartable();
boolean nextState = currentState;
if (!currentState) {
if (mStorageHealthStatus == IStorageHealthListener.HEALTH_STATUS_OK
&& mStreamStatus == IDataLoaderStatusListener.STREAM_HEALTHY) {
// change from unstartable -> startable when both stream and storage are healthy
nextState = true;
}
} else {
if (mStorageHealthStatus == IStorageHealthListener.HEALTH_STATUS_UNHEALTHY) {
// unrecoverable if storage is unhealthy
nextState = false;
} else {
switch (mStreamStatus) {
case IDataLoaderStatusListener.STREAM_INTEGRITY_ERROR:
// unrecoverable, fall through
case IDataLoaderStatusListener.STREAM_SOURCE_ERROR: {
// unrecoverable
nextState = false;
break;
}
case IDataLoaderStatusListener.STREAM_STORAGE_ERROR: {
if (mStorageHealthStatus != IStorageHealthListener.HEALTH_STATUS_OK) {
// unrecoverable if there is a pending read AND storage is limited
nextState = false;
}
break;
}
default:
// anything else, remain startable
break;
}
}
}
if (nextState == currentState) {
return;
}
mStartableState.adoptNewStartableStateLocked(nextState);
}
private void updateProgressLocked(float progress) {
if (DEBUG) {
Slog.i(TAG, "received progress update: " + progress);
}
mLoadingState.setProgress(progress);
if (1 - progress < 0.001) {
if (DEBUG) {
Slog.i(TAG, "package is fully loaded");
}
mLoadingState.setProgress(1);
if (mLoadingState.isLoading()) {
mLoadingState.adoptNewLoadingStateLocked(false);
}
// Also updates startable state if necessary
if (!mStartableState.isStartable()) {
mStartableState.adoptNewStartableStateLocked(true);
}
}
}
private class StartableState {
private boolean mIsStartable;
private int mUnstartableReason = PackageManager.UNSTARTABLE_REASON_UNKNOWN;
StartableState(boolean isStartable) {
mIsStartable = isStartable;
}
public boolean isStartable() {
return mIsStartable;
}
public int getUnstartableReason() {
return mUnstartableReason;
}
public void adoptNewStartableStateLocked(boolean nextState) {
if (DEBUG) {
Slog.i(TAG, "startable state changed from " + mIsStartable + " to " + nextState);
}
mIsStartable = nextState;
mUnstartableReason = getUnstartableReasonLocked();
}
private int getUnstartableReasonLocked() {
if (mIsStartable) {
return PackageManager.UNSTARTABLE_REASON_UNKNOWN;
}
// Translate stream status to reason for unstartable state
switch (mStreamStatus) {
case IDataLoaderStatusListener.STREAM_TRANSPORT_ERROR:
// fall through
case IDataLoaderStatusListener.STREAM_INTEGRITY_ERROR:
// fall through
case IDataLoaderStatusListener.STREAM_SOURCE_ERROR: {
return PackageManager.UNSTARTABLE_REASON_DATALOADER_TRANSPORT;
}
case IDataLoaderStatusListener.STREAM_STORAGE_ERROR: {
return PackageManager.UNSTARTABLE_REASON_DATALOADER_STORAGE;
}
default:
return PackageManager.UNSTARTABLE_REASON_UNKNOWN;
}
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof StartableState)) {
return false;
}
StartableState l = (StartableState) o;
return l.mIsStartable == mIsStartable;
}
@Override
public int hashCode() {
return Boolean.hashCode(mIsStartable);
}
}
private class LoadingState {
private boolean mIsLoading;
private float mProgress;
LoadingState(boolean isLoading) {
mIsLoading = isLoading;
mProgress = isLoading ? 0 : 1;
}
public boolean isLoading() {
return mIsLoading;
}
public float getProgress() {
return mProgress;
}
public void setProgress(float progress) {
mProgress = progress;
}
public void adoptNewLoadingStateLocked(boolean nextState) {
if (DEBUG) {
Slog.i(TAG, "Loading state changed from " + mIsLoading + " to " + nextState);
}
mIsLoading = nextState;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof LoadingState)) {
return false;
}
LoadingState l = (LoadingState) o;
return l.mIsLoading == mIsLoading && l.mProgress == mProgress;
}
@Override
public int hashCode() {
int hashCode = Boolean.hashCode(mIsLoading);
hashCode = 31 * hashCode + Float.hashCode(mProgress);
return hashCode;
}
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof IncrementalStates)) {
return false;
}
IncrementalStates l = (IncrementalStates) o;
return l.mStorageHealthStatus == mStorageHealthStatus
&& l.mStreamStatus == mStreamStatus
&& l.mStartableState.equals(mStartableState)
&& l.mLoadingState.equals(mLoadingState);
}
@Override
public int hashCode() {
int hashCode = mStartableState.hashCode();
hashCode = 31 * hashCode + mLoadingState.hashCode();
hashCode = 31 * hashCode + mStorageHealthStatus;
hashCode = 31 * hashCode + mStreamStatus;
return hashCode;
}
}

View File

@@ -141,6 +141,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.dex.DexManager;
@@ -1700,20 +1701,26 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
private void onStorageUnhealthy() {
private void onStorageHealthStatusChanged(int status) {
final String packageName = getPackageName();
if (TextUtils.isEmpty(packageName)) {
// The package has not been installed.
return;
}
final PackageManagerService packageManagerService = mPm;
mHandler.post(() -> {
if (packageManagerService.deletePackageX(packageName,
PackageManager.VERSION_CODE_HIGHEST, UserHandle.USER_SYSTEM,
PackageManager.DELETE_ALL_USERS) != PackageManager.DELETE_SUCCEEDED) {
Slog.e(TAG, "Failed to uninstall package with failed dataloader: " + packageName);
}
});
mHandler.post(PooledLambda.obtainRunnable(
PackageManagerService::onStorageHealthStatusChanged,
mPm, packageName, status, userId).recycleOnUse());
}
private void onStreamHealthStatusChanged(int status) {
final String packageName = getPackageName();
if (TextUtils.isEmpty(packageName)) {
// The package has not been installed.
return;
}
mHandler.post(PooledLambda.obtainRunnable(
PackageManagerService::onStreamStatusChanged,
mPm, packageName, status, userId).recycleOnUse());
}
/**
@@ -3261,7 +3268,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (isDestroyedOrDataLoaderFinished) {
switch (status) {
case IDataLoaderStatusListener.DATA_LOADER_UNRECOVERABLE:
onStorageUnhealthy();
// treat as unhealthy storage
onStorageHealthStatusChanged(
IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
return;
}
return;
@@ -3358,6 +3367,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
sendPendingStreaming(mContext, statusReceiver, sessionId, e.getMessage());
}
}
@Override
public void reportStreamHealth(int dataLoaderId, int streamStatus) {
synchronized (mLock) {
if (!mDestroyed && !mDataLoaderFinished) {
// ignore streaming status if package isn't installed
return;
}
}
onStreamHealthStatusChanged(streamStatus);
}
};
if (!manualStartAndDestroy) {
@@ -3377,11 +3396,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
if (isDestroyedOrDataLoaderFinished) {
// App's installed.
switch (status) {
case IStorageHealthListener.HEALTH_STATUS_UNHEALTHY:
onStorageUnhealthy();
return;
}
onStorageHealthStatusChanged(status);
return;
}

View File

@@ -16392,12 +16392,24 @@ public class PackageManagerService extends IPackageManager.Stub
} else if (!previousUserIds.contains(userId)) {
ps.setInstallReason(installReason, userId);
}
// TODO(b/169721400): generalize Incremental States and create a Callback object
// that can be used for all the packages.
final IncrementalStatesCallback incrementalStatesCallback =
new IncrementalStatesCallback(ps, userId);
final String codePath = ps.getPathString();
if (IncrementalManager.isIncrementalPath(codePath) && mIncrementalManager != null) {
mIncrementalManager.registerCallback(codePath, incrementalStatesCallback);
ps.setIncrementalStatesCallback(incrementalStatesCallback);
}
// Ensure that the uninstall reason is UNKNOWN for users with the package installed.
for (int currentUserId : allUsersList) {
if (ps.getInstalled(currentUserId)) {
ps.setUninstallReason(UNINSTALL_REASON_UNKNOWN, currentUserId);
}
}
mSettings.writeKernelMappingLPr(ps);
}
res.name = pkgName;
@@ -17194,6 +17206,7 @@ public class PackageManagerService extends IPackageManager.Stub
mDexManager.notifyPackageUpdated(pkg.getPackageName(),
pkg.getBaseApkPath(), pkg.getSplitCodePaths());
}
reconciledPkg.pkgSetting.setStatesOnCommit();
// Prepare the application profiles for the new code paths.
// This needs to be done before invoking dexopt so that any install-time profile
@@ -17290,6 +17303,155 @@ public class PackageManagerService extends IPackageManager.Stub
NativeLibraryHelper.waitForNativeBinariesExtraction(incrementalStorages);
}
private class IncrementalStatesCallback extends IPackageLoadingProgressCallback.Stub
implements IncrementalStates.Callback {
@GuardedBy("mPackageSetting")
private final PackageSetting mPackageSetting;
private final String mPackageName;
private final String mPathString;
private final int mUserId;
private final int[] mInstalledUserIds;
IncrementalStatesCallback(PackageSetting packageSetting, int userId) {
mPackageSetting = packageSetting;
mPackageName = packageSetting.name;
mUserId = userId;
mPathString = packageSetting.getPathString();
final int[] allUserIds = resolveUserIds(userId);
final ArrayList<Integer> installedUserIds = new ArrayList<>();
for (int i = 0; i < allUserIds.length; i++) {
if (packageSetting.getInstalled(allUserIds[i])) {
installedUserIds.add(allUserIds[i]);
}
}
final int numInstalledUserId = installedUserIds.size();
mInstalledUserIds = new int[numInstalledUserId];
for (int i = 0; i < numInstalledUserId; i++) {
mInstalledUserIds[i] = installedUserIds.get(i);
}
}
@Override
public void onPackageLoadingProgressChanged(float progress) {
synchronized (mPackageSetting) {
mPackageSetting.setLoadingProgress(progress);
}
}
@Override
public void onPackageFullyLoaded() {
mIncrementalManager.unregisterCallback(mPathString, this);
final SparseArray<int[]> newBroadcastAllowList;
synchronized (mLock) {
newBroadcastAllowList = mAppsFilter.getVisibilityAllowList(
getPackageSettingInternal(mPackageName, Process.SYSTEM_UID),
mInstalledUserIds, mSettings.mPackages);
}
Bundle extras = new Bundle(1);
extras.putInt(Intent.EXTRA_UID, mUserId);
extras.putString(Intent.EXTRA_PACKAGE_NAME, mPackageName);
sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_LOADED, mPackageName,
extras, 0 /*flags*/,
null /*targetPackage*/, null /*finishedReceiver*/,
mInstalledUserIds, null /* instantUserIds */, newBroadcastAllowList);
}
@Override
public void onPackageUnstartable(int reason) {
final SparseArray<int[]> newBroadcastAllowList;
synchronized (mLock) {
newBroadcastAllowList = mAppsFilter.getVisibilityAllowList(
getPackageSettingInternal(mPackageName, Process.SYSTEM_UID),
mInstalledUserIds, mSettings.mPackages);
}
Bundle extras = new Bundle(1);
extras.putInt(Intent.EXTRA_UID, mUserId);
extras.putString(Intent.EXTRA_PACKAGE_NAME, mPackageName);
extras.putInt(Intent.EXTRA_REASON, reason);
// send broadcast to users with this app installed
sendPackageBroadcast(Intent.ACTION_PACKAGE_UNSTARTABLE, mPackageName,
extras, 0 /*flags*/,
null /*targetPackage*/, null /*finishedReceiver*/,
mInstalledUserIds, null /* instantUserIds */, newBroadcastAllowList);
}
@Override
public void onPackageStartable() {
final SparseArray<int[]> newBroadcastAllowList;
synchronized (mLock) {
newBroadcastAllowList = mAppsFilter.getVisibilityAllowList(
getPackageSettingInternal(mPackageName, Process.SYSTEM_UID),
mInstalledUserIds, mSettings.mPackages);
}
Bundle extras = new Bundle(1);
extras.putInt(Intent.EXTRA_UID, mUserId);
extras.putString(Intent.EXTRA_PACKAGE_NAME, mPackageName);
// send broadcast to users with this app installed
sendPackageBroadcast(Intent.ACTION_PACKAGE_STARTABLE, mPackageName,
extras, 0 /*flags*/,
null /*targetPackage*/, null /*finishedReceiver*/,
mInstalledUserIds, null /* instantUserIds */, newBroadcastAllowList);
}
}
/**
* This is an internal method that is used to indicate changes on the health status of the
* Incremental Storage used by an installed package with an associated user id. This might
* result in a change in the loading state of the package.
*/
public void onStorageHealthStatusChanged(String packageName, int status, int userId) {
final int callingUid = Binder.getCallingUid();
mPermissionManager.enforceCrossUserPermission(
callingUid, userId, true, false,
"onStorageHealthStatusChanged");
final PackageSetting ps = getPackageSettingForUser(packageName, callingUid, userId);
if (ps == null) {
return;
}
ps.setStorageHealthStatus(status);
}
/**
* This is an internal method that is used to indicate changes on the stream status of the
* data loader used by an installed package with an associated user id. This might
* result in a change in the loading state of the package.
*/
public void onStreamStatusChanged(String packageName, int status, int userId) {
final int callingUid = Binder.getCallingUid();
mPermissionManager.enforceCrossUserPermission(
callingUid, userId, true, false,
"onStreamStatusChanged");
final PackageSetting ps = getPackageSettingForUser(packageName, callingUid, userId);
if (ps == null) {
return;
}
ps.setStreamStatus(status);
}
@Nullable PackageSetting getPackageSettingForUser(String packageName, int callingUid,
int userId) {
final PackageSetting ps;
synchronized (mLock) {
ps = mSettings.mPackages.get(packageName);
if (ps == null) {
Slog.w(TAG, "Failed to get package setting. Package " + packageName
+ " is not installed");
return null;
}
if (!ps.getInstalled(userId)) {
Slog.w(TAG, "Failed to get package setting. Package " + packageName
+ " is not installed for user " + userId);
return null;
}
if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
Slog.w(TAG, "Failed to get package setting. Package " + packageName
+ " is not visible to the calling app");
return null;
}
}
return ps;
}
private void notifyPackageChangeObserversOnUpdate(ReconciledPackage reconciledPkg) {
final PackageSetting pkgSetting = reconciledPkg.pkgSetting;
final PackageInstalledInfo pkgInstalledInfo = reconciledPkg.installResult;
@@ -25409,24 +25571,15 @@ public class PackageManagerService extends IPackageManager.Stub
@Override
public boolean registerInstalledLoadingProgressCallback(String packageName,
PackageManagerInternal.InstalledLoadingProgressCallback callback, int userId) {
final int callingUid = Binder.getCallingUid();
mPermissionManager.enforceCrossUserPermission(
callingUid, userId, true, false,
"registerLoadingProgressCallback");
final PackageSetting ps;
synchronized (mLock) {
ps = mSettings.mPackages.get(packageName);
if (ps == null) {
Slog.w(TAG, "Failed registering loading progress callback. Package "
+ packageName + " is not installed");
return false;
}
if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
Slog.w(TAG, "Failed registering loading progress callback. Package "
+ packageName + " is not visible to the calling app");
return false;
}
// TODO(b/165841827): return false if package is fully loaded
final PackageSetting ps = getPackageSettingForUser(packageName, Binder.getCallingUid(),
userId);
if (ps == null) {
return false;
}
if (!ps.isPackageLoading()) {
Slog.w(TAG,
"Failed registering loading progress callback. Package is fully loaded.");
return false;
}
if (mIncrementalManager == null) {
Slog.w(TAG,

View File

@@ -333,6 +333,8 @@ public class PackageSetting extends PackageSettingBase {
installSource.originatingPackageName);
proto.end(sourceToken);
}
proto.write(PackageProto.StatesProto.IS_STARTABLE, incrementalStates.isStartable());
proto.write(PackageProto.StatesProto.IS_LOADING, incrementalStates.isLoading());
writeUsersInfoToProto(proto, PackageProto.USERS);
proto.end(packageToken);
}

View File

@@ -25,6 +25,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.IncrementalStatesInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.UninstallReason;
@@ -33,6 +34,7 @@ import android.content.pm.PackageUserState;
import android.content.pm.Signature;
import android.content.pm.SuspendDialogInfo;
import android.os.PersistableBundle;
import android.os.incremental.IncrementalManager;
import android.service.pm.PackageProto;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -133,6 +135,9 @@ public abstract class PackageSettingBase extends SettingBase {
boolean forceQueryableOverride;
@NonNull
public IncrementalStates incrementalStates;
PackageSettingBase(String name, String realName, @NonNull File path,
String legacyNativeLibraryPathString, String primaryCpuAbiString,
String secondaryCpuAbiString, String cpuAbiOverrideString,
@@ -151,6 +156,7 @@ public abstract class PackageSettingBase extends SettingBase {
this.versionCode = pVersionCode;
this.signatures = new PackageSignatures();
this.installSource = InstallSource.EMPTY;
this.incrementalStates = new IncrementalStates();
}
/**
@@ -257,6 +263,7 @@ public abstract class PackageSettingBase extends SettingBase {
orig.usesStaticLibrariesVersions.length) : null;
updateAvailable = orig.updateAvailable;
forceQueryableOverride = orig.forceQueryableOverride;
incrementalStates = orig.incrementalStates;
}
@VisibleForTesting
@@ -733,6 +740,66 @@ public abstract class PackageSettingBase extends SettingBase {
modifyUserState(userId).resetOverrideComponentLabelIcon();
}
/**
* @return True if package is startable, false otherwise.
*/
public boolean isPackageStartable() {
return incrementalStates.isStartable();
}
/**
* @return True if package is still being loaded, false if the package is fully loaded.
*/
public boolean isPackageLoading() {
return incrementalStates.isLoading();
}
/**
* @return all current states in a Parcelable.
*/
public IncrementalStatesInfo getIncrementalStates() {
return incrementalStates.getIncrementalStatesInfo();
}
/**
* Called to indicate that the package installation has been committed. This will create a
* new startable state and a new loading state with default values. By default, the package is
* startable after commit. For a package installed on Incremental, the loading state is true.
* For non-Incremental packages, the loading state is false.
*/
public void setStatesOnCommit() {
incrementalStates.onCommit(IncrementalManager.isIncrementalPath(getPathString()));
}
/**
* Called to set the callback to listen for startable state changes.
*/
public void setIncrementalStatesCallback(IncrementalStates.Callback callback) {
incrementalStates.setCallback(callback);
}
/**
* Called to report progress changes. This might trigger loading state change.
* @see IncrementalStates#setProgress(float)
*/
public void setLoadingProgress(float progress) {
incrementalStates.setProgress(progress);
}
/**
* @see IncrementalStates#onStorageHealthStatusChanged(int)
*/
public void setStorageHealthStatus(int status) {
incrementalStates.onStorageHealthStatusChanged(status);
}
/**
* @see IncrementalStates#onStreamStatusChanged(int)
*/
public void setStreamStatus(int status) {
incrementalStates.onStreamStatusChanged(status);
}
protected PackageSettingBase updateFrom(PackageSettingBase other) {
super.copyFrom(other);
setPath(other.getPath());
@@ -756,6 +823,7 @@ public abstract class PackageSettingBase extends SettingBase {
this.updateAvailable = other.updateAvailable;
this.verificationInfo = other.verificationInfo;
this.forceQueryableOverride = other.forceQueryableOverride;
this.incrementalStates = other.incrementalStates;
if (mOldCodePaths != null) {
if (other.mOldCodePaths != null) {

View File

@@ -2809,6 +2809,12 @@ public final class Settings {
if (pkg.forceQueryableOverride) {
serializer.attribute(null, "forceQueryable", "true");
}
if (pkg.isPackageStartable()) {
serializer.attribute(null, "isStartable", "true");
}
if (pkg.isPackageLoading()) {
serializer.attribute(null, "isLoading", "true");
}
writeUsesStaticLibLPw(serializer, pkg.usesStaticLibraries, pkg.usesStaticLibrariesVersions);
@@ -3594,6 +3600,8 @@ public final class Settings {
String version = null;
long versionCode = 0;
String installedForceQueryable = null;
String isStartable = null;
String isLoading = null;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
realName = parser.getAttributeValue(null, "realName");
@@ -3610,6 +3618,8 @@ public final class Settings {
cpuAbiOverrideString = parser.getAttributeValue(null, "cpuAbiOverride");
updateAvailable = parser.getAttributeValue(null, "updateAvailable");
installedForceQueryable = parser.getAttributeValue(null, "forceQueryable");
isStartable = parser.getAttributeValue(null, "isStartable");
isLoading = parser.getAttributeValue(null, "isLoading");
if (primaryCpuAbiString == null && legacyCpuAbiString != null) {
primaryCpuAbiString = legacyCpuAbiString;
@@ -3793,6 +3803,8 @@ public final class Settings {
packageSetting.secondaryCpuAbiString = secondaryCpuAbiString;
packageSetting.updateAvailable = "true".equals(updateAvailable);
packageSetting.forceQueryableOverride = "true".equals(installedForceQueryable);
packageSetting.incrementalStates = new IncrementalStates("true".equals(isStartable),
"true".equals(isLoading));
// Handle legacy string here for single-user mode
final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
if (enabledStr != null) {

View File

@@ -2135,6 +2135,11 @@ binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mount
return binder::Status::ok();
}
binder::Status IncrementalService::DataLoaderStub::reportStreamHealth(MountId mountId,
int newStatus) {
return binder::Status::ok();
}
bool IncrementalService::DataLoaderStub::isHealthParamsValid() const {
return mHealthCheckParams.blockedTimeoutMs > 0 &&
mHealthCheckParams.blockedTimeoutMs < mHealthCheckParams.unhealthyTimeoutMs;

View File

@@ -200,6 +200,7 @@ private:
private:
binder::Status onStatusChanged(MountId mount, int newStatus) final;
binder::Status reportStreamHealth(MountId mount, int newStatus) final;
sp<content::pm::IDataLoader> getDataLoader();