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 android.content.pm.ActivityInfo.CONFIG_ORIENTATION; 19 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE; 20 21 import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS; 22 import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE; 23 24 import android.content.res.Configuration; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.view.LayoutInflater; 28 import android.view.View; 29 30 import androidx.annotation.CallSuper; 31 32 import com.android.launcher3.AbstractFloatingView; 33 import com.android.launcher3.BaseDraggingActivity; 34 import com.android.launcher3.LauncherRootView; 35 import com.android.launcher3.Utilities; 36 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory; 37 import com.android.launcher3.statemanager.StateManager.StateHandler; 38 import com.android.launcher3.util.window.WindowManagerProxy; 39 import com.android.launcher3.views.BaseDragLayer; 40 41 import java.util.List; 42 43 /** 44 * Abstract activity with state management 45 * @param <STATE_TYPE> Type of state object 46 */ 47 public abstract class StatefulActivity<STATE_TYPE extends BaseState<STATE_TYPE>> 48 extends BaseDraggingActivity { 49 50 public final Handler mHandler = new Handler(); 51 private final Runnable mHandleDeferredResume = this::handleDeferredResume; 52 private boolean mDeferredResumePending; 53 54 private LauncherRootView mRootView; 55 56 protected Configuration mOldConfig; 57 private int mOldRotation; 58 59 @Override onCreate(Bundle savedInstanceState)60 protected void onCreate(Bundle savedInstanceState) { 61 super.onCreate(savedInstanceState); 62 63 mOldConfig = new Configuration(getResources().getConfiguration()); 64 mOldRotation = WindowManagerProxy.INSTANCE.get(this).getRotation(this); 65 } 66 67 /** 68 * Create handlers to control the property changes for this activity 69 */ collectStateHandlers(List<StateHandler> out)70 protected abstract void collectStateHandlers(List<StateHandler> out); 71 72 /** 73 * Returns true if the activity is in the provided state 74 */ isInState(STATE_TYPE state)75 public boolean isInState(STATE_TYPE state) { 76 return getStateManager().getState() == state; 77 } 78 79 /** 80 * Returns the state manager for this activity 81 */ getStateManager()82 public abstract StateManager<STATE_TYPE> getStateManager(); 83 inflateRootView(int layoutId)84 protected void inflateRootView(int layoutId) { 85 mRootView = (LauncherRootView) LayoutInflater.from(this).inflate(layoutId, null); 86 mRootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 87 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 88 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 89 } 90 91 @Override getRootView()92 public final LauncherRootView getRootView() { 93 return mRootView; 94 } 95 96 @Override findViewById(int id)97 public <T extends View> T findViewById(int id) { 98 return mRootView.findViewById(id); 99 } 100 101 /** 102 * Called when transition to the state starts 103 */ 104 @CallSuper onStateSetStart(STATE_TYPE state)105 public void onStateSetStart(STATE_TYPE state) { 106 if (mDeferredResumePending) { 107 handleDeferredResume(); 108 } 109 110 if (state.hasFlag(FLAG_CLOSE_POPUPS)) { 111 AbstractFloatingView.closeAllOpenViews(this, !state.hasFlag(FLAG_NON_INTERACTIVE)); 112 } 113 } 114 115 /** 116 * Called when transition to state ends 117 */ onStateSetEnd(STATE_TYPE state)118 public void onStateSetEnd(STATE_TYPE state) { } 119 120 /** 121 * Creates a factory for atomic state animations 122 */ createAtomicAnimationFactory()123 public AtomicAnimationFactory<STATE_TYPE> createAtomicAnimationFactory() { 124 return new AtomicAnimationFactory(0); 125 } 126 127 @Override reapplyUi()128 public void reapplyUi() { 129 reapplyUi(true /* cancelCurrentAnimation */); 130 } 131 132 /** 133 * Re-applies if any state transition is not running, optionally cancelling 134 * the transition if requested. 135 */ reapplyUi(boolean cancelCurrentAnimation)136 public void reapplyUi(boolean cancelCurrentAnimation) { 137 getRootView().dispatchInsets(); 138 getStateManager().reapplyState(cancelCurrentAnimation); 139 } 140 141 @Override onStop()142 protected void onStop() { 143 BaseDragLayer dragLayer = getDragLayer(); 144 final boolean wasActive = isUserActive(); 145 final STATE_TYPE origState = getStateManager().getState(); 146 final int origDragLayerChildCount = dragLayer.getChildCount(); 147 super.onStop(); 148 149 if (!isChangingConfigurations()) { 150 getStateManager().moveToRestState(); 151 } 152 153 // Workaround for b/78520668, explicitly trim memory once UI is hidden 154 onTrimMemory(TRIM_MEMORY_UI_HIDDEN); 155 156 if (wasActive) { 157 // The expected condition is that this activity is stopped because the device goes to 158 // sleep and the UI may have noticeable changes. 159 dragLayer.post(() -> { 160 if ((!getStateManager().isInStableState(origState) 161 // The drag layer may be animating (e.g. dismissing QSB). 162 || dragLayer.getAlpha() < 1 163 // Maybe an ArrowPopup is closed. 164 || dragLayer.getChildCount() != origDragLayerChildCount)) { 165 onUiChangedWhileSleeping(); 166 } 167 }); 168 } 169 } 170 171 /** 172 * Called if the Activity UI changed while the activity was not visible 173 */ onUiChangedWhileSleeping()174 public void onUiChangedWhileSleeping() { } 175 handleDeferredResume()176 private void handleDeferredResume() { 177 if (hasBeenResumed() && !getStateManager().getState().hasFlag(FLAG_NON_INTERACTIVE)) { 178 addActivityFlags(ACTIVITY_STATE_DEFERRED_RESUMED); 179 onDeferredResumed(); 180 181 mDeferredResumePending = false; 182 } else { 183 mDeferredResumePending = true; 184 } 185 } 186 187 /** 188 * Called want the activity has stayed resumed for 1 frame. 189 */ onDeferredResumed()190 protected void onDeferredResumed() { } 191 192 @Override onResume()193 protected void onResume() { 194 super.onResume(); 195 196 mHandler.removeCallbacks(mHandleDeferredResume); 197 Utilities.postAsyncCallback(mHandler, mHandleDeferredResume); 198 } 199 200 /** 201 * Runs the given {@param r} runnable when this activity binds to the touch interaction service. 202 */ runOnBindToTouchInteractionService(Runnable r)203 public void runOnBindToTouchInteractionService(Runnable r) { 204 r.run(); 205 } 206 207 @Override onConfigurationChanged(Configuration newConfig)208 public void onConfigurationChanged(Configuration newConfig) { 209 handleConfigurationChanged(newConfig); 210 super.onConfigurationChanged(newConfig); 211 } 212 213 /** 214 * Handles configuration change when system calls {@link #onConfigurationChanged}, or on other 215 * situations that configuration might change. 216 */ handleConfigurationChanged(Configuration newConfig)217 public void handleConfigurationChanged(Configuration newConfig) { 218 int diff = newConfig.diff(mOldConfig); 219 int rotation = WindowManagerProxy.INSTANCE.get(this).getRotation(this); 220 if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0 221 || rotation != mOldRotation) { 222 onHandleConfigurationChanged(); 223 } 224 225 mOldConfig.setTo(newConfig); 226 mOldRotation = rotation; 227 } 228 229 /** 230 * Logic for when device configuration changes (rotation, screen size change, multi-window, 231 * etc.) 232 */ onHandleConfigurationChanged()233 protected abstract void onHandleConfigurationChanged(); 234 235 /** 236 * Enter staged split directly from the current running app. 237 * @param leftOrTop if the staged split will be positioned left or top. 238 */ enterStageSplitFromRunningApp(boolean leftOrTop)239 public void enterStageSplitFromRunningApp(boolean leftOrTop) { } 240 } 241