1 /*
2  * Copyright (C) 2014 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.recents.misc;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
23 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
24 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
25 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
26 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
27 
28 import android.app.ActivityManager;
29 import android.app.ActivityManager.StackInfo;
30 import android.app.ActivityOptions;
31 import android.app.ActivityTaskManager;
32 import android.app.AppGlobals;
33 import android.app.IActivityManager;
34 import android.app.IActivityTaskManager;
35 import android.app.WindowConfiguration;
36 import android.content.ComponentName;
37 import android.content.Context;
38 import android.content.Intent;
39 import android.content.pm.IPackageManager;
40 import android.content.pm.PackageManager;
41 import android.content.res.Resources;
42 import android.graphics.Bitmap;
43 import android.graphics.BitmapFactory;
44 import android.graphics.Canvas;
45 import android.graphics.Paint;
46 import android.graphics.Point;
47 import android.graphics.PorterDuff;
48 import android.graphics.PorterDuffXfermode;
49 import android.graphics.Rect;
50 import android.graphics.drawable.Drawable;
51 import android.os.RemoteException;
52 import android.os.ServiceManager;
53 import android.os.UserHandle;
54 import android.os.UserManager;
55 import android.service.dreams.DreamService;
56 import android.service.dreams.IDreamManager;
57 import android.util.Log;
58 import android.util.MutableBoolean;
59 import android.view.Display;
60 import android.view.IDockedStackListener;
61 import android.view.IWindowManager;
62 import android.view.WindowManager;
63 import android.view.WindowManager.KeyboardShortcutsReceiver;
64 import android.view.WindowManagerGlobal;
65 import android.view.accessibility.AccessibilityManager;
66 
67 import com.android.internal.app.AssistUtils;
68 import com.android.internal.os.BackgroundThread;
69 import com.android.systemui.Dependency;
70 import com.android.systemui.UiOffloadThread;
71 import com.android.systemui.recents.LegacyRecentsImpl;
72 import com.android.systemui.recents.RecentsImpl;
73 import com.android.systemui.statusbar.policy.UserInfoController;
74 
75 import java.util.List;
76 
77 /**
78  * Acts as a shim around the real system services that we need to access data from, and provides
79  * a point of injection when testing UI.
80  */
81 public class SystemServicesProxy {
82     final static String TAG = "SystemServicesProxy";
83 
84     final static BitmapFactory.Options sBitmapOptions;
85     static {
86         sBitmapOptions = new BitmapFactory.Options();
87         sBitmapOptions.inMutable = true;
88         sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
89     }
90 
91     private static SystemServicesProxy sSystemServicesProxy;
92 
93     AccessibilityManager mAccm;
94     ActivityManager mAm;
95     IActivityManager mIam;
96     IActivityTaskManager mIatm;
97     PackageManager mPm;
98     IPackageManager mIpm;
99     private final IDreamManager mDreamManager;
100     private final Context mContext;
101     AssistUtils mAssistUtils;
102     WindowManager mWm;
103     IWindowManager mIwm;
104     UserManager mUm;
105     Display mDisplay;
106     String mRecentsPackage;
107     private int mCurrentUserId;
108 
109     boolean mIsSafeMode;
110 
111     int mDummyThumbnailWidth;
112     int mDummyThumbnailHeight;
113     Paint mBgProtectionPaint;
114     Canvas mBgProtectionCanvas;
115 
116     private final Runnable mGcRunnable = new Runnable() {
117         @Override
118         public void run() {
119             System.gc();
120             System.runFinalization();
121         }
122     };
123 
124     private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
125 
126     private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener =
127             (String name, Drawable picture, String userAccount) ->
128                     mCurrentUserId = mAm.getCurrentUser();
129 
130     /** Private constructor */
SystemServicesProxy(Context context)131     private SystemServicesProxy(Context context) {
132         mContext = context.getApplicationContext();
133         mAccm = AccessibilityManager.getInstance(context);
134         mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
135         mIam = ActivityManager.getService();
136         mIatm = ActivityTaskManager.getService();
137         mPm = context.getPackageManager();
138         mIpm = AppGlobals.getPackageManager();
139         mAssistUtils = new AssistUtils(context);
140         mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
141         mIwm = WindowManagerGlobal.getWindowManagerService();
142         mUm = UserManager.get(context);
143         mDreamManager = IDreamManager.Stub.asInterface(
144                 ServiceManager.checkService(DreamService.DREAM_SERVICE));
145         mDisplay = mWm.getDefaultDisplay();
146         mRecentsPackage = context.getPackageName();
147         mIsSafeMode = mPm.isSafeMode();
148         mCurrentUserId = mAm.getCurrentUser();
149 
150         // Get the dummy thumbnail width/heights
151         Resources res = context.getResources();
152         int wId = com.android.internal.R.dimen.thumbnail_width;
153         int hId = com.android.internal.R.dimen.thumbnail_height;
154         mDummyThumbnailWidth = res.getDimensionPixelSize(wId);
155         mDummyThumbnailHeight = res.getDimensionPixelSize(hId);
156 
157         // Create the protection paints
158         mBgProtectionPaint = new Paint();
159         mBgProtectionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
160         mBgProtectionPaint.setColor(0xFFffffff);
161         mBgProtectionCanvas = new Canvas();
162 
163         // Since SystemServicesProxy can be accessed from a per-SysUI process component, create a
164         // per-process listener to keep track of the current user id to reduce the number of binder
165         // calls to fetch it.
166         UserInfoController userInfoController = Dependency.get(UserInfoController.class);
167         userInfoController.addCallback(mOnUserInfoChangedListener);
168     }
169 
170     /**
171      * Returns the single instance of the {@link SystemServicesProxy}.
172      * This should only be called on the main thread.
173      */
getInstance(Context context)174     public static synchronized SystemServicesProxy getInstance(Context context) {
175         if (sSystemServicesProxy == null) {
176             sSystemServicesProxy = new SystemServicesProxy(context);
177         }
178         return sSystemServicesProxy;
179     }
180 
181     /**
182      * Requests a gc() from the background thread.
183      */
gc()184     public void gc() {
185         BackgroundThread.getHandler().post(mGcRunnable);
186     }
187 
188     /**
189      * Returns whether the recents activity is currently visible.
190      */
isRecentsActivityVisible()191     public boolean isRecentsActivityVisible() {
192         return isRecentsActivityVisible(null);
193     }
194 
195     /**
196      * Returns whether the recents activity is currently visible.
197      *
198      * @param isHomeStackVisible if provided, will return whether the home stack is visible
199      *                           regardless of the recents visibility
200      *
201      * TODO(winsonc): Refactor this check to just use the recents activity lifecycle
202      */
isRecentsActivityVisible(MutableBoolean isHomeStackVisible)203     public boolean isRecentsActivityVisible(MutableBoolean isHomeStackVisible) {
204         if (mIam == null) return false;
205 
206         try {
207             List<StackInfo> stackInfos = mIatm.getAllStackInfos();
208             ActivityManager.StackInfo homeStackInfo = null;
209             ActivityManager.StackInfo fullscreenStackInfo = null;
210             ActivityManager.StackInfo recentsStackInfo = null;
211             for (int i = 0; i < stackInfos.size(); i++) {
212                 final StackInfo stackInfo = stackInfos.get(i);
213                 final WindowConfiguration winConfig = stackInfo.configuration.windowConfiguration;
214                 final int activityType = winConfig.getActivityType();
215                 final int windowingMode = winConfig.getWindowingMode();
216                 if (homeStackInfo == null && activityType == ACTIVITY_TYPE_HOME) {
217                     homeStackInfo = stackInfo;
218                 } else if (fullscreenStackInfo == null && activityType == ACTIVITY_TYPE_STANDARD
219                         && (windowingMode == WINDOWING_MODE_FULLSCREEN
220                             || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
221                     fullscreenStackInfo = stackInfo;
222                 } else if (recentsStackInfo == null && activityType == ACTIVITY_TYPE_RECENTS) {
223                     recentsStackInfo = stackInfo;
224                 }
225             }
226             boolean homeStackVisibleNotOccluded = isStackNotOccluded(homeStackInfo,
227                     fullscreenStackInfo);
228             boolean recentsStackVisibleNotOccluded = isStackNotOccluded(recentsStackInfo,
229                     fullscreenStackInfo);
230             if (isHomeStackVisible != null) {
231                 isHomeStackVisible.value = homeStackVisibleNotOccluded;
232             }
233             ComponentName topActivity = recentsStackInfo != null ?
234                     recentsStackInfo.topActivity : null;
235             return (recentsStackVisibleNotOccluded && topActivity != null
236                     && topActivity.getPackageName().equals(RecentsImpl.RECENTS_PACKAGE)
237                     && LegacyRecentsImpl.RECENTS_ACTIVITIES.contains(topActivity.getClassName()));
238         } catch (RemoteException e) {
239             e.printStackTrace();
240         }
241         return false;
242     }
243 
isStackNotOccluded(ActivityManager.StackInfo stackInfo, ActivityManager.StackInfo fullscreenStackInfo)244     private boolean isStackNotOccluded(ActivityManager.StackInfo stackInfo,
245             ActivityManager.StackInfo fullscreenStackInfo) {
246         boolean stackVisibleNotOccluded = stackInfo == null || stackInfo.visible;
247         if (fullscreenStackInfo != null && stackInfo != null) {
248             boolean isFullscreenStackOccludingg = fullscreenStackInfo.visible &&
249                     fullscreenStackInfo.position > stackInfo.position;
250             stackVisibleNotOccluded &= !isFullscreenStackOccludingg;
251         }
252         return stackVisibleNotOccluded;
253     }
254 
255     /**
256      * Returns whether this device is in the safe mode.
257      */
isInSafeMode()258     public boolean isInSafeMode() {
259         return mIsSafeMode;
260     }
261 
262     /** Moves an already resumed task to the side of the screen to initiate split screen. */
setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, Rect initialBounds)263     public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode,
264             Rect initialBounds) {
265         if (mIatm == null) {
266             return false;
267         }
268 
269         try {
270             return mIatm.setTaskWindowingModeSplitScreenPrimary(taskId, createMode,
271                     true /* onTop */, false /* animate */, initialBounds, true /* showRecents */);
272         } catch (RemoteException e) {
273             e.printStackTrace();
274         }
275         return false;
276     }
277 
getSplitScreenPrimaryStack()278     public ActivityManager.StackInfo getSplitScreenPrimaryStack() {
279         try {
280             return mIatm.getStackInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
281         } catch (RemoteException e) {
282             return null;
283         }
284     }
285 
286     /**
287      * @return whether there are any docked tasks for the current user.
288      */
hasDockedTask()289     public boolean hasDockedTask() {
290         if (mIam == null) return false;
291 
292         ActivityManager.StackInfo stackInfo = getSplitScreenPrimaryStack();
293         if (stackInfo != null) {
294             int userId = getCurrentUser();
295             boolean hasUserTask = false;
296             for (int i = stackInfo.taskUserIds.length - 1; i >= 0 && !hasUserTask; i--) {
297                 hasUserTask = (stackInfo.taskUserIds[i] == userId);
298             }
299             return hasUserTask;
300         }
301         return false;
302     }
303 
304     /**
305      * Returns whether there is a soft nav bar on specified display.
306      *
307      * @param displayId the id of display to check if there is a software navigation bar.
308      */
hasSoftNavigationBar(int displayId)309     public boolean hasSoftNavigationBar(int displayId) {
310         try {
311             return mIwm.hasNavigationBar(displayId);
312         } catch (RemoteException e) {
313             e.printStackTrace();
314         }
315         return false;
316     }
317 
318     /**
319      * Returns whether the device has a transposed nav bar (on the right of the screen) in the
320      * current display orientation.
321      */
hasTransposedNavigationBar()322     public boolean hasTransposedNavigationBar() {
323         Rect insets = new Rect();
324         getStableInsets(insets);
325         return insets.right > 0;
326     }
327 
328     /** Set the task's windowing mode. */
setTaskWindowingMode(int taskId, int windowingMode)329     public void setTaskWindowingMode(int taskId, int windowingMode) {
330         if (mIatm == null) return;
331 
332         try {
333             mIatm.setTaskWindowingMode(taskId, windowingMode, false /* onTop */);
334         } catch (RemoteException | IllegalArgumentException e) {
335             e.printStackTrace();
336         }
337     }
338 
339     /**
340      * Returns whether the provided {@param userId} represents the system user.
341      */
isSystemUser(int userId)342     public boolean isSystemUser(int userId) {
343         return userId == UserHandle.USER_SYSTEM;
344     }
345 
346     /**
347      * Returns the current user id.  Used instead of KeyguardUpdateMonitor in SystemUI components
348      * that run in the non-primary SystemUI process.
349      */
getCurrentUser()350     public int getCurrentUser() {
351         return mCurrentUserId;
352     }
353 
354     /**
355      * Returns the processes user id.
356      */
getProcessUser()357     public int getProcessUser() {
358         if (mUm == null) return 0;
359         return mUm.getUserHandle();
360     }
361 
362     /**
363      * Returns whether touch exploration is currently enabled.
364      */
isTouchExplorationEnabled()365     public boolean isTouchExplorationEnabled() {
366         if (mAccm == null) return false;
367 
368         return mAccm.isEnabled() && mAccm.isTouchExplorationEnabled();
369     }
370 
371     /**
372      * Returns whether the current task is in screen-pinning mode.
373      */
isScreenPinningActive()374     public boolean isScreenPinningActive() {
375         if (mIam == null) return false;
376 
377         try {
378             return mIatm.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED;
379         } catch (RemoteException e) {
380             return false;
381         }
382     }
383 
384     /**
385      * Returns the smallest width/height.
386      */
getDeviceSmallestWidth()387     public int getDeviceSmallestWidth() {
388         if (mDisplay == null) return 0;
389 
390         Point smallestSizeRange = new Point();
391         Point largestSizeRange = new Point();
392         mDisplay.getCurrentSizeRange(smallestSizeRange, largestSizeRange);
393         return smallestSizeRange.x;
394     }
395 
396     /**
397      * Returns the current display rect in the current display orientation.
398      */
getDisplayRect()399     public Rect getDisplayRect() {
400         Rect displayRect = new Rect();
401         if (mDisplay == null) return displayRect;
402 
403         Point p = new Point();
404         mDisplay.getRealSize(p);
405         displayRect.set(0, 0, p.x, p.y);
406         return displayRect;
407     }
408 
409     /**
410      * Returns the window rect for the RecentsActivity, based on the dimensions of the recents stack
411      */
getWindowRect()412     public Rect getWindowRect() {
413         Rect windowRect = new Rect();
414         if (mIam == null) return windowRect;
415 
416         try {
417             // Use the recents stack bounds, fallback to fullscreen stack if it is null
418             ActivityManager.StackInfo stackInfo =
419                     mIatm.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
420             if (stackInfo == null) {
421                 stackInfo = mIatm.getStackInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
422             }
423             if (stackInfo != null) {
424                 windowRect.set(stackInfo.bounds);
425             }
426         } catch (RemoteException e) {
427             e.printStackTrace();
428         } finally {
429             return windowRect;
430         }
431     }
432 
startActivityAsUserAsync(Intent intent, ActivityOptions opts)433     public void startActivityAsUserAsync(Intent intent, ActivityOptions opts) {
434         mUiOffloadThread.submit(() -> mContext.startActivityAsUser(intent,
435                 opts != null ? opts.toBundle() : null, UserHandle.CURRENT));
436     }
437 
438     /** Starts an in-place animation on the front most application windows. */
startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts)439     public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) {
440         if (mIam == null) return;
441 
442         try {
443             mIatm.startInPlaceAnimationOnFrontMostApplication(
444                     opts == null ? null : opts.toBundle());
445         } catch (Exception e) {
446             e.printStackTrace();
447         }
448     }
449 
registerDockedStackListener(IDockedStackListener listener)450     public void registerDockedStackListener(IDockedStackListener listener) {
451         if (mWm == null) return;
452 
453         try {
454             mIwm.registerDockedStackListener(listener);
455         } catch (Exception e) {
456             e.printStackTrace();
457         }
458     }
459 
460     /**
461      * Calculates the size of the dock divider in the current orientation.
462      */
getDockedDividerSize(Context context)463     public int getDockedDividerSize(Context context) {
464         Resources res = context.getResources();
465         int dividerWindowWidth = res.getDimensionPixelSize(
466                 com.android.internal.R.dimen.docked_stack_divider_thickness);
467         int dividerInsets = res.getDimensionPixelSize(
468                 com.android.internal.R.dimen.docked_stack_divider_insets);
469         return dividerWindowWidth - 2 * dividerInsets;
470     }
471 
requestKeyboardShortcuts( Context context, KeyboardShortcutsReceiver receiver, int deviceId)472     public void requestKeyboardShortcuts(
473             Context context, KeyboardShortcutsReceiver receiver, int deviceId) {
474         mWm.requestAppKeyboardShortcuts(receiver, deviceId);
475     }
476 
getStableInsets(Rect outStableInsets)477     public void getStableInsets(Rect outStableInsets) {
478         if (mWm == null) return;
479 
480         try {
481             mIwm.getStableInsets(Display.DEFAULT_DISPLAY, outStableInsets);
482         } catch (Exception e) {
483             e.printStackTrace();
484         }
485     }
486 
487     /**
488      * Updates the visibility of recents.
489      */
setRecentsVisibility(final boolean visible)490     public void setRecentsVisibility(final boolean visible) {
491         mUiOffloadThread.submit(() -> {
492             try {
493                 mIwm.setRecentsVisibility(visible);
494             } catch (RemoteException e) {
495                 Log.e(TAG, "Unable to reach window manager", e);
496             }
497         });
498     }
499 
500     /**
501      * Updates the visibility of the picture-in-picture.
502      */
setPipVisibility(final boolean visible)503     public void setPipVisibility(final boolean visible) {
504         mUiOffloadThread.submit(() -> {
505             try {
506                 mIwm.setPipVisibility(visible);
507             } catch (RemoteException e) {
508                 Log.e(TAG, "Unable to reach window manager", e);
509             }
510         });
511     }
512 
isDreaming()513     public boolean isDreaming() {
514         try {
515             return mDreamManager.isDreaming();
516         } catch (RemoteException e) {
517             Log.e(TAG, "Failed to query dream manager.", e);
518         }
519         return false;
520     }
521 
awakenDreamsAsync()522     public void awakenDreamsAsync() {
523         mUiOffloadThread.submit(() -> {
524             try {
525                 mDreamManager.awaken();
526             } catch (RemoteException e) {
527                 e.printStackTrace();
528             }
529         });
530     }
531 
532     public interface StartActivityFromRecentsResultListener {
onStartActivityResult(boolean succeeded)533         void onStartActivityResult(boolean succeeded);
534     }
535 }
536