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