• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.systemui.car.displayarea;
18 
19 import static android.view.Display.DEFAULT_DISPLAY;
20 
21 import static com.android.systemui.car.displayarea.CarDisplayAreaController.BACKGROUND_LAYER_INDEX;
22 import static com.android.systemui.car.displayarea.CarDisplayAreaController.CONTROL_BAR_LAYER_INDEX;
23 
24 import android.annotation.NonNull;
25 import android.car.Car;
26 import android.car.app.CarActivityManager;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.res.Resources;
31 import android.graphics.Rect;
32 import android.hardware.display.DisplayManager;
33 import android.os.Handler;
34 import android.util.ArrayMap;
35 import android.util.DisplayMetrics;
36 import android.view.Display;
37 import android.view.SurfaceControl;
38 import android.window.DisplayAreaAppearedInfo;
39 import android.window.DisplayAreaInfo;
40 import android.window.DisplayAreaOrganizer;
41 import android.window.WindowContainerToken;
42 import android.window.WindowContainerTransaction;
43 
44 import com.android.systemui.R;
45 import com.android.wm.shell.common.SyncTransactionQueue;
46 
47 import java.util.ArrayList;
48 import java.util.List;
49 import java.util.concurrent.Executor;
50 
51 import javax.inject.Inject;
52 
53 /**
54  * Organizer for controlling the policies defined in
55  * {@link com.android.server.wm.CarDisplayAreaPolicyProvider}
56  */
57 public class CarDisplayAreaOrganizer extends DisplayAreaOrganizer {
58 
59     /**
60      * The display partition to launch applications by default.
61      */
62     public static final int FOREGROUND_DISPLAY_AREA_ROOT = FEATURE_VENDOR_FIRST + 1;
63     /**
64      * Background applications task container.
65      */
66     public static final int BACKGROUND_TASK_CONTAINER = FEATURE_VENDOR_FIRST + 2;
67     /**
68      * Control bar task container.
69      */
70     public static final int CONTROL_BAR_DISPLAY_AREA = FEATURE_VENDOR_FIRST + 4;
71     public static final int FEATURE_TITLE_BAR = FEATURE_VENDOR_FIRST + 5;
72     static final int FEATURE_VOICE_PLATE = FEATURE_VENDOR_FIRST + 6;
73     private static final String TAG = "CarDisplayAreaOrganizer";
74     private final ComponentName mControlBarActivityName;
75     private final List<ComponentName> mBackGroundActivities;
76 
77     private final Context mContext;
78     private final SyncTransactionQueue mTransactionQueue;
79     private final Rect mForegroundApplicationDisplayBounds = new Rect();
80     private final Rect mBackgroundApplicationDisplayBounds = new Rect();
81     private final CarDisplayAreaAnimationController mAnimationController;
82     private final Handler mHandlerForAnimation;
83     private final ArrayMap<WindowContainerToken, SurfaceControl> mDisplayAreaTokenMap =
84             new ArrayMap();
85     private final Car.CarServiceLifecycleListener mCarServiceLifecycleListener =
86             new Car.CarServiceLifecycleListener() {
87                 @Override
88                 public void onLifecycleChanged(@NonNull Car car, boolean ready) {
89                     if (ready) {
90                         CarActivityManager carAm = (CarActivityManager) car.getCarManager(
91                                 Car.CAR_ACTIVITY_SERVICE);
92                         for (ComponentName backgroundCmp : mBackGroundActivities) {
93                             CarDisplayAreaUtils.setPersistentActivity(carAm, backgroundCmp,
94                                     BACKGROUND_TASK_CONTAINER, "Background");
95                         }
96                         CarDisplayAreaUtils.setPersistentActivity(carAm, mControlBarActivityName,
97                                 CONTROL_BAR_DISPLAY_AREA, "ControlBar");
98                     }
99                 }
100             };
101     DisplayAreaAnimationRunnable mDisplayAreaAnimationRunnable = null;
102     private WindowContainerToken mBackgroundDisplayToken;
103     private WindowContainerToken mForegroundDisplayToken;
104     private int mDpiDensity = -1;
105     private DisplayAreaAppearedInfo mBackgroundApplicationDisplay;
106     private DisplayAreaAppearedInfo mForegroundApplicationDisplay;
107     private DisplayAreaAppearedInfo mControlBarDisplay;
108     private boolean mIsRegistered = false;
109     private boolean mIsDisplayAreaAnimating = false;
110     private DisplayAreaComponent.FOREGROUND_DA_STATE mToState;
111     private CarDisplayAreaAnimationCallback mDisplayAreaAnimationCallback =
112             new CarDisplayAreaAnimationCallback() {
113                 @Override
114                 public void onAnimationStart(
115                         CarDisplayAreaAnimationController
116                                 .CarDisplayAreaTransitionAnimator animator) {
117 
118                     mIsDisplayAreaAnimating = true;
119 
120                     mTransactionQueue.runInSync(tx -> {
121                         // Update the foreground panel layer index to animate on top of the
122                         // background DA.
123                         tx.setLayer(mBackgroundApplicationDisplay.getLeash(),
124                                 BACKGROUND_LAYER_INDEX);
125                         tx.setLayer(mForegroundApplicationDisplay.getLeash(),
126                                 BACKGROUND_LAYER_INDEX + 1);
127                         tx.setLayer(mControlBarDisplay.getLeash(),
128                                 CONTROL_BAR_LAYER_INDEX);
129                     });
130                 }
131 
132                 @Override
133                 public void onAnimationEnd(SurfaceControl.Transaction tx,
134                         CarDisplayAreaAnimationController
135                                 .CarDisplayAreaTransitionAnimator animator) {
136                     mIsDisplayAreaAnimating = false;
137                     mAnimationController.removeAnimator(animator.getToken());
138                     if (mAnimationController.isAnimatorsConsumed()) {
139                         WindowContainerTransaction wct = new WindowContainerTransaction();
140                         if (mToState == DisplayAreaComponent.FOREGROUND_DA_STATE.DEFAULT) {
141                             // Foreground DA opens to default height.
142                             updateBackgroundDisplayBounds(wct);
143                         } else if (mToState
144                                 == DisplayAreaComponent.FOREGROUND_DA_STATE.FULL_TO_DEFAULT) {
145                             updateForegroundDisplayBounds(wct);
146                             updateBackgroundDisplayBounds(wct);
147                         }
148                         else if (mToState == DisplayAreaComponent.FOREGROUND_DA_STATE.CONTROL_BAR) {
149                             Intent homeActivityIntent = new Intent(Intent.ACTION_MAIN);
150                             homeActivityIntent.addCategory(Intent.CATEGORY_HOME);
151                             homeActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
152                             mContext.startActivity(homeActivityIntent);
153                         }
154                     }
155                 }
156 
157                 @Override
158                 public void onAnimationCancel(
159                         CarDisplayAreaAnimationController
160                                 .CarDisplayAreaTransitionAnimator animator) {
161                     mIsDisplayAreaAnimating = false;
162                     mAnimationController.removeAnimator(animator.getToken());
163                 }
164             };
165 
166     @Inject
CarDisplayAreaOrganizer(Executor executor, Context context, SyncTransactionQueue tx)167     public CarDisplayAreaOrganizer(Executor executor, Context context, SyncTransactionQueue tx) {
168         super(executor);
169         mContext = context;
170         mTransactionQueue = tx;
171         mControlBarActivityName = ComponentName.unflattenFromString(
172                 context.getResources().getString(R.string.config_controlBarActivity));
173         mBackGroundActivities = new ArrayList<>();
174         String[] backgroundActivities = mContext.getResources().getStringArray(
175                 R.array.config_backgroundActivities);
176         for (String backgroundActivity : backgroundActivities) {
177             mBackGroundActivities
178                     .add(ComponentName.unflattenFromString(backgroundActivity));
179         }
180         mAnimationController = new CarDisplayAreaAnimationController(mContext);
181         mHandlerForAnimation = mContext.getMainThreadHandler();
182 
183         Car.createCar(context, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
184                 mCarServiceLifecycleListener);
185     }
186 
getDpiDensity()187     int getDpiDensity() {
188         if (mDpiDensity != -1) {
189             return mDpiDensity;
190         }
191 
192         DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
193         Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
194         Resources displayResources = mContext.createDisplayContext(display).getResources();
195         mDpiDensity = displayResources.getConfiguration().densityDpi;
196 
197         return mDpiDensity;
198     }
199 
isDisplayAreaAnimating()200     boolean isDisplayAreaAnimating() {
201         return mIsDisplayAreaAnimating;
202     }
203 
204     // WCT will be queued in updateBackgroundDisplayBounds().
updateForegroundDisplayBounds(WindowContainerTransaction wct)205     private void updateForegroundDisplayBounds(WindowContainerTransaction wct) {
206         Rect foregroundApplicationDisplayBound = mForegroundApplicationDisplayBounds;
207         WindowContainerToken foregroundDisplayToken =
208                 mForegroundApplicationDisplay.getDisplayAreaInfo().token;
209 
210         int foregroundDisplayWidthDp =
211                 foregroundApplicationDisplayBound.width() * DisplayMetrics.DENSITY_DEFAULT
212                         / getDpiDensity();
213         int foregroundDisplayHeightDp =
214                 foregroundApplicationDisplayBound.height() * DisplayMetrics.DENSITY_DEFAULT
215                         / getDpiDensity();
216         wct.setBounds(foregroundDisplayToken, foregroundApplicationDisplayBound);
217         wct.setScreenSizeDp(foregroundDisplayToken, foregroundDisplayWidthDp,
218                 foregroundDisplayHeightDp);
219         wct.setSmallestScreenWidthDp(foregroundDisplayToken,
220                 Math.min(foregroundDisplayWidthDp, foregroundDisplayHeightDp));
221     }
222 
updateBackgroundDisplayBounds(WindowContainerTransaction wct)223     private void updateBackgroundDisplayBounds(WindowContainerTransaction wct) {
224         Rect backgroundApplicationDisplayBound = mBackgroundApplicationDisplayBounds;
225         WindowContainerToken backgroundDisplayToken =
226                 mBackgroundApplicationDisplay.getDisplayAreaInfo().token;
227 
228         int backgroundDisplayWidthDp =
229                 backgroundApplicationDisplayBound.width() * DisplayMetrics.DENSITY_DEFAULT
230                         / getDpiDensity();
231         int backgroundDisplayHeightDp =
232                 backgroundApplicationDisplayBound.height() * DisplayMetrics.DENSITY_DEFAULT
233                         / getDpiDensity();
234         wct.setBounds(backgroundDisplayToken, backgroundApplicationDisplayBound);
235         wct.setScreenSizeDp(backgroundDisplayToken, backgroundDisplayWidthDp,
236                 backgroundDisplayHeightDp);
237         wct.setSmallestScreenWidthDp(backgroundDisplayToken,
238                 Math.min(backgroundDisplayWidthDp, backgroundDisplayHeightDp));
239         mTransactionQueue.queue(wct);
240 
241         mTransactionQueue.runInSync(t -> {
242             // Do not set window crop on backgroundApplicationDisplay. Its windowCrop should remain
243             // full screen so that IME doesn't get cropped.
244             t.setPosition(mBackgroundApplicationDisplay.getLeash(),
245                     backgroundApplicationDisplayBound.left,
246                     backgroundApplicationDisplayBound.top);
247         });
248     }
249 
resetWindowsOffset()250     void resetWindowsOffset() {
251         SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
252         mDisplayAreaTokenMap.forEach(
253                 (token, leash) -> {
254                     CarDisplayAreaAnimationController.CarDisplayAreaTransitionAnimator animator =
255                             mAnimationController.getAnimatorMap().remove(token);
256                     if (animator != null && animator.isRunning()) {
257                         animator.cancel();
258                     }
259                     tx.setPosition(leash, /* x= */ 0, /* y= */ 0)
260                             .setWindowCrop(leash, /* width= */ -1, /* height= */ -1)
261                             .setCornerRadius(leash, /* cornerRadius= */ -1);
262                 });
263         tx.apply();
264     }
265 
266     /**
267      * Offsets the windows by a given offset on Y-axis, triggered also from screen rotation.
268      * Directly perform manipulation/offset on the leash.
269      */
scheduleOffset(int fromPos, int toPos, Rect finalBackgroundBounds, Rect finalForegroundBounds, DisplayAreaAppearedInfo backgroundApplicationDisplay, DisplayAreaAppearedInfo foregroundDisplay, DisplayAreaAppearedInfo controlBarDisplay, DisplayAreaComponent.FOREGROUND_DA_STATE toState, int durationMs)270     void scheduleOffset(int fromPos, int toPos,
271             Rect finalBackgroundBounds, Rect finalForegroundBounds,
272             DisplayAreaAppearedInfo backgroundApplicationDisplay,
273             DisplayAreaAppearedInfo foregroundDisplay,
274             DisplayAreaAppearedInfo controlBarDisplay,
275             DisplayAreaComponent.FOREGROUND_DA_STATE toState,
276             int durationMs) {
277         mToState = toState;
278         mBackgroundApplicationDisplay = backgroundApplicationDisplay;
279         mForegroundApplicationDisplay = foregroundDisplay;
280         mControlBarDisplay = controlBarDisplay;
281         mDisplayAreaTokenMap.forEach(
282                 (token, leash) -> {
283                     if (token == mBackgroundDisplayToken) {
284                         mBackgroundApplicationDisplayBounds.set(finalBackgroundBounds);
285                     } else if (token == mForegroundDisplayToken) {
286                         mForegroundApplicationDisplayBounds.set(finalForegroundBounds);
287                         animateWindows(token, leash, fromPos, toPos, durationMs);
288                     }
289                 });
290 
291         if (mToState == DisplayAreaComponent.FOREGROUND_DA_STATE.CONTROL_BAR) {
292             WindowContainerTransaction wct = new WindowContainerTransaction();
293             updateBackgroundDisplayBounds(wct);
294         }
295     }
296 
animateWindows(WindowContainerToken token, SurfaceControl leash, float fromPos, float toPos, int durationMs)297     void animateWindows(WindowContainerToken token, SurfaceControl leash, float fromPos,
298             float toPos, int durationMs) {
299         CarDisplayAreaAnimationController.CarDisplayAreaTransitionAnimator
300                 animator =
301                 mAnimationController.getAnimator(token, leash, fromPos, toPos);
302 
303 
304         if (animator != null) {
305             if (mDisplayAreaAnimationRunnable != null) {
306                 mDisplayAreaAnimationRunnable.stopAnimation();
307                 mHandlerForAnimation.removeCallbacks(mDisplayAreaAnimationRunnable);
308             }
309             mDisplayAreaAnimationRunnable = new DisplayAreaAnimationRunnable(animator, durationMs);
310             mHandlerForAnimation.post(mDisplayAreaAnimationRunnable);
311         }
312     }
313 
314     @Override
onDisplayAreaAppeared(@onNull DisplayAreaInfo displayAreaInfo, @NonNull SurfaceControl leash)315     public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
316             @NonNull SurfaceControl leash) {
317         if (displayAreaInfo.featureId == BACKGROUND_TASK_CONTAINER) {
318             mBackgroundDisplayToken = displayAreaInfo.token;
319         } else if (displayAreaInfo.featureId == FOREGROUND_DISPLAY_AREA_ROOT) {
320             mForegroundDisplayToken = displayAreaInfo.token;
321         }
322         mDisplayAreaTokenMap.put(displayAreaInfo.token, leash);
323     }
324 
325     @Override
onDisplayAreaVanished(@onNull DisplayAreaInfo displayAreaInfo)326     public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) {
327         if (!mIsRegistered) {
328             mDisplayAreaTokenMap.remove(displayAreaInfo.token);
329         }
330     }
331 
332     @Override
registerOrganizer(int displayAreaFeature)333     public List<DisplayAreaAppearedInfo> registerOrganizer(int displayAreaFeature) {
334         List<DisplayAreaAppearedInfo> displayAreaInfos =
335                 super.registerOrganizer(displayAreaFeature);
336         for (DisplayAreaAppearedInfo info : displayAreaInfos) {
337             onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
338         }
339         mIsRegistered = true;
340         return displayAreaInfos;
341     }
342 
343     @Override
unregisterOrganizer()344     public void unregisterOrganizer() {
345         super.unregisterOrganizer();
346         mIsRegistered = false;
347     }
348 
349     /**
350      * A custom runnable with a flag to stop running the code within the {@link #run()} method when
351      * the runnable is in the message queue. In such cases calling
352      * {@link #removeCallbacksAndMessages(null)} won't work it only stops pending messages
353      * (Runnables) not currently running runnable.
354      */
355     private class DisplayAreaAnimationRunnable implements Runnable {
356         private final CarDisplayAreaAnimationController.CarDisplayAreaTransitionAnimator mAnimator;
357         private final int mDurationMs;
358         private boolean mStopAnimation = false;
359 
DisplayAreaAnimationRunnable( CarDisplayAreaAnimationController.CarDisplayAreaTransitionAnimator animator, int durationMs)360         DisplayAreaAnimationRunnable(
361                 CarDisplayAreaAnimationController.CarDisplayAreaTransitionAnimator animator,
362                 int durationMs) {
363             mAnimator = animator;
364             mDurationMs = durationMs;
365         }
366 
367         @Override
run()368         public void run() {
369             if (mStopAnimation) {
370                 return;
371             }
372 
373             mAnimator.addDisplayAreaAnimationCallback(mDisplayAreaAnimationCallback)
374                     .setDuration(mDurationMs)
375                     .start();
376         }
377 
stopAnimation()378         public void stopAnimation() {
379             // we don't call animator.cancel() here because if there is only one animation call
380             // such as just to open the DA then it will get canceled here.
381             mStopAnimation = true;
382         }
383     }
384 }
385