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