• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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