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