• 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 
17 package com.android.wm.shell.onehanded;
18 
19 import static com.android.internal.jank.Cuj.CUJ_ONE_HANDED_ENTER_TRANSITION;
20 import static com.android.internal.jank.Cuj.CUJ_ONE_HANDED_EXIT_TRANSITION;
21 import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_EXIT;
22 import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_TRIGGER;
23 
24 import android.content.Context;
25 import android.graphics.Rect;
26 import android.os.Handler;
27 import android.os.SystemProperties;
28 import android.text.TextUtils;
29 import android.util.ArrayMap;
30 import android.view.SurfaceControl;
31 import android.window.DisplayAreaAppearedInfo;
32 import android.window.DisplayAreaInfo;
33 import android.window.DisplayAreaOrganizer;
34 import android.window.WindowContainerToken;
35 import android.window.WindowContainerTransaction;
36 
37 import androidx.annotation.NonNull;
38 import androidx.annotation.Nullable;
39 import androidx.annotation.VisibleForTesting;
40 
41 import com.android.internal.jank.Cuj.CujType;
42 import com.android.internal.jank.InteractionJankMonitor;
43 import com.android.wm.shell.R;
44 import com.android.wm.shell.common.DisplayLayout;
45 import com.android.wm.shell.common.ShellExecutor;
46 import com.android.wm.shell.shared.annotations.ShellMainThread;
47 
48 import java.io.PrintWriter;
49 import java.util.ArrayList;
50 import java.util.List;
51 import java.util.Map;
52 
53 /**
54  * Manages OneHanded display areas such as offset.
55  *
56  * This class listens on {@link DisplayAreaOrganizer} callbacks for windowing mode change
57  * both to and from OneHanded and issues corresponding animation if applicable.
58  * Normally, we apply series of {@link SurfaceControl.Transaction} when the animator is running
59  * and files a final {@link WindowContainerTransaction} at the end of the transition.
60  *
61  * This class is also responsible for translating one handed operations within SysUI component
62  */
63 public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
64     private static final String TAG = "OneHandedDisplayAreaOrganizer";
65     private static final String ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION =
66             "persist.debug.one_handed_translate_animation_duration";
67 
68     private DisplayLayout mDisplayLayout = new DisplayLayout();
69 
70     private final Rect mLastVisualDisplayBounds = new Rect();
71     private final Rect mDefaultDisplayBounds = new Rect();
72     private final OneHandedSettingsUtil mOneHandedSettingsUtil;
73     private final InteractionJankMonitor mJankMonitor;
74     private final Context mContext;
75     @ShellMainThread
76     private final Handler mHandler;
77 
78     private boolean mIsReady;
79     private float mLastVisualOffset = 0;
80     private int mEnterExitAnimationDurationMs;
81 
82     private ArrayMap<WindowContainerToken, SurfaceControl> mDisplayAreaTokenMap = new ArrayMap();
83     private OneHandedAnimationController mAnimationController;
84     private OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
85             mSurfaceControlTransactionFactory;
86     private OneHandedTutorialHandler mTutorialHandler;
87     private List<OneHandedTransitionCallback> mTransitionCallbacks = new ArrayList<>();
88 
89     @VisibleForTesting
90     OneHandedAnimationCallback mOneHandedAnimationCallback =
91             new OneHandedAnimationCallback() {
92                 @Override
93                 public void onOneHandedAnimationStart(
94                         OneHandedAnimationController.OneHandedTransitionAnimator animator) {
95                     final boolean isEntering = animator.getTransitionDirection()
96                             == TRANSITION_DIRECTION_TRIGGER;
97                     if (!mTransitionCallbacks.isEmpty()) {
98                         for (int i = mTransitionCallbacks.size() - 1; i >= 0; i--) {
99                             final OneHandedTransitionCallback cb = mTransitionCallbacks.get(i);
100                             cb.onStartTransition(isEntering);
101                         }
102                     }
103                 }
104 
105                 @Override
106                 public void onOneHandedAnimationEnd(SurfaceControl.Transaction tx,
107                         OneHandedAnimationController.OneHandedTransitionAnimator animator) {
108                     mAnimationController.removeAnimator(animator.getToken());
109                     final boolean isEntering = animator.getTransitionDirection()
110                             == TRANSITION_DIRECTION_TRIGGER;
111                     if (mAnimationController.isAnimatorsConsumed()) {
112                         endCUJTracing(isEntering ? CUJ_ONE_HANDED_ENTER_TRANSITION
113                                 : CUJ_ONE_HANDED_EXIT_TRANSITION);
114                         finishOffset((int) animator.getDestinationOffset(),
115                                 animator.getTransitionDirection());
116                     }
117                 }
118 
119                 @Override
120                 public void onOneHandedAnimationCancel(
121                         OneHandedAnimationController.OneHandedTransitionAnimator animator) {
122                     mAnimationController.removeAnimator(animator.getToken());
123                     final boolean isEntering = animator.getTransitionDirection()
124                             == TRANSITION_DIRECTION_TRIGGER;
125                     if (mAnimationController.isAnimatorsConsumed()) {
126                         cancelCUJTracing(isEntering ? CUJ_ONE_HANDED_ENTER_TRANSITION
127                                 : CUJ_ONE_HANDED_EXIT_TRANSITION);
128                         finishOffset((int) animator.getDestinationOffset(),
129                                 animator.getTransitionDirection());
130                     }
131                 }
132             };
133 
134     /**
135      * Constructor of OneHandedDisplayAreaOrganizer
136      */
OneHandedDisplayAreaOrganizer(Context context, DisplayLayout displayLayout, OneHandedSettingsUtil oneHandedSettingsUtil, OneHandedAnimationController animationController, OneHandedTutorialHandler tutorialHandler, InteractionJankMonitor jankMonitor, ShellExecutor mainExecutor, @ShellMainThread Handler handler)137     public OneHandedDisplayAreaOrganizer(Context context,
138             DisplayLayout displayLayout,
139             OneHandedSettingsUtil oneHandedSettingsUtil,
140             OneHandedAnimationController animationController,
141             OneHandedTutorialHandler tutorialHandler,
142             InteractionJankMonitor jankMonitor,
143             ShellExecutor mainExecutor,
144             @ShellMainThread Handler handler) {
145         super(mainExecutor);
146         mContext = context;
147         mHandler = handler;
148         setDisplayLayout(displayLayout);
149         mOneHandedSettingsUtil = oneHandedSettingsUtil;
150         mAnimationController = animationController;
151         mJankMonitor = jankMonitor;
152         final int animationDurationConfig = context.getResources().getInteger(
153                 R.integer.config_one_handed_translate_animation_duration);
154         mEnterExitAnimationDurationMs =
155                 SystemProperties.getInt(ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION,
156                         animationDurationConfig);
157         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
158         mTutorialHandler = tutorialHandler;
159     }
160 
161     @Override
onDisplayAreaAppeared(@onNull DisplayAreaInfo displayAreaInfo, @NonNull SurfaceControl leash)162     public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
163             @NonNull SurfaceControl leash) {
164         leash.setUnreleasedWarningCallSite(
165                 "OneHandedSiaplyAreaOrganizer.onDisplayAreaAppeared");
166         mDisplayAreaTokenMap.put(displayAreaInfo.token, leash);
167     }
168 
169     @Override
onDisplayAreaVanished(@onNull DisplayAreaInfo displayAreaInfo)170     public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) {
171         final SurfaceControl leash = mDisplayAreaTokenMap.get(displayAreaInfo.token);
172         if (leash != null) {
173             leash.release();
174         }
175         mDisplayAreaTokenMap.remove(displayAreaInfo.token);
176     }
177 
178     @Override
registerOrganizer(int displayAreaFeature)179     public List<DisplayAreaAppearedInfo> registerOrganizer(int displayAreaFeature) {
180         final List<DisplayAreaAppearedInfo> displayAreaInfos =
181                 super.registerOrganizer(displayAreaFeature);
182         for (int i = 0; i < displayAreaInfos.size(); i++) {
183             final DisplayAreaAppearedInfo info = displayAreaInfos.get(i);
184             onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
185         }
186         mIsReady = true;
187         updateDisplayBounds();
188         return displayAreaInfos;
189     }
190 
191     @Override
unregisterOrganizer()192     public void unregisterOrganizer() {
193         super.unregisterOrganizer();
194         mIsReady = false;
195         resetWindowsOffset();
196     }
197 
isReady()198     boolean isReady() {
199         return mIsReady;
200     }
201 
202     /**
203      * Handler for display rotation changes by {@link DisplayLayout}
204      *
205      * @param context    Any context
206      * @param toRotation target rotation of the display (after rotating).
207      * @param wct        A task transaction {@link WindowContainerTransaction} from
208      *                   {@link DisplayChangeController} to populate.
209      */
onRotateDisplay(Context context, int toRotation, WindowContainerTransaction wct)210     public void onRotateDisplay(Context context, int toRotation, WindowContainerTransaction wct) {
211         if (mDisplayLayout.rotation() == toRotation) {
212             return;
213         }
214         mDisplayLayout.rotateTo(context.getResources(), toRotation);
215         updateDisplayBounds();
216         finishOffset(0, TRANSITION_DIRECTION_EXIT);
217     }
218 
219     /**
220      * Offset the windows by a given offset on Y-axis, triggered also from screen rotation.
221      * Directly perform manipulation/offset on the leash.
222      */
scheduleOffset(int xOffset, int yOffset)223     public void scheduleOffset(int xOffset, int yOffset) {
224         final float fromPos = mLastVisualOffset;
225         final int direction = yOffset > 0
226                 ? TRANSITION_DIRECTION_TRIGGER
227                 : TRANSITION_DIRECTION_EXIT;
228         if (direction == TRANSITION_DIRECTION_TRIGGER) {
229             beginCUJTracing(CUJ_ONE_HANDED_ENTER_TRANSITION, "enterOneHanded");
230         } else {
231             beginCUJTracing(CUJ_ONE_HANDED_EXIT_TRANSITION, "stopOneHanded");
232         }
233         mDisplayAreaTokenMap.forEach(
234                 (token, leash) -> {
235                     animateWindows(token, leash, fromPos, yOffset, direction,
236                             mEnterExitAnimationDurationMs);
237                 });
238         mLastVisualOffset = yOffset;
239     }
240 
241     @VisibleForTesting
resetWindowsOffset()242     void resetWindowsOffset() {
243         final SurfaceControl.Transaction tx =
244                 mSurfaceControlTransactionFactory.getTransaction();
245         mDisplayAreaTokenMap.forEach(
246                 (token, leash) -> {
247                     final OneHandedAnimationController.OneHandedTransitionAnimator animator =
248                             mAnimationController.getAnimatorMap().remove(token);
249                     if (animator != null && animator.isRunning()) {
250                         animator.cancel();
251                     }
252                     tx.setPosition(leash, 0, 0)
253                             .setWindowCrop(leash, -1, -1)
254                             .setCornerRadius(leash, -1);
255                 });
256         tx.apply();
257         mLastVisualOffset = 0;
258         mLastVisualDisplayBounds.offsetTo(0, 0);
259     }
260 
animateWindows(WindowContainerToken token, SurfaceControl leash, float fromPos, float toPos, @OneHandedAnimationController.TransitionDirection int direction, int durationMs)261     private void animateWindows(WindowContainerToken token, SurfaceControl leash, float fromPos,
262             float toPos, @OneHandedAnimationController.TransitionDirection int direction,
263             int durationMs) {
264         final OneHandedAnimationController.OneHandedTransitionAnimator animator =
265                 mAnimationController.getAnimator(token, leash, fromPos, toPos,
266                         mLastVisualDisplayBounds);
267         if (animator != null) {
268             animator.setTransitionDirection(direction)
269                     .addOneHandedAnimationCallback(mOneHandedAnimationCallback)
270                     .addOneHandedAnimationCallback(mTutorialHandler)
271                     .setDuration(durationMs)
272                     .start();
273         }
274     }
275 
276     @VisibleForTesting
finishOffset(int offset, @OneHandedAnimationController.TransitionDirection int direction)277     void finishOffset(int offset, @OneHandedAnimationController.TransitionDirection int direction) {
278         if (direction == TRANSITION_DIRECTION_EXIT) {
279             // We must do this to ensure reset property for leash when exit one handed mode
280             resetWindowsOffset();
281         }
282         mLastVisualOffset = direction == TRANSITION_DIRECTION_TRIGGER ? offset : 0;
283         mLastVisualDisplayBounds.offsetTo(0, Math.round(mLastVisualOffset));
284         for (int i = mTransitionCallbacks.size() - 1; i >= 0; i--) {
285             final OneHandedTransitionCallback cb = mTransitionCallbacks.get(i);
286             if (direction == TRANSITION_DIRECTION_TRIGGER) {
287                 cb.onStartFinished(getLastVisualDisplayBounds());
288             } else {
289                 cb.onStopFinished(getLastVisualDisplayBounds());
290             }
291         }
292     }
293 
294     /**
295      * The latest visual bounds of displayArea translated
296      *
297      * @return Rect latest finish_offset
298      */
getLastVisualDisplayBounds()299     private Rect getLastVisualDisplayBounds() {
300         return mLastVisualDisplayBounds;
301     }
302 
303     @VisibleForTesting
304     @Nullable
getLastDisplayBounds()305     Rect getLastDisplayBounds() {
306         return mLastVisualDisplayBounds;
307     }
308 
getDisplayLayout()309     public DisplayLayout getDisplayLayout() {
310         return mDisplayLayout;
311     }
312 
313     @VisibleForTesting
setDisplayLayout(@onNull DisplayLayout displayLayout)314     void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
315         mDisplayLayout.set(displayLayout);
316         updateDisplayBounds();
317     }
318 
319     @VisibleForTesting
getDisplayAreaTokenMap()320     ArrayMap<WindowContainerToken, SurfaceControl> getDisplayAreaTokenMap() {
321         return mDisplayAreaTokenMap;
322     }
323 
324     @VisibleForTesting
updateDisplayBounds()325     void updateDisplayBounds() {
326         mDefaultDisplayBounds.set(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
327         mLastVisualDisplayBounds.set(mDefaultDisplayBounds);
328     }
329 
330     /**
331      * Register transition callback
332      */
registerTransitionCallback(OneHandedTransitionCallback callback)333     public void registerTransitionCallback(OneHandedTransitionCallback callback) {
334         mTransitionCallbacks.add(callback);
335     }
336 
beginCUJTracing(@ujType int cujType, @Nullable String tag)337     void beginCUJTracing(@CujType int cujType, @Nullable String tag) {
338         final Map.Entry<WindowContainerToken, SurfaceControl> firstEntry =
339                 getDisplayAreaTokenMap().entrySet().iterator().next();
340         final InteractionJankMonitor.Configuration.Builder builder =
341                 InteractionJankMonitor.Configuration.Builder.withSurface(
342                         cujType, mContext, firstEntry.getValue(), mHandler);
343         if (!TextUtils.isEmpty(tag)) {
344             builder.setTag(tag);
345         }
346         mJankMonitor.begin(builder);
347     }
348 
endCUJTracing(@ujType int cujType)349     void endCUJTracing(@CujType int cujType) {
350         mJankMonitor.end(cujType);
351     }
352 
cancelCUJTracing(@ujType int cujType)353     void cancelCUJTracing(@CujType int cujType) {
354         mJankMonitor.cancel(cujType);
355     }
356 
dump(@onNull PrintWriter pw)357     void dump(@NonNull PrintWriter pw) {
358         final String innerPrefix = "  ";
359         pw.println(TAG);
360         pw.print(innerPrefix + "mDisplayLayout.rotation()=");
361         pw.println(mDisplayLayout.rotation());
362         pw.print(innerPrefix + "mDisplayAreaTokenMap=");
363         pw.println(mDisplayAreaTokenMap);
364         pw.print(innerPrefix + "mDefaultDisplayBounds=");
365         pw.println(mDefaultDisplayBounds);
366         pw.print(innerPrefix + "mIsReady=");
367         pw.println(mIsReady);
368         pw.print(innerPrefix + "mLastVisualDisplayBounds=");
369         pw.println(mLastVisualDisplayBounds);
370         pw.print(innerPrefix + "mLastVisualOffset=");
371         pw.println(mLastVisualOffset);
372 
373         if (mAnimationController != null) {
374             mAnimationController.dump(pw);
375         }
376     }
377 }
378