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