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