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