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 import java.util.List; 34 35 /** 36 * Abstract activity with state management 37 * @param <STATE_TYPE> Type of state object 38 */ 39 public abstract class StatefulActivity<STATE_TYPE extends BaseState<STATE_TYPE>> 40 extends BaseDraggingActivity { 41 42 public final Handler mHandler = new Handler(); 43 private final Runnable mHandleDeferredResume = this::handleDeferredResume; 44 private boolean mDeferredResumePending; 45 46 private LauncherRootView mRootView; 47 48 /** 49 * Create handlers to control the property changes for this activity 50 */ collectStateHandlers(List<StateHandler> out)51 protected abstract void collectStateHandlers(List<StateHandler> out); 52 53 /** 54 * Returns true if the activity is in the provided state 55 */ isInState(STATE_TYPE state)56 public boolean isInState(STATE_TYPE state) { 57 return getStateManager().getState() == state; 58 } 59 60 /** 61 * Returns the state manager for this activity 62 */ getStateManager()63 public abstract StateManager<STATE_TYPE> getStateManager(); 64 inflateRootView(int layoutId)65 protected void inflateRootView(int layoutId) { 66 mRootView = (LauncherRootView) LayoutInflater.from(this).inflate(layoutId, null); 67 mRootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 68 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 69 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 70 } 71 72 @Override getRootView()73 public final LauncherRootView getRootView() { 74 return mRootView; 75 } 76 77 @Override findViewById(int id)78 public <T extends View> T findViewById(int id) { 79 return mRootView.findViewById(id); 80 } 81 82 /** 83 * Called when transition to the state starts 84 */ 85 @CallSuper onStateSetStart(STATE_TYPE state)86 public void onStateSetStart(STATE_TYPE state) { 87 if (mDeferredResumePending) { 88 handleDeferredResume(); 89 } 90 } 91 92 /** 93 * Called when transition to state ends 94 */ onStateSetEnd(STATE_TYPE state)95 public void onStateSetEnd(STATE_TYPE state) { } 96 97 /** 98 * Creates a factory for atomic state animations 99 */ createAtomicAnimationFactory()100 public AtomicAnimationFactory<STATE_TYPE> createAtomicAnimationFactory() { 101 return new AtomicAnimationFactory(0); 102 } 103 104 @Override reapplyUi()105 public void reapplyUi() { 106 reapplyUi(true /* cancelCurrentAnimation */); 107 } 108 109 /** 110 * Re-applies if any state transition is not running, optionally cancelling 111 * the transition if requested. 112 */ reapplyUi(boolean cancelCurrentAnimation)113 public void reapplyUi(boolean cancelCurrentAnimation) { 114 getRootView().dispatchInsets(); 115 getStateManager().reapplyState(cancelCurrentAnimation); 116 } 117 118 @Override onStop()119 protected void onStop() { 120 BaseDragLayer dragLayer = getDragLayer(); 121 final boolean wasActive = isUserActive(); 122 final STATE_TYPE origState = getStateManager().getState(); 123 final int origDragLayerChildCount = dragLayer.getChildCount(); 124 super.onStop(); 125 126 if (!isChangingConfigurations()) { 127 getStateManager().moveToRestState(); 128 } 129 130 // Workaround for b/78520668, explicitly trim memory once UI is hidden 131 onTrimMemory(TRIM_MEMORY_UI_HIDDEN); 132 133 if (wasActive) { 134 // The expected condition is that this activity is stopped because the device goes to 135 // sleep and the UI may have noticeable changes. 136 dragLayer.post(() -> { 137 if ((!getStateManager().isInStableState(origState) 138 // The drag layer may be animating (e.g. dismissing QSB). 139 || dragLayer.getAlpha() < 1 140 // Maybe an ArrowPopup is closed. 141 || dragLayer.getChildCount() != origDragLayerChildCount)) { 142 onUiChangedWhileSleeping(); 143 } 144 }); 145 } 146 } 147 148 /** 149 * Called if the Activity UI changed while the activity was not visible 150 */ onUiChangedWhileSleeping()151 protected void onUiChangedWhileSleeping() { } 152 handleDeferredResume()153 private void handleDeferredResume() { 154 if (hasBeenResumed() && !getStateManager().getState().hasFlag(FLAG_NON_INTERACTIVE)) { 155 addActivityFlags(ACTIVITY_STATE_DEFERRED_RESUMED); 156 onDeferredResumed(); 157 158 mDeferredResumePending = false; 159 } else { 160 mDeferredResumePending = true; 161 } 162 } 163 164 /** 165 * Called want the activity has stayed resumed for 1 frame. 166 */ onDeferredResumed()167 protected void onDeferredResumed() { } 168 169 @Override onResume()170 protected void onResume() { 171 super.onResume(); 172 173 mHandler.removeCallbacks(mHandleDeferredResume); 174 Utilities.postAsyncCallback(mHandler, mHandleDeferredResume); 175 } 176 } 177