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