Fixing MainThreadInitializedObject
> Making SafeCloseable implementation mandatory, to prevent leaks during test and preview > Removing getNoCreate method and defining executeIfCreated to avoid null pointer exceptions > Fixing sandbox value leaking into main, by Checking sandbox against App context > Converting sanbox to an interface instead a class Bug: 335280439 Test: Presubmit Flag: None Change-Id: I951dcde871898e745ff6490a1c4f8fd1512888f5
This commit is contained in:
@@ -35,6 +35,7 @@ import androidx.room.Room;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.launcher3.model.AppShareabilityDatabase.ShareabilityDao;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.util.ArrayList;
|
||||
@@ -47,7 +48,7 @@ import java.util.function.Consumer;
|
||||
* Each app's status is retrieved from the Play Store's API. Statuses are cached in order
|
||||
* to limit extraneous calls to that API (which can be time-consuming).
|
||||
*/
|
||||
public class AppShareabilityManager {
|
||||
public class AppShareabilityManager implements SafeCloseable {
|
||||
@Retention(SOURCE)
|
||||
@IntDef({
|
||||
ShareabilityStatus.UNKNOWN,
|
||||
@@ -194,6 +195,11 @@ public class AppShareabilityManager {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
mDatabase.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a testable instance of this class
|
||||
* This instance allows database queries on the main thread
|
||||
|
||||
@@ -326,8 +326,12 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
super.destroy();
|
||||
mActive = false;
|
||||
StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer);
|
||||
if (mIsPrimaryInstance) {
|
||||
mStatsManager.clearPullAtomCallback(SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT);
|
||||
if (mIsPrimaryInstance && mStatsManager != null) {
|
||||
try {
|
||||
mStatsManager.clearPullAtomCallback(SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT);
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "Failed to unregister snapshot logging callback with StatsManager", e);
|
||||
}
|
||||
}
|
||||
destroyPredictors();
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.DeadObjectException;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
@@ -48,9 +47,10 @@ import com.android.launcher3.R;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.popup.RemoteActionShortcut;
|
||||
import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.util.BgObjectWithLooper;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.util.SimpleBroadcastReceiver;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
@@ -61,7 +61,7 @@ import java.util.Map;
|
||||
/**
|
||||
* Data model for digital wellbeing status of apps.
|
||||
*/
|
||||
public final class WellbeingModel extends BgObjectWithLooper {
|
||||
public final class WellbeingModel implements SafeCloseable {
|
||||
private static final String TAG = "WellbeingModel";
|
||||
private static final int[] RETRY_TIMES_MS = {5000, 15000, 30000};
|
||||
private static final boolean DEBUG = false;
|
||||
@@ -81,8 +81,12 @@ public final class WellbeingModel extends BgObjectWithLooper {
|
||||
private final Context mContext;
|
||||
private final String mWellbeingProviderPkg;
|
||||
|
||||
private Handler mWorkerHandler;
|
||||
private ContentObserver mContentObserver;
|
||||
private final Handler mWorkerHandler;
|
||||
private final ContentObserver mContentObserver;
|
||||
private final SimpleBroadcastReceiver mWellbeingAppChangeReceiver =
|
||||
new SimpleBroadcastReceiver(t -> restartObserver());
|
||||
private final SimpleBroadcastReceiver mAppAddRemoveReceiver =
|
||||
new SimpleBroadcastReceiver(this::onAppPackageChanged);
|
||||
|
||||
private final Object mModelLock = new Object();
|
||||
// Maps the action Id to the corresponding RemoteAction
|
||||
@@ -94,16 +98,23 @@ public final class WellbeingModel extends BgObjectWithLooper {
|
||||
private WellbeingModel(final Context context) {
|
||||
mContext = context;
|
||||
mWellbeingProviderPkg = mContext.getString(R.string.wellbeing_provider_pkg);
|
||||
initializeInBackground("WellbeingHandler");
|
||||
mWorkerHandler = new Handler(TextUtils.isEmpty(mWellbeingProviderPkg)
|
||||
? Executors.UI_HELPER_EXECUTOR.getLooper()
|
||||
: Executors.getPackageExecutor(mWellbeingProviderPkg).getLooper());
|
||||
|
||||
mContentObserver = new ContentObserver(mWorkerHandler) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
updateAllPackages();
|
||||
}
|
||||
};
|
||||
mWorkerHandler.post(this::initializeInBackground);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInitialized(Looper looper) {
|
||||
mWorkerHandler = new Handler(looper);
|
||||
mContentObserver = newContentObserver(mWorkerHandler, this::onWellbeingUriChanged);
|
||||
private void initializeInBackground() {
|
||||
if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
|
||||
mContext.registerReceiver(
|
||||
new SimpleBroadcastReceiver(t -> restartObserver()),
|
||||
mWellbeingAppChangeReceiver,
|
||||
getPackageFilter(mWellbeingProviderPkg,
|
||||
Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_CHANGED,
|
||||
Intent.ACTION_PACKAGE_REMOVED, Intent.ACTION_PACKAGE_DATA_CLEARED,
|
||||
@@ -113,17 +124,21 @@ public final class WellbeingModel extends BgObjectWithLooper {
|
||||
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
|
||||
filter.addDataScheme("package");
|
||||
mContext.registerReceiver(new SimpleBroadcastReceiver(this::onAppPackageChanged),
|
||||
filter, null, mWorkerHandler);
|
||||
mContext.registerReceiver(mAppAddRemoveReceiver, filter, null, mWorkerHandler);
|
||||
|
||||
restartObserver();
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void onWellbeingUriChanged(Uri uri) {
|
||||
Preconditions.assertNonUiThread();
|
||||
updateAllPackages();
|
||||
@Override
|
||||
public void close() {
|
||||
if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
|
||||
mWorkerHandler.post(() -> {
|
||||
mWellbeingAppChangeReceiver.unregisterReceiverSafely(mContext);
|
||||
mAppAddRemoveReceiver.unregisterReceiverSafely(mContext);
|
||||
mContext.getContentResolver().unregisterContentObserver(mContentObserver);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void setInTest(boolean inTest) {
|
||||
|
||||
@@ -39,6 +39,7 @@ import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
|
||||
import com.android.launcher3.util.DisplayController.Info;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.util.NavigationMode;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.quickstep.util.RecentsOrientedState;
|
||||
import com.android.systemui.shared.system.QuickStepContract;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
@@ -50,7 +51,7 @@ import java.util.ArrayList;
|
||||
/**
|
||||
* Helper class for transforming touch events
|
||||
*/
|
||||
public class RotationTouchHelper implements DisplayInfoChangeListener {
|
||||
public class RotationTouchHelper implements DisplayInfoChangeListener, SafeCloseable {
|
||||
|
||||
public static final MainThreadInitializedObject<RotationTouchHelper> INSTANCE =
|
||||
new MainThreadInitializedObject<>(RotationTouchHelper::new);
|
||||
@@ -197,6 +198,11 @@ public class RotationTouchHelper implements DisplayInfoChangeListener {
|
||||
mOnDestroyActions.add(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up all the registered listeners and receivers.
|
||||
*/
|
||||
|
||||
@@ -24,21 +24,29 @@ import android.view.MotionEvent;
|
||||
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
|
||||
public class SimpleOrientationTouchTransformer implements
|
||||
DisplayController.DisplayInfoChangeListener {
|
||||
DisplayController.DisplayInfoChangeListener, SafeCloseable {
|
||||
|
||||
public static final MainThreadInitializedObject<SimpleOrientationTouchTransformer> INSTANCE =
|
||||
new MainThreadInitializedObject<>(SimpleOrientationTouchTransformer::new);
|
||||
|
||||
private final Context mContext;
|
||||
private OrientationRectF mOrientationRectF;
|
||||
|
||||
public SimpleOrientationTouchTransformer(Context context) {
|
||||
mContext = context;
|
||||
DisplayController.INSTANCE.get(context).addChangeListener(this);
|
||||
onDisplayInfoChanged(context, DisplayController.INSTANCE.get(context).getInfo(),
|
||||
CHANGE_ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
DisplayController.INSTANCE.get(mContext).removeChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
|
||||
if ((flags & (CHANGE_ROTATION | CHANGE_ACTIVE_SCREEN)) == 0) {
|
||||
|
||||
@@ -65,6 +65,7 @@ import com.android.internal.util.ScreenshotRequest;
|
||||
import com.android.internal.view.AppearanceRegion;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.quickstep.util.ActiveGestureLog;
|
||||
import com.android.quickstep.util.AssistUtils;
|
||||
import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider;
|
||||
@@ -108,7 +109,7 @@ import java.util.List;
|
||||
/**
|
||||
* Holds the reference to SystemUI.
|
||||
*/
|
||||
public class SystemUiProxy implements ISystemUiProxy, NavHandle {
|
||||
public class SystemUiProxy implements ISystemUiProxy, NavHandle, SafeCloseable {
|
||||
private static final String TAG = SystemUiProxy.class.getSimpleName();
|
||||
|
||||
public static final MainThreadInitializedObject<SystemUiProxy> INSTANCE =
|
||||
@@ -198,6 +199,9 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
|
||||
? new ProxyUnfoldTransitionProvider() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() { }
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (mSystemUiProxy != null) {
|
||||
|
||||
@@ -59,6 +59,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
|
||||
public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
|
||||
&& SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
|
||||
|
||||
private final Context mCtx;
|
||||
private RecentsAnimationController mController;
|
||||
private RecentsAnimationCallbacks mCallbacks;
|
||||
private RecentsAnimationTargets mTargets;
|
||||
@@ -66,7 +67,6 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
|
||||
private GestureState mLastGestureState;
|
||||
private RemoteAnimationTarget[] mLastAppearedTaskTargets;
|
||||
private Runnable mLiveTileCleanUpHandler;
|
||||
private Context mCtx;
|
||||
|
||||
private boolean mRecentsAnimationStartPending = false;
|
||||
private boolean mShouldIgnoreMotionEvents = false;
|
||||
@@ -329,7 +329,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
|
||||
options.setTransientLaunch();
|
||||
}
|
||||
options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
|
||||
mRecentsAnimationStartPending = SystemUiProxy.INSTANCE.getNoCreate()
|
||||
mRecentsAnimationStartPending = SystemUiProxy.INSTANCE.get(mCtx)
|
||||
.startRecentsActivity(intent, options, mCallbacks);
|
||||
if (enableHandleDelayedGestureCallbacks()) {
|
||||
ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
|
||||
|
||||
@@ -34,6 +34,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitStageInfo;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
|
||||
@@ -57,7 +58,8 @@ import java.util.List;
|
||||
* This class tracked the top-most task and some 'approximate' task history to allow faster
|
||||
* system state estimation during touch interaction
|
||||
*/
|
||||
public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskStackChangeListener {
|
||||
public class TopTaskTracker extends ISplitScreenListener.Stub
|
||||
implements TaskStackChangeListener, SafeCloseable {
|
||||
|
||||
public static MainThreadInitializedObject<TopTaskTracker> INSTANCE =
|
||||
new MainThreadInitializedObject<>(TopTaskTracker::new);
|
||||
@@ -67,12 +69,13 @@ public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskSta
|
||||
// Ordered list with first item being the most recent task.
|
||||
private final LinkedList<RunningTaskInfo> mOrderedTaskList = new LinkedList<>();
|
||||
|
||||
|
||||
private final Context mContext;
|
||||
private final SplitStageInfo mMainStagePosition = new SplitStageInfo();
|
||||
private final SplitStageInfo mSideStagePosition = new SplitStageInfo();
|
||||
private int mPinnedTaskId = INVALID_TASK_ID;
|
||||
|
||||
private TopTaskTracker(Context context) {
|
||||
mContext = context;
|
||||
mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN;
|
||||
mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE;
|
||||
|
||||
@@ -80,6 +83,12 @@ public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskSta
|
||||
SystemUiProxy.INSTANCE.get(context).registerSplitScreenListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(this);
|
||||
SystemUiProxy.INSTANCE.get(mContext).unregisterSplitScreenListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskRemoved(int taskId) {
|
||||
mOrderedTaskList.removeIf(rto -> rto.taskId == taskId);
|
||||
|
||||
@@ -347,7 +347,6 @@ public class StatsLogCompatManager extends StatsLogManager {
|
||||
event.getId() + "";
|
||||
Log.d(TAG, name);
|
||||
}
|
||||
LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
|
||||
|
||||
if (mSlice == null && mSliceItem != null) {
|
||||
mSlice = LauncherAtom.Slice.newBuilder().setUri(
|
||||
@@ -369,15 +368,10 @@ public class StatsLogCompatManager extends StatsLogManager {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mItemInfo.container < 0 || appState == null) {
|
||||
// Write log on the model thread so that logs do not go out of order
|
||||
// (for eg: drop comes after drag)
|
||||
Executors.MODEL_EXECUTOR.execute(
|
||||
() -> write(event, applyOverwrites(mItemInfo.buildProto())));
|
||||
} else {
|
||||
if (mItemInfo.container < 0 || !LauncherAppState.INSTANCE.executeIfCreated(app -> {
|
||||
// Item is inside a collection, fetch collection info in a BG thread
|
||||
// and then write to StatsLog.
|
||||
appState.getModel().enqueueModelUpdateTask(
|
||||
app.getModel().enqueueModelUpdateTask(
|
||||
new BaseModelUpdateTask() {
|
||||
@Override
|
||||
public void execute(@NonNull final LauncherAppState app,
|
||||
@@ -388,6 +382,11 @@ public class StatsLogCompatManager extends StatsLogManager {
|
||||
write(event, applyOverwrites(mItemInfo.buildProto(collectionInfo)));
|
||||
}
|
||||
});
|
||||
})) {
|
||||
// Write log on the model thread so that logs do not go out of order
|
||||
// (for eg: drop comes after drag)
|
||||
Executors.MODEL_EXECUTOR.execute(
|
||||
() -> write(event, applyOverwrites(mItemInfo.buildProto())));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -316,7 +316,7 @@ public class AppPairsController {
|
||||
itemInfos.stream().map(ItemInfo::getComponentKey).toList();
|
||||
|
||||
// Use TopTaskTracker to find the currently running app (or apps)
|
||||
TopTaskTracker topTaskTracker = getTopTaskTracker(context);
|
||||
TopTaskTracker topTaskTracker = getTopTaskTracker();
|
||||
|
||||
// getRunningSplitTasksIds() will return a pair of ids if we are currently running a
|
||||
// split pair, or an empty array with zero length if we are running a single app.
|
||||
@@ -489,7 +489,7 @@ public class AppPairsController {
|
||||
* Gets the TopTaskTracker, which is a cached record of the top running Task.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public TopTaskTracker getTopTaskTracker(Context context) {
|
||||
return TopTaskTracker.INSTANCE.get(context);
|
||||
public TopTaskTracker getTopTaskTracker() {
|
||||
return TopTaskTracker.INSTANCE.get(mContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class SplitToWorkspaceController {
|
||||
SplitSelectStateController controller) {
|
||||
mLauncher = launcher;
|
||||
mController = controller;
|
||||
mIconCache = LauncherAppState.getInstanceNoCreate().getIconCache();
|
||||
mIconCache = LauncherAppState.getInstance(launcher).getIconCache();
|
||||
mHalfDividerSize = mLauncher.getResources().getDimensionPixelSize(
|
||||
R.dimen.multi_window_task_divider_size) / 2;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
|
||||
import static com.android.launcher3.BaseActivity.EVENT_RESUMED;
|
||||
import static com.android.launcher3.BaseActivity.EVENT_STOPPED;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.quickstep.RecentsModel;
|
||||
@@ -45,6 +47,12 @@ public class TaskRemovedDuringLaunchListener {
|
||||
private final Runnable mUnregisterCallback = this::unregister;
|
||||
private final Runnable mResumeCallback = this::checkTaskLaunchFailed;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
public TaskRemovedDuringLaunchListener(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a failure listener callback if it detects a scenario in which an app launch
|
||||
* failed before the transition finished.
|
||||
@@ -88,7 +96,7 @@ public class TaskRemovedDuringLaunchListener {
|
||||
if (mLaunchedTaskId != INVALID_TASK_ID) {
|
||||
final int launchedTaskId = mLaunchedTaskId;
|
||||
final Runnable taskLaunchFailedCallback = mTaskLaunchFailedCallback;
|
||||
RecentsModel.INSTANCE.getNoCreate().isTaskRemoved(mLaunchedTaskId, (taskRemoved) -> {
|
||||
RecentsModel.INSTANCE.get(mContext).isTaskRemoved(mLaunchedTaskId, (taskRemoved) -> {
|
||||
if (taskRemoved) {
|
||||
ActiveGestureLog.INSTANCE.addLog(
|
||||
new ActiveGestureLog.CompoundString("Launch failed, task (id=")
|
||||
|
||||
@@ -93,6 +93,7 @@ import com.android.launcher3.util.ActivityOptionsWrapper;
|
||||
import com.android.launcher3.util.CancellableTask;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.RunnableList;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
|
||||
import com.android.launcher3.util.TraceHelper;
|
||||
@@ -118,6 +119,8 @@ import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.QuickStepContract;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -126,8 +129,6 @@ import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
/**
|
||||
* A task in the Recents view.
|
||||
*/
|
||||
@@ -922,8 +923,8 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
TestLogging.recordEvent(
|
||||
TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
|
||||
|
||||
final TaskRemovedDuringLaunchListener
|
||||
failureListener = new TaskRemovedDuringLaunchListener();
|
||||
TaskRemovedDuringLaunchListener failureListener = new TaskRemovedDuringLaunchListener(
|
||||
getContext().getApplicationContext());
|
||||
if (isQuickswitch) {
|
||||
// We only listen for failures to launch in quickswitch because the during this
|
||||
// gesture launcher is in the background state, vs other launches which are in
|
||||
@@ -950,12 +951,8 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
// Indicate success once the system has indicated that the transition has started
|
||||
ActivityOptions opts = ActivityOptions.makeCustomTaskAnimation(getContext(), 0, 0,
|
||||
MAIN_EXECUTOR.getHandler(),
|
||||
elapsedRealTime -> {
|
||||
callback.accept(true);
|
||||
},
|
||||
elapsedRealTime -> {
|
||||
failureListener.onTransitionFinished();
|
||||
});
|
||||
elapsedRealTime -> callback.accept(true),
|
||||
elapsedRealTime -> failureListener.onTransitionFinished());
|
||||
opts.setLaunchDisplayId(
|
||||
getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
|
||||
if (isQuickswitch) {
|
||||
@@ -1857,7 +1854,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
/**
|
||||
* We update and subsequently draw these in {@link #setFullscreenProgress(float)}.
|
||||
*/
|
||||
public static class FullscreenDrawParams {
|
||||
public static class FullscreenDrawParams implements SafeCloseable {
|
||||
|
||||
private float mCornerRadius;
|
||||
private float mWindowCornerRadius;
|
||||
@@ -1892,6 +1889,9 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
Utilities.mapRange(fullscreenProgress, mCornerRadius, mWindowCornerRadius)
|
||||
/ parentScale / taskViewScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() { }
|
||||
}
|
||||
|
||||
public class TaskIdAttributeContainer {
|
||||
|
||||
-2
@@ -44,7 +44,6 @@ import com.android.launcher3.util.WindowBounds;
|
||||
import com.android.launcher3.util.window.CachedDisplayInfo;
|
||||
import com.android.launcher3.util.window.WindowManagerProxy;
|
||||
import com.android.quickstep.FallbackActivityInterface;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.util.SurfaceTransaction.MockProperties;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
@@ -160,7 +159,6 @@ public class TaskViewSimulatorTest {
|
||||
void verifyNoTransforms() {
|
||||
LauncherModelHelper helper = new LauncherModelHelper();
|
||||
try {
|
||||
helper.sandboxContext.allow(SystemUiProxy.INSTANCE);
|
||||
int rotation = mDisplaySize.x > mDisplaySize.y
|
||||
? Surface.ROTATION_90 : Surface.ROTATION_0;
|
||||
CachedDisplayInfo cdi = new CachedDisplayInfo(mDisplaySize, rotation);
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static androidx.test.InstrumentationRegistry.getTargetContext;
|
||||
|
||||
import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
|
||||
import static com.android.quickstep.TaplTestsTaskbar.TaskbarMode.PERSISTENT;
|
||||
import static com.android.quickstep.TaplTestsTaskbar.TaskbarMode.TRANSIENT;
|
||||
@@ -53,7 +55,7 @@ public class TaplTestsTaskbar extends AbstractTaplTestsTaskbar {
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
mTaskbarWasInTransientMode = isTaskbarInTransientMode(mTargetContext);
|
||||
mTaskbarWasInTransientMode = isTaskbarInTransientMode(getTargetContext());
|
||||
setTaskbarMode(mLauncher, isTaskbarTestModeTransient());
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import org.mockito.MockitoAnnotations
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.anyOrNull
|
||||
import org.mockito.kotlin.doNothing
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.eq
|
||||
import org.mockito.kotlin.never
|
||||
import org.mockito.kotlin.spy
|
||||
@@ -100,8 +101,7 @@ class AppPairsControllerTest {
|
||||
// Stub methods on appPairsController so that they return mocks
|
||||
spyAppPairsController = spy(appPairsController)
|
||||
whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext)
|
||||
whenever(spyAppPairsController.getTopTaskTracker(mockTaskbarActivityContext))
|
||||
.thenReturn(mockTopTaskTracker)
|
||||
doReturn(mockTopTaskTracker).whenever(spyAppPairsController).topTaskTracker
|
||||
whenever(mockTopTaskTracker.getCachedTopTask(any())).thenReturn(mockCachedTaskInfo)
|
||||
whenever(mockTask1.getKey()).thenReturn(mockTaskKey1)
|
||||
whenever(mockTask2.getKey()).thenReturn(mockTaskKey2)
|
||||
|
||||
@@ -59,6 +59,7 @@ import com.android.launcher3.util.DisplayController.Info;
|
||||
import com.android.launcher3.util.LockedUserState;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.util.Partner;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.util.WindowBounds;
|
||||
import com.android.launcher3.util.window.WindowManagerProxy;
|
||||
|
||||
@@ -75,7 +76,7 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class InvariantDeviceProfile {
|
||||
public class InvariantDeviceProfile implements SafeCloseable {
|
||||
|
||||
public static final String TAG = "IDP";
|
||||
// We do not need any synchronization for this variable as its only written on UI thread.
|
||||
@@ -229,9 +230,8 @@ public class InvariantDeviceProfile {
|
||||
if (!newGridName.equals(gridName)) {
|
||||
LauncherPrefs.get(context).put(GRID_NAME, newGridName);
|
||||
}
|
||||
LockedUserState.get(context).runOnUserUnlocked(() -> {
|
||||
new DeviceGridState(this).writeToPrefs(context);
|
||||
});
|
||||
LockedUserState.get(context).runOnUserUnlocked(() ->
|
||||
new DeviceGridState(this).writeToPrefs(context));
|
||||
|
||||
DisplayController.INSTANCE.get(context).setPriorityListener(
|
||||
(displayContext, info, flags) -> {
|
||||
@@ -295,6 +295,11 @@ public class InvariantDeviceProfile {
|
||||
initGrid(context, myInfo, result, deviceType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
DisplayController.INSTANCE.executeIfCreated(dc -> dc.setPriorityListener(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinitialize the current grid after a restore, where some grids might now be disabled.
|
||||
*/
|
||||
|
||||
@@ -78,14 +78,10 @@ public class LauncherAppState implements SafeCloseable {
|
||||
|
||||
private final RunnableList mOnTerminateCallback = new RunnableList();
|
||||
|
||||
public static LauncherAppState getInstance(final Context context) {
|
||||
public static LauncherAppState getInstance(Context context) {
|
||||
return INSTANCE.get(context);
|
||||
}
|
||||
|
||||
public static LauncherAppState getInstanceNoCreate() {
|
||||
return INSTANCE.getNoCreate();
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.android.launcher3.provider.RestoreDbTask.FIRST_LOAD_AFTER_RESTORE_KEY
|
||||
import com.android.launcher3.states.RotationHelper
|
||||
import com.android.launcher3.util.DisplayController
|
||||
import com.android.launcher3.util.MainThreadInitializedObject
|
||||
import com.android.launcher3.util.SafeCloseable
|
||||
import com.android.launcher3.util.Themes
|
||||
|
||||
/**
|
||||
@@ -37,7 +38,7 @@ import com.android.launcher3.util.Themes
|
||||
*
|
||||
* TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
|
||||
*/
|
||||
class LauncherPrefs(private val encryptedContext: Context) {
|
||||
class LauncherPrefs(private val encryptedContext: Context) : SafeCloseable {
|
||||
private val deviceProtectedStorageContext =
|
||||
encryptedContext.createDeviceProtectedStorageContext()
|
||||
|
||||
@@ -242,6 +243,8 @@ class LauncherPrefs(private val encryptedContext: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {}
|
||||
|
||||
companion object {
|
||||
@VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
|
||||
|
||||
|
||||
@@ -48,11 +48,11 @@ public class LauncherProvider extends ContentProvider {
|
||||
*/
|
||||
@Override
|
||||
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
|
||||
LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
|
||||
if (appState == null || !appState.getModel().isModelLoaded()) {
|
||||
return;
|
||||
}
|
||||
appState.getModel().dumpState("", fd, writer, args);
|
||||
LauncherAppState.INSTANCE.executeIfCreated(appState -> {
|
||||
if (appState.getModel().isModelLoaded()) {
|
||||
appState.getModel().dumpState("", fd, writer, args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -66,7 +66,6 @@ import com.android.launcher3.Hotseat;
|
||||
import com.android.launcher3.InsettableFrameLayout;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
@@ -88,14 +87,11 @@ import com.android.launcher3.model.data.FolderInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.pm.InstallSessionHelper;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
import com.android.launcher3.util.IntSet;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
|
||||
import com.android.launcher3.util.PluginManagerWrapper;
|
||||
import com.android.launcher3.util.WindowBounds;
|
||||
import com.android.launcher3.util.window.WindowManagerProxy;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
@@ -104,7 +100,6 @@ import com.android.launcher3.widget.BaseLauncherAppWidgetHostView;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.widget.LauncherWidgetHolder;
|
||||
import com.android.launcher3.widget.LocalColorExtractor;
|
||||
import com.android.launcher3.widget.custom.CustomWidgetManager;
|
||||
import com.android.launcher3.widget.util.WidgetSizes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -136,13 +131,10 @@ public class LauncherPreviewRenderer extends ContextWrapper
|
||||
new ConcurrentLinkedQueue<>();
|
||||
|
||||
public PreviewContext(Context base, InvariantDeviceProfile idp) {
|
||||
super(base, UserCache.INSTANCE, InstallSessionHelper.INSTANCE, LauncherPrefs.INSTANCE,
|
||||
LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
|
||||
CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE,
|
||||
WindowManagerProxy.INSTANCE, DisplayController.INSTANCE);
|
||||
super(base);
|
||||
mIdp = idp;
|
||||
mObjectMap.put(InvariantDeviceProfile.INSTANCE, idp);
|
||||
mObjectMap.put(LauncherAppState.INSTANCE,
|
||||
putObject(InvariantDeviceProfile.INSTANCE, idp);
|
||||
putObject(LauncherAppState.INSTANCE,
|
||||
new LauncherAppState(this, null /* iconCacheFileName */));
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.logging.FileLog;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
@@ -55,6 +54,7 @@ import com.android.launcher3.shortcuts.ShortcutRequest;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.util.PersistedItemArray;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
|
||||
import java.util.HashSet;
|
||||
@@ -65,7 +65,7 @@ import java.util.stream.Stream;
|
||||
/**
|
||||
* Class to maintain a queue of pending items to be added to the workspace.
|
||||
*/
|
||||
public class ItemInstallQueue {
|
||||
public class ItemInstallQueue implements SafeCloseable {
|
||||
|
||||
private static final String LOG = "ItemInstallQueue";
|
||||
|
||||
@@ -99,6 +99,9 @@ public class ItemInstallQueue {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {}
|
||||
|
||||
@WorkerThread
|
||||
private void ensureQueueLoaded() {
|
||||
Preconditions.assertWorkerThread();
|
||||
|
||||
@@ -427,12 +427,10 @@ public class ItemInfo {
|
||||
@NonNull
|
||||
protected LauncherAtom.ItemInfo.Builder getDefaultItemInfoBuilder() {
|
||||
LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
|
||||
UserIconInfo info = getUserInfo();
|
||||
itemBuilder.setIsWork(info != null && info.isWork());
|
||||
itemBuilder.setUserType(getUserType(info));
|
||||
SettingsCache settingsCache = SettingsCache.INSTANCE.getNoCreate();
|
||||
boolean isKidsMode = settingsCache != null && settingsCache.getValue(NAV_BAR_KIDS_MODE, 0);
|
||||
itemBuilder.setIsKidsMode(isKidsMode);
|
||||
SettingsCache.INSTANCE.executeIfCreated(cache ->
|
||||
itemBuilder.setIsKidsMode(cache.getValue(NAV_BAR_KIDS_MODE, 0)));
|
||||
UserCache.INSTANCE.executeIfCreated(cache ->
|
||||
itemBuilder.setUserType(getUserType(cache.getUserInfo(user))));
|
||||
itemBuilder.setRank(rank);
|
||||
return itemBuilder;
|
||||
}
|
||||
@@ -526,15 +524,6 @@ public class ItemInfo {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
private UserIconInfo getUserInfo() {
|
||||
UserCache userCache = UserCache.INSTANCE.getNoCreate();
|
||||
if (userCache == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return userCache.getUserInfo(user);
|
||||
}
|
||||
|
||||
private int getUserType(UserIconInfo info) {
|
||||
if (info == null) {
|
||||
return SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_UNKNOWN;
|
||||
|
||||
@@ -32,7 +32,6 @@ import androidx.annotation.WorkerThread;
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.SessionCommitReceiver;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.logging.FileLog;
|
||||
import com.android.launcher3.model.ItemInstallQueue;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
@@ -41,6 +40,7 @@ import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.util.PackageManagerHelper;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -52,7 +52,7 @@ import java.util.Objects;
|
||||
* Utility class to tracking install sessions
|
||||
*/
|
||||
@SuppressWarnings("NewApi")
|
||||
public class InstallSessionHelper {
|
||||
public class InstallSessionHelper implements SafeCloseable {
|
||||
|
||||
@NonNull
|
||||
private static final String LOG = "InstallSessionHelper";
|
||||
@@ -89,6 +89,9 @@ public class InstallSessionHelper {
|
||||
mLauncherApps = context.getSystemService(LauncherApps.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() { }
|
||||
|
||||
@WorkerThread
|
||||
@NonNull
|
||||
private IntSet getPromiseIconIds() {
|
||||
|
||||
@@ -526,10 +526,7 @@ public class RestoreDbTask {
|
||||
}
|
||||
|
||||
logFavoritesTable(controller.getDb(), "launcher db after remap widget ids", null, null);
|
||||
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
|
||||
if (app != null) {
|
||||
app.getModel().forceReload();
|
||||
}
|
||||
LauncherAppState.INSTANCE.executeIfCreated(app -> app.getModel().forceReload());
|
||||
}
|
||||
|
||||
private static void logDatabaseWidgetInfo(ModelDbController controller) {
|
||||
|
||||
@@ -94,13 +94,10 @@ public class TestInformationHandler implements ResourceBasedOverride {
|
||||
|
||||
protected Context mContext;
|
||||
protected DeviceProfile mDeviceProfile;
|
||||
protected LauncherAppState mLauncherAppState;
|
||||
|
||||
public void init(Context context) {
|
||||
mContext = context;
|
||||
mDeviceProfile = InvariantDeviceProfile.INSTANCE.
|
||||
get(context).getDeviceProfile(context);
|
||||
mLauncherAppState = LauncherAppState.getInstanceNoCreate();
|
||||
mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context);
|
||||
if (sActivityLifecycleCallbacks == null) {
|
||||
sActivityLifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
|
||||
@Override
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* 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.launcher3.util;
|
||||
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Utility class to define an object which does most of it's processing on a
|
||||
* dedicated background thread.
|
||||
*/
|
||||
public abstract class BgObjectWithLooper {
|
||||
|
||||
/**
|
||||
* Start initialization of the object
|
||||
*/
|
||||
public final void initializeInBackground(String threadName) {
|
||||
new Thread(this::runOnThread, threadName).start();
|
||||
}
|
||||
|
||||
private void runOnThread() {
|
||||
Looper.prepare();
|
||||
onInitialized(Looper.myLooper());
|
||||
Looper.loop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on the background thread to handle initialization
|
||||
*/
|
||||
@WorkerThread
|
||||
protected abstract void onInitialized(Looper looper);
|
||||
|
||||
/**
|
||||
* Helper method to create a content provider
|
||||
*/
|
||||
protected static ContentObserver newContentObserver(Handler handler, Consumer<Uri> command) {
|
||||
return new ContentObserver(handler) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
command.accept(uri);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,8 @@ import com.android.systemui.plugins.ResourceProvider;
|
||||
*
|
||||
* To allow customization for a particular resource, add them to dynamic_resources.xml
|
||||
*/
|
||||
public class DynamicResource implements ResourceProvider, PluginListener<ResourceProvider> {
|
||||
public class DynamicResource implements
|
||||
ResourceProvider, PluginListener<ResourceProvider>, SafeCloseable {
|
||||
|
||||
private static final MainThreadInitializedObject<DynamicResource> INSTANCE =
|
||||
new MainThreadInitializedObject<>(DynamicResource::new);
|
||||
@@ -47,6 +48,11 @@ public class DynamicResource implements ResourceProvider, PluginListener<Resourc
|
||||
ResourceProvider.class, false /* allowedMultiple */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
PluginManagerWrapper.INSTANCE.get(mContext).removePluginListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(@IntegerRes int resId) {
|
||||
return mContext.getResources().getInteger(resId);
|
||||
|
||||
@@ -28,17 +28,15 @@ import androidx.annotation.VisibleForTesting;
|
||||
import com.android.launcher3.util.ResourceBasedOverride.Overrides;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Utility class for defining singletons which are initiated on main thread.
|
||||
*/
|
||||
public class MainThreadInitializedObject<T> {
|
||||
public class MainThreadInitializedObject<T extends SafeCloseable> {
|
||||
|
||||
private final ObjectProvider<T> mProvider;
|
||||
private T mValue;
|
||||
@@ -48,14 +46,14 @@ public class MainThreadInitializedObject<T> {
|
||||
}
|
||||
|
||||
public T get(Context context) {
|
||||
if (context instanceof SandboxContext sc) {
|
||||
Context app = context.getApplicationContext();
|
||||
if (app instanceof SandboxApplication sc) {
|
||||
return sc.getObject(this);
|
||||
}
|
||||
|
||||
if (mValue == null) {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
mValue = TraceHelper.allowIpcs("main.thread.object",
|
||||
() -> mProvider.get(context.getApplicationContext()));
|
||||
mValue = TraceHelper.allowIpcs("main.thread.object", () -> mProvider.get(app));
|
||||
} else {
|
||||
try {
|
||||
return MAIN_EXECUTOR.submit(() -> get(context)).get();
|
||||
@@ -67,8 +65,18 @@ public class MainThreadInitializedObject<T> {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public T getNoCreate() {
|
||||
return mValue;
|
||||
/**
|
||||
* Executes the callback is the value is already created
|
||||
* @return true if the callback was executed, false otherwise
|
||||
*/
|
||||
public boolean executeIfCreated(Consumer<T> callback) {
|
||||
T v = mValue;
|
||||
if (v != null) {
|
||||
callback.accept(v);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -79,8 +87,8 @@ public class MainThreadInitializedObject<T> {
|
||||
/**
|
||||
* Initializes a provider based on resource overrides
|
||||
*/
|
||||
public static <T extends ResourceBasedOverride> MainThreadInitializedObject<T> forOverride(
|
||||
Class<T> clazz, int resourceId) {
|
||||
public static <T extends ResourceBasedOverride & SafeCloseable> MainThreadInitializedObject<T>
|
||||
forOverride(Class<T> clazz, int resourceId) {
|
||||
return new MainThreadInitializedObject<>(c -> Overrides.getObject(clazz, c, resourceId));
|
||||
}
|
||||
|
||||
@@ -89,24 +97,36 @@ public class MainThreadInitializedObject<T> {
|
||||
T get(Context context);
|
||||
}
|
||||
|
||||
public interface SandboxApplication {
|
||||
|
||||
/**
|
||||
* Find a cached object from mObjectMap if we have already created one. If not, generate
|
||||
* an object using the provider.
|
||||
*/
|
||||
<T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object);
|
||||
|
||||
@UiThread
|
||||
default <T extends SafeCloseable> T createObject(MainThreadInitializedObject<T> object) {
|
||||
return object.mProvider.get((Context) this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract Context which allows custom implementations for
|
||||
* {@link MainThreadInitializedObject} providers
|
||||
*/
|
||||
public static class SandboxContext extends ContextWrapper {
|
||||
public static class SandboxContext extends ContextWrapper implements SandboxApplication {
|
||||
|
||||
private static final String TAG = "SandboxContext";
|
||||
|
||||
protected final Set<MainThreadInitializedObject> mAllowedObjects;
|
||||
protected final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>();
|
||||
protected final ArrayList<Object> mOrderedObjects = new ArrayList<>();
|
||||
private final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>();
|
||||
private final ArrayList<SafeCloseable> mOrderedObjects = new ArrayList<>();
|
||||
|
||||
private final Object mDestroyLock = new Object();
|
||||
private boolean mDestroyed = false;
|
||||
|
||||
public SandboxContext(Context base, MainThreadInitializedObject... allowedObjects) {
|
||||
public SandboxContext(Context base) {
|
||||
super(base);
|
||||
mAllowedObjects = new HashSet<>(Arrays.asList(allowedObjects));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -118,20 +138,14 @@ public class MainThreadInitializedObject<T> {
|
||||
synchronized (mDestroyLock) {
|
||||
// Destroy in reverse order
|
||||
for (int i = mOrderedObjects.size() - 1; i >= 0; i--) {
|
||||
Object o = mOrderedObjects.get(i);
|
||||
if (o instanceof SafeCloseable) {
|
||||
((SafeCloseable) o).close();
|
||||
}
|
||||
mOrderedObjects.get(i).close();
|
||||
}
|
||||
mDestroyed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a cached object from mObjectMap if we have already created one. If not, generate
|
||||
* an object using the provider.
|
||||
*/
|
||||
protected <T> T getObject(MainThreadInitializedObject<T> object) {
|
||||
@Override
|
||||
public <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object) {
|
||||
synchronized (mDestroyLock) {
|
||||
if (mDestroyed) {
|
||||
Log.e(TAG, "Static object access with a destroyed context");
|
||||
@@ -142,12 +156,6 @@ public class MainThreadInitializedObject<T> {
|
||||
}
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
t = createObject(object);
|
||||
// Check if we've explicitly allowed the object or if it's a SafeCloseable,
|
||||
// it will get destroyed in onDestroy()
|
||||
if (!mAllowedObjects.contains(object) && !(t instanceof SafeCloseable)) {
|
||||
throw new IllegalStateException("Leaking unknown objects "
|
||||
+ object + " " + object.mProvider + " " + t);
|
||||
}
|
||||
mObjectMap.put(object, t);
|
||||
mOrderedObjects.add(t);
|
||||
return t;
|
||||
@@ -161,17 +169,12 @@ public class MainThreadInitializedObject<T> {
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
protected <T> T createObject(MainThreadInitializedObject<T> object) {
|
||||
return object.mProvider.get(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a value into mObjectMap, can be used to put mocked MainThreadInitializedObject
|
||||
* instances into SandboxContext.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public <T> void putObject(MainThreadInitializedObject<T> object, T value) {
|
||||
public <T extends SafeCloseable> void putObject(
|
||||
MainThreadInitializedObject<T> object, T value) {
|
||||
mObjectMap.put(object, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
/**
|
||||
* Utility class for tracking if the screen is currently on or off
|
||||
*/
|
||||
public class ScreenOnTracker {
|
||||
public class ScreenOnTracker implements SafeCloseable {
|
||||
|
||||
public static final MainThreadInitializedObject<ScreenOnTracker> INSTANCE =
|
||||
new MainThreadInitializedObject<>(ScreenOnTracker::new);
|
||||
@@ -35,14 +35,21 @@ public class ScreenOnTracker {
|
||||
private final SimpleBroadcastReceiver mReceiver = new SimpleBroadcastReceiver(this::onReceive);
|
||||
private final CopyOnWriteArrayList<ScreenOnListener> mListeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
private final Context mContext;
|
||||
private boolean mIsScreenOn;
|
||||
|
||||
private ScreenOnTracker(Context context) {
|
||||
// Assume that the screen is on to begin with
|
||||
mContext = context;
|
||||
mIsScreenOn = true;
|
||||
mReceiver.register(context, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
mReceiver.unregisterReceiverSafely(mContext);
|
||||
}
|
||||
|
||||
private void onReceive(Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (ACTION_SCREEN_ON.equals(action)) {
|
||||
|
||||
@@ -39,7 +39,7 @@ import com.android.launcher3.Utilities;
|
||||
/**
|
||||
* Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
|
||||
*/
|
||||
public class VibratorWrapper {
|
||||
public class VibratorWrapper implements SafeCloseable {
|
||||
|
||||
public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE =
|
||||
new MainThreadInitializedObject<>(VibratorWrapper::new);
|
||||
@@ -77,6 +77,7 @@ public class VibratorWrapper {
|
||||
private final Vibrator mVibrator;
|
||||
private final boolean mHasVibrator;
|
||||
|
||||
private ContentObserver mHapticFeedbackObserver;
|
||||
private boolean mIsHapticFeedbackEnabled;
|
||||
|
||||
private VibratorWrapper(Context context) {
|
||||
@@ -86,14 +87,14 @@ public class VibratorWrapper {
|
||||
if (mHasVibrator) {
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver);
|
||||
final ContentObserver observer = new ContentObserver(MAIN_EXECUTOR.getHandler()) {
|
||||
mHapticFeedbackObserver = new ContentObserver(MAIN_EXECUTOR.getHandler()) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver);
|
||||
}
|
||||
};
|
||||
resolver.registerContentObserver(Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED),
|
||||
false /* notifyForDescendants */, observer);
|
||||
false /* notifyForDescendants */, mHapticFeedbackObserver);
|
||||
} else {
|
||||
mIsHapticFeedbackEnabled = false;
|
||||
}
|
||||
@@ -126,6 +127,13 @@ public class VibratorWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (mHapticFeedbackObserver != null) {
|
||||
mContext.getContentResolver().unregisterContentObserver(mHapticFeedbackObserver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when the user swipes to/from all apps. This is meant to be used in between
|
||||
* long animation progresses so that it gives a dragging texture effect. For a better
|
||||
|
||||
@@ -59,6 +59,7 @@ import com.android.launcher3.testing.shared.ResourceUtils;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.util.NavigationMode;
|
||||
import com.android.launcher3.util.ResourceBasedOverride;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.util.WindowBounds;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -67,7 +68,7 @@ import java.util.List;
|
||||
/**
|
||||
* Utility class for mocking some window manager behaviours
|
||||
*/
|
||||
public class WindowManagerProxy implements ResourceBasedOverride {
|
||||
public class WindowManagerProxy implements ResourceBasedOverride, SafeCloseable {
|
||||
|
||||
private static final String TAG = "WindowManagerProxy";
|
||||
public static final int MIN_TABLET_WIDTH = 600;
|
||||
@@ -305,12 +306,12 @@ public class WindowManagerProxy implements ResourceBasedOverride {
|
||||
|
||||
navBarHeightPortrait = isTablet
|
||||
? (mTaskbarDrawnInProcess
|
||||
? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
|
||||
? 0 : context.getResources().getDimensionPixelSize(R.dimen.taskbar_size))
|
||||
: getDimenByName(systemRes, NAVBAR_HEIGHT);
|
||||
|
||||
navBarHeightLandscape = isTablet
|
||||
? (mTaskbarDrawnInProcess
|
||||
? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
|
||||
? 0 : context.getResources().getDimensionPixelSize(R.dimen.taskbar_size))
|
||||
: (isTabletOrGesture
|
||||
? getDimenByName(systemRes, NAVBAR_HEIGHT_LANDSCAPE) : 0);
|
||||
navbarWidthLandscape = isTabletOrGesture
|
||||
@@ -474,6 +475,9 @@ public class WindowManagerProxy implements ResourceBasedOverride {
|
||||
NavigationMode.THREE_BUTTONS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() { }
|
||||
|
||||
/**
|
||||
* @see DisplayCutout#getSafeInsets
|
||||
*/
|
||||
|
||||
@@ -52,17 +52,11 @@ import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.LauncherModel.ModelUpdateTask;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.model.AllAppsList;
|
||||
import com.android.launcher3.model.BgDataModel;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
import com.android.launcher3.model.ItemInstallQueue;
|
||||
import com.android.launcher3.pm.InstallSessionHelper;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
import com.android.launcher3.testing.TestInformationProvider;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
|
||||
import com.android.launcher3.util.window.WindowManagerProxy;
|
||||
import com.android.launcher3.widget.custom.CustomWidgetManager;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
@@ -233,13 +227,7 @@ public class LauncherModelHelper {
|
||||
private final File mDbDir;
|
||||
|
||||
public SandboxModelContext() {
|
||||
super(ApplicationProvider.getApplicationContext(),
|
||||
UserCache.INSTANCE, InstallSessionHelper.INSTANCE, LauncherPrefs.INSTANCE,
|
||||
LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
|
||||
DisplayController.INSTANCE, CustomWidgetManager.INSTANCE,
|
||||
SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE,
|
||||
LockedUserState.INSTANCE, WallpaperColorHints.INSTANCE,
|
||||
ItemInstallQueue.INSTANCE, WindowManagerProxy.INSTANCE);
|
||||
super(ApplicationProvider.getApplicationContext());
|
||||
|
||||
// System settings cache content provider. Ensure that they are statically initialized
|
||||
Settings.Secure.getString(
|
||||
@@ -254,18 +242,13 @@ public class LauncherModelHelper {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> T createObject(MainThreadInitializedObject<T> object) {
|
||||
public <T extends SafeCloseable> T createObject(MainThreadInitializedObject<T> object) {
|
||||
if (object == LauncherAppState.INSTANCE) {
|
||||
return (T) new LauncherAppState(this, null /* iconCacheFileName */);
|
||||
}
|
||||
return super.createObject(object);
|
||||
}
|
||||
|
||||
public SandboxModelContext allow(MainThreadInitializedObject object) {
|
||||
mAllowedObjects.add(object);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDatabasePath(String name) {
|
||||
if (!mDbDir.exists()) {
|
||||
|
||||
@@ -300,13 +300,7 @@ abstract class AbstractDeviceProfileTest {
|
||||
smallestScreenWidthDp = min(screenWidthDp, screenHeightDp)
|
||||
}
|
||||
val configurationContext = runningContext.createConfigurationContext(config)
|
||||
context =
|
||||
SandboxContext(
|
||||
configurationContext,
|
||||
DisplayController.INSTANCE,
|
||||
WindowManagerProxy.INSTANCE,
|
||||
LauncherPrefs.INSTANCE
|
||||
)
|
||||
context = SandboxContext(configurationContext)
|
||||
context.putObject(DisplayController.INSTANCE, displayController)
|
||||
context.putObject(WindowManagerProxy.INSTANCE, windowManagerProxy)
|
||||
context.putObject(LauncherPrefs.INSTANCE, launcherPrefs)
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.anyOrNull
|
||||
import org.mockito.kotlin.doNothing
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.eq
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.verify
|
||||
@@ -123,6 +124,7 @@ class DisplayControllerTest {
|
||||
whenever(displayManager.getDisplay(any())).thenReturn(display)
|
||||
|
||||
// Mock resources
|
||||
doReturn(context).whenever(context).applicationContext
|
||||
whenever(resources.configuration).thenReturn(configuration)
|
||||
whenever(context.resources).thenReturn(resources)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user