1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.statemanager; 17 18 import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE; 19 20 import android.os.Handler; 21 import android.view.LayoutInflater; 22 import android.view.View; 23 24 import androidx.annotation.CallSuper; 25 26 import com.android.launcher3.BaseDraggingActivity; 27 import com.android.launcher3.LauncherRootView; 28 import com.android.launcher3.Utilities; 29 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory; 30 import com.android.launcher3.statemanager.StateManager.StateHandler; 31 import com.android.launcher3.views.BaseDragLayer; 32 33 /** 34 * Abstract activity with state management 35 * @param <STATE_TYPE> Type of state object 36 */ 37 public abstract class StatefulActivity<STATE_TYPE extends BaseState<STATE_TYPE>> 38 extends BaseDraggingActivity { 39 40 public final Handler mHandler = new Handler(); 41 private final Runnable mHandleDeferredResume = this::handleDeferredResume; 42 private boolean mDeferredResumePending; 43 44 private LauncherRootView mRootView; 45 46 /** 47 * Create handlers to control the property changes for this activity 48 */ createStateHandlers()49 protected abstract StateHandler<STATE_TYPE>[] createStateHandlers(); 50 51 /** 52 * Returns true if the activity is in the provided state 53 */ isInState(STATE_TYPE state)54 public boolean isInState(STATE_TYPE state) { 55 return getStateManager().getState() == state; 56 } 57 58 /** 59 * Returns the state manager for this activity 60 */ getStateManager()61 public abstract StateManager<STATE_TYPE> getStateManager(); 62 inflateRootView(int layoutId)63 protected void inflateRootView(int layoutId) { 64 mRootView = (LauncherRootView) LayoutInflater.from(this).inflate(layoutId, null); 65 mRootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 66 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 67 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 68 } 69 70 @Override getRootView()71 public final LauncherRootView getRootView() { 72 return mRootView; 73 } 74 75 @Override findViewById(int id)76 public <T extends View> T findViewById(int id) { 77 return mRootView.findViewById(id); 78 } 79 80 /** 81 * Called when transition to the state starts 82 */ 83 @CallSuper onStateSetStart(STATE_TYPE state)84 public void onStateSetStart(STATE_TYPE state) { 85 if (mDeferredResumePending) { 86 handleDeferredResume(); 87 } 88 } 89 90 /** 91 * Called when transition to state ends 92 */ onStateSetEnd(STATE_TYPE state)93 public void onStateSetEnd(STATE_TYPE state) { } 94 95 /** 96 * Creates a factory for atomic state animations 97 */ createAtomicAnimationFactory()98 public AtomicAnimationFactory<STATE_TYPE> createAtomicAnimationFactory() { 99 return new AtomicAnimationFactory(0); 100 } 101 102 @Override reapplyUi()103 public void reapplyUi() { 104 reapplyUi(true /* cancelCurrentAnimation */); 105 } 106 107 /** 108 * Re-applies if any state transition is not running, optionally cancelling 109 * the transition if requested. 110 */ reapplyUi(boolean cancelCurrentAnimation)111 public void reapplyUi(boolean cancelCurrentAnimation) { 112 getRootView().dispatchInsets(); 113 getStateManager().reapplyState(cancelCurrentAnimation); 114 } 115 116 @Override onStop()117 protected void onStop() { 118 BaseDragLayer dragLayer = getDragLayer(); 119 final boolean wasActive = isUserActive(); 120 final STATE_TYPE origState = getStateManager().getState(); 121 final int origDragLayerChildCount = dragLayer.getChildCount(); 122 super.onStop(); 123 124 if (!isChangingConfigurations()) { 125 getStateManager().moveToRestState(); 126 } 127 128 // Workaround for b/78520668, explicitly trim memory once UI is hidden 129 onTrimMemory(TRIM_MEMORY_UI_HIDDEN); 130 131 if (wasActive) { 132 // The expected condition is that this activity is stopped because the device goes to 133 // sleep and the UI may have noticeable changes. 134 dragLayer.post(() -> { 135 if ((!getStateManager().isInStableState(origState) 136 // The drag layer may be animating (e.g. dismissing QSB). 137 || dragLayer.getAlpha() < 1 138 // Maybe an ArrowPopup is closed. 139 || dragLayer.getChildCount() != origDragLayerChildCount)) { 140 onUiChangedWhileSleeping(); 141 } 142 }); 143 } 144 } 145 146 /** 147 * Called if the Activity UI changed while the activity was not visible 148 */ onUiChangedWhileSleeping()149 protected void onUiChangedWhileSleeping() { } 150 handleDeferredResume()151 private void handleDeferredResume() { 152 if (hasBeenResumed() && !getStateManager().getState().hasFlag(FLAG_NON_INTERACTIVE)) { 153 onDeferredResumed(); 154 addActivityFlags(ACTIVITY_STATE_DEFERRED_RESUMED); 155 156 mDeferredResumePending = false; 157 } else { 158 mDeferredResumePending = true; 159 } 160 } 161 162 /** 163 * Called want the activity has stayed resumed for 1 frame. 164 */ onDeferredResumed()165 protected void onDeferredResumed() { } 166 167 @Override onResume()168 protected void onResume() { 169 super.onResume(); 170 171 mHandler.removeCallbacks(mHandleDeferredResume); 172 Utilities.postAsyncCallback(mHandler, mHandleDeferredResume); 173 } 174 } 175