/* * 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.statemanager; import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE; import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE; import android.content.res.Configuration; import android.os.Bundle; import android.os.Handler; import android.view.LayoutInflater; import android.view.View; import androidx.annotation.CallSuper; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.LauncherRootView; import com.android.launcher3.Utilities; import com.android.launcher3.statemanager.StateManager.StateHandler; import com.android.launcher3.util.window.WindowManagerProxy; import com.android.launcher3.views.BaseDragLayer; import java.util.List; /** * Abstract activity with state management * @param Type of state object */ public abstract class StatefulActivity> extends BaseDraggingActivity implements StatefulContainer { public final Handler mHandler = new Handler(); private final Runnable mHandleDeferredResume = this::handleDeferredResume; private boolean mDeferredResumePending; private LauncherRootView mRootView; protected Configuration mOldConfig; private int mOldRotation; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mOldConfig = new Configuration(getResources().getConfiguration()); mOldRotation = WindowManagerProxy.INSTANCE.get(this).getRotation(this); } /** * Create handlers to control the property changes for this activity */ @Override public abstract void collectStateHandlers(List> out); protected void inflateRootView(int layoutId) { mRootView = (LauncherRootView) LayoutInflater.from(this).inflate(layoutId, null); mRootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); } @Override public final LauncherRootView getRootView() { return mRootView; } @Override public T findViewById(int id) { return mRootView.findViewById(id); } /** * Called when transition to the state starts */ @CallSuper public void onStateSetStart(STATE_TYPE state) { if (mDeferredResumePending) { handleDeferredResume(); } StatefulContainer.super.onStateSetStart(state); } @Override public boolean shouldAnimateStateChange() { return !isForceInvisible() && isStarted(); } @Override public void reapplyUi() { getRootView().dispatchInsets(); getStateManager().reapplyState(true /* cancelCurrentAnimation */); } @Override protected void onStop() { BaseDragLayer dragLayer = getDragLayer(); final boolean wasActive = isUserActive(); final STATE_TYPE origState = getStateManager().getState(); final int origDragLayerChildCount = dragLayer.getChildCount(); super.onStop(); if (!isChangingConfigurations()) { getStateManager().moveToRestState(); } // Workaround for b/78520668, explicitly trim memory once UI is hidden onTrimMemory(TRIM_MEMORY_UI_HIDDEN); if (wasActive) { // The expected condition is that this activity is stopped because the device goes to // sleep and the UI may have noticeable changes. dragLayer.post(() -> { if ((!getStateManager().isInStableState(origState) // The drag layer may be animating (e.g. dismissing QSB). || dragLayer.getAlpha() < 1 // Maybe an ArrowPopup is closed. || dragLayer.getChildCount() != origDragLayerChildCount)) { onUiChangedWhileSleeping(); } }); } } /** * Called if the Activity UI changed while the activity was not visible */ public void onUiChangedWhileSleeping() { } private void handleDeferredResume() { if (hasBeenResumed() && !getStateManager().getState().hasFlag(FLAG_NON_INTERACTIVE)) { addActivityFlags(ACTIVITY_STATE_DEFERRED_RESUMED); onDeferredResumed(); mDeferredResumePending = false; } else { mDeferredResumePending = true; } } /** * Called want the activity has stayed resumed for 1 frame. */ protected void onDeferredResumed() { } @Override protected void onResume() { super.onResume(); mHandler.removeCallbacks(mHandleDeferredResume); Utilities.postAsyncCallback(mHandler, mHandleDeferredResume); } /** * Runs the given {@param r} runnable when this activity binds to the touch interaction service. */ public void runOnBindToTouchInteractionService(Runnable r) { r.run(); } @Override public void onConfigurationChanged(Configuration newConfig) { handleConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig); } /** * Handles configuration change when system calls {@link #onConfigurationChanged}, or on other * situations that configuration might change. */ public void handleConfigurationChanged(Configuration newConfig) { int diff = newConfig.diff(mOldConfig); int rotation = WindowManagerProxy.INSTANCE.get(this).getRotation(this); if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0 || rotation != mOldRotation) { onHandleConfigurationChanged(); } mOldConfig.setTo(newConfig); mOldRotation = rotation; } /** * Logic for when device configuration changes (rotation, screen size change, multi-window, * etc.) */ protected abstract void onHandleConfigurationChanged(); /** * Enter staged split directly from the current running app. * @param leftOrTop if the staged split will be positioned left or top. */ public void enterStageSplitFromRunningApp(boolean leftOrTop) { } }