blob: 3101ca7f9f6bc6d0691da5b8cbbd49e3c1b7383c [file] [log] [blame]
/*
* 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.IncrementalStatesInfo;
import android.os.Handler;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.function.pooled.PooledLambda;
/**
* Manages state transitions of a package installed on Incremental File System. Currently manages:
* 1. 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. 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 final LoadingState mLoadingState;
@GuardedBy("mLock")
private Callback mCallback = null;
public IncrementalStates() {
// By default the package is not fully loaded (i.e., is loading)
this(true, 0);
}
public IncrementalStates(boolean isLoading, float loadingProgress) {
mLoadingState = new LoadingState(isLoading, loadingProgress);
}
/**
* Callback interface to report that the loading state of this package has changed.
*/
public interface Callback {
/**
* Reports that package is fully loaded.
*/
void onPackageFullyLoaded();
}
/**
* By calling this method, the caller indicates that package installation has just been
* committed. 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");
}
if (!isIncremental) {
synchronized (mLock) {
updateProgressLocked(1.0f);
}
onLoadingStateChanged();
}
}
private void onLoadingStateChanged() {
mHandler.post(PooledLambda.obtainRunnable(
IncrementalStates::reportFullyLoaded,
IncrementalStates.this).recycleOnUse());
}
private void reportFullyLoaded() {
final Callback callback;
synchronized (mLock) {
callback = mCallback;
}
if (callback != null) {
callback.onPackageFullyLoaded();
}
}
/**
* 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.
*
* @param progress Value between [0, 1].
*/
public void setProgress(float progress) {
final boolean oldLoadingState;
final boolean newLoadingState;
synchronized (mLock) {
oldLoadingState = mLoadingState.isLoading();
if (oldLoadingState) {
// Due to asynchronous progress reporting, incomplete progress might be received
// after the app is migrated off incremental. Ignore such progress updates.
updateProgressLocked(progress);
}
newLoadingState = mLoadingState.isLoading();
}
if (oldLoadingState && !newLoadingState) {
// Only report the state change when loading state changes from true to false
onLoadingStateChanged();
}
}
/**
* @return all current states in a Parcelable.
*/
public IncrementalStatesInfo getIncrementalStatesInfo() {
synchronized (mLock) {
return new IncrementalStatesInfo(
mLoadingState.isLoading(),
mLoadingState.getProgress());
}
}
private void updateProgressLocked(float progress) {
if (DEBUG) {
Slog.i(TAG, "received progress update: " + progress);
}
mLoadingState.setProgress(progress);
if (Math.abs(1.0f - progress) < 0.00000001f) {
if (DEBUG) {
Slog.i(TAG, "package is fully loaded");
}
mLoadingState.setProgress(1.0f);
if (mLoadingState.isLoading()) {
mLoadingState.adoptNewLoadingStateLocked(false);
}
}
}
private class LoadingState {
private boolean mIsLoading;
private float mProgress;
LoadingState(boolean isLoading, float loadingProgress) {
mIsLoading = isLoading;
// loading progress is reset to 1 if loading has finished
mProgress = isLoading ? loadingProgress : 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.mLoadingState.equals(mLoadingState);
}
@Override
public int hashCode() {
return mLoadingState.hashCode();
}
}