Home | History | Annotate | Download | only in misc
      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 */
    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      */
    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      */
    183     public void gc() {
    184         BackgroundThread.getHandler().post(mGcRunnable);
    185     }
    186 
    187     /**
    188      * Returns whether the recents activity is currently visible.
    189      */
    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      */
    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 
    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      */
    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. */
    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 
    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      */
    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      */
    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      */
    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. */
    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      */
    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      */
    347     public int getCurrentUser() {
    348         return mCurrentUserId;
    349     }
    350 
    351     /**
    352      * Returns the processes user id.
    353      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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. */
    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 
    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      */
    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 
    469     public void requestKeyboardShortcuts(
    470             Context context, KeyboardShortcutsReceiver receiver, int deviceId) {
    471         mWm.requestAppKeyboardShortcuts(receiver, deviceId);
    472     }
    473 
    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      */
    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      */
    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 
    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 
    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 {
    530         void onStartActivityResult(boolean succeeded);
    531     }
    532 }
    533